I am facing this issue since last two months
My App is live with auto-renew InApp purchase before it was working fine then many user complained that when they click on Buy Button Nothing happens so i debug my code and run on my real device I found this error
failed SKPrdouctName
Error Domain=SKErrorDomain Code=2 "Cannot connect to iTunes Store" UserInfo={NSLocalizedDescription=Cannot connect to iTunes Store}
i did not change any code
i did not upload new build
My product is also Approved on Appstore Connect
I did not change anything on the product
I already tried Restarting device, clear caches, login logout apple id still getting the same error
This is happening to many live users
I really need a solution to this
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print(response.products)
products = response.products
for product in response.products {
print(product.localizedTitle)
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print(error.localizedDescription)
}
func requestDidFinish(_ request: SKRequest) {
print(request)
Global.sharedInstance.isConsiderPurchasedOrNot = true
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
print(transaction.transactionState.status(), transaction.payment.productIdentifier)
switch transaction.transactionState {
case .purchasing:
break
case .purchased:
Miscellaneous.APPDELEGATE.window!.stopMyToastActivity()
if Global.sharedInstance.isConsiderPurchasedOrNot{
print(transaction.payment.productIdentifier + "is purchased")
let prodID = transaction.payment.productIdentifier as String
if prodID == MyProducts.autoRenewSubscription
{
Global.sharedInstance.isPurchased = 1
purchaseSubscriber(usediD: Global.getUserID())
}
}
break
case .restored:
print(transaction.payment.productIdentifier + "is purchased/restored! hello ")
// let str1 = UserDefaults.standard.string(forKey: "username")
Global.sharedInstance.isPurchased = 1
let prodID = transaction.payment.productIdentifier as String
Miscellaneous.APPDELEGATE.window!.stopMyToastActivity()
if prodID == MyProducts.autoRenewSubscription
{
Global.sharedInstance.isPurchased = 1
}
case .failed:
print(transaction.payment.productIdentifier + "is failed!")
Miscellaneous.APPDELEGATE.window!.stopMyToastActivity()
default: queue.finishTransaction(transaction)
}
}
}
Related
I'm trying to make an in app purchase inside my app but for some reason when i enter my apple ID and my password i'm getting an error on the log and nothing happens on UI. I nedd to upload my app on test flight or something like this to be able to purchase items from my app?. I have added the payment methods and the tax info.
I'm just calling the purchase product method from my view.
The error:
2021-09-14 19:06:01.857244+0300 iGrow Goals[697:72415] <SKPaymentQueue: 0x2802709f0>: Payment completed with error: Error Domain=ASDErrorDomain Code=500 "Unhandled exception" UserInfo={NSUnderlyingError=0x280ec2610 {Error Domain=AMSErrorDomain Code=100 "Authentication Failed" UserInfo={NSLocalizedFailureReason=The verify credentials call failed., NSLocalizedDescription=Authentication Failed, NSUnderlyingError=0x280ec2700 {Error Domain=AMSServerErrorDomain Code=-5000 "The operation couldn’t be completed. (AMSServerErrorDomain error -5000.)" UserInfo={NSLocalizedDescription=The operation couldn’t be completed. (AMSServerErrorDomain error -5000.)}}}}, NSLocalizedFailureReason=An unknown error occurred, NSLocalizedDescription=Unhandled exception}
StoreManager.swift:
//
// StoreManager.swift
// iGrow Goals
//
// Created by George Sepetadelis on 30/8/21.
//
import Foundation
import StoreKit
class StoreManager: NSObject, ObservableObject, SKProductsRequestDelegate {
#Published var myProducts = [SKProduct]()
var request: SKProductsRequest!
#Published var transactionState: SKPaymentTransactionState?
func enablePremiumPlan() {
}
func purchaseProduct(product: SKProduct) {
if SKPaymentQueue.canMakePayments() {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
} else {
print("User can't make payment.")
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchasing:
transactionState = .purchasing
case .purchased:
UserDefaults.standard.setValue(true, forKey: transaction.payment.productIdentifier)
queue.finishTransaction(transaction)
print("payment success")
transactionState = .purchased
enablePremiumPlan()
case .restored:
UserDefaults.standard.setValue(true, forKey: transaction.payment.productIdentifier)
queue.finishTransaction(transaction)
transactionState = .restored
enablePremiumPlan()
case .failed, .deferred:
queue.finishTransaction(transaction)
transactionState = .failed
print("payment failed")
default:
queue.finishTransaction(transaction)
print("default")
}
}
}
func getProducts(productIDs: [String]) {
print("Start requesting products ...")
let request = SKProductsRequest(productIdentifiers: Set(productIDs))
request.delegate = self
request.start()
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("Did receive response")
if !response.products.isEmpty {
for fetchedProduct in response.products {
DispatchQueue.main.async {
self.myProducts.append(fetchedProduct)
}
}
}
for invalidIdentifier in response.invalidProductIdentifiers {
print("Invalid identifiers found: \(invalidIdentifier)")
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("Request did fail: \(error)")
}
}
Try a sandbox account when testing in app purchases straight out of Xcode.
Your regular account will work on Test Flight and when in the App Store.
https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/testing_in-app_purchases_with_sandbox
I have an app which has inApp purchase feature. The problem is even if I get error, such as the fail case because of "Cannot connect to iTunes Store", the system dialog says "You are all set. Your purchase was successful". You can find my inApp purchase helper class code down.
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
NotificationCenter.default.post(name: .IAPHelperSetPepqueenNotification, object: nil)
if let url = Bundle.main.appStoreReceiptURL {
guard let receipt = try? Data(contentsOf: url) else {
print("error to take receipt")
return
}
let receiptData: String = receipt.base64EncodedString(options: .init(rawValue: 0))
PepappNetwork.request(target: .postReceipt(platform: "ios", receipt: receiptData) , success: { (JSON) in
print(JSON)
let user = User(JSON: JSON["data"].dictionaryObject!)
UserDefaults.standard.set(user?.identifier, forKey: "userID")
user?.persist()
if user?.language != nil {
UserDefaults.standard.set(user!.language!, forKey: "forcedLanguage")
UserDefaults(suiteName: Constants.UserDefaults.containerName)!.set(user!.language!, forKey: "forcedLanguage")
}
NotificationCenter.default.post(name: Notification.Name.CurrentUserChanged, object: nil)
self.complete(transaction: transaction)
}, error: { (errorString, _) in
}) { (MoyaError) in
}
}
break
case .failed:
NotificationCenter.default.post(name: .IAPHelperCancelNotification, object: nil)
fail(transaction: transaction)
break
case .restored:
restore(transaction: transaction)
break
case .deferred:
break
case .purchasing:
break
}
}
}
Fail transaction func
private func fail(transaction: SKPaymentTransaction) {
print("fail...")
if let transactionError = transaction.error as NSError?,
let localizedDescription = transaction.error?.localizedDescription,
transactionError.code != SKError.paymentCancelled.rawValue {
print("Transaction Error: \(localizedDescription)")
}
Before app enter update transactions function "You're all set." dialog has already been show.
There was an issue with the Apple Sandbox, It has been resolved now - https://developer.apple.com/system-status/
Seems like it's a recent Apple bug. I started experiencing it today and it appears when making purchase with sandbox account.
However, in-app purchases still work if you upload an app to TestFlight.
The SKPaymentTransactionObserver.paymentQueue:updatedTransactions returns a array of transactions. When I make a payment, how do I know which transaction is the payment I made? Does it always return one transaction when I make a payment?
Meanwhile, this observer function is also called when restoring transactions. So, what is the best practice to handle the updatedTransactions?
BTW, my subscription product is a auto-renew subscription.
iterate through the loop of the transactions and check for each transaction.
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
completeTransaction(transaction: transaction)
break
case .failed:
failedTransaction(transaction: transaction)
break
case .restored:
restoreTransaction(transaction: transaction)
break
case .deferred:
// TODO show user that is waiting for approval
break
case .purchasing:
break
}
}
}
private func completeTransaction(transaction: SKPaymentTransaction) {
print("completeTransaction...")
deliverPurchaseForIdentifier(identifier: transaction.payment.productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func restoreTransaction(transaction: SKPaymentTransaction) {
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
print("restoreTransaction... \(productIdentifier)")
deliverPurchaseForIdentifier(identifier: productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func failedTransaction(transaction: SKPaymentTransaction) {
if let error = transaction.error as NSError? {
if error.domain == SKErrorDomain {
// handle all possible errors
switch (error.code) {
case SKError.unknown.rawValue:
print("Unknown error")
BaseViewController.currentViewController?.Alert(title: MFErrors.purchaseFaild.messgae.title, msg: MFErrors.purchaseFaild.messgae.body)
case SKError.clientInvalid.rawValue:
print("client is not allowed to issue the request")
BaseViewController.currentViewController?.Alert(title: MFErrors.accountNotAllowedToMakePurchase.messgae.title, msg: MFErrors.accountNotAllowedToMakePurchase.messgae.body)
case SKError.paymentCancelled.rawValue:
print("user cancelled the request")
case SKError.paymentInvalid.rawValue:
print("purchase identifier was invalid")
case SKError.paymentNotAllowed.rawValue:
print("this device is not allowed to make the payment")
BaseViewController.currentViewController?.Alert(title: MFErrors.purchaseFaild.messgae.title, msg: MFErrors.purchaseFaild.messgae.body)
default:
break;
}
}
ProgressViewManager.shared.hide()
}
SKPaymentQueue.default().finishTransaction(transaction)
}
private func deliverPurchaseForIdentifier(identifier: String?) {
guard let identifier = identifier else { return }
//Check if this transactions is a subscription
//SubscriptionsProductIdentifiers is an array of subscriptions product ids you sent to the app store to get SKProducts
//If subscription
if SubscriptionsProductIdentifiers.contains(identifier) {
}else{
//If non-consumable, consumables etc...
}
}
here's complete Store Manager in my previous answer:
How to handle shouldAddStorePayment for In-App Purchases in iOS 11?
I am building an app with In-App Purchases. The app has 6 products for sale (all consumable).
The app is able to recognize the products (from itunes connect) available for purchase using the following code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Apple Store
appleStoreProducts = [productID, product5ID, product25ID, product100ID, product500ID, product1000ID]
SKPaymentQueue.default().add(self)
self.getProductInfo()
}
func getProductInfo() {
if SKPaymentQueue.canMakePayments() {
for product in appleStoreProducts {
let request = SKProductsRequest(productIdentifiers: NSSet(objects: product) as! Set<String>)
request.delegate = self
request.start()
}
} else {
// Tell user that In-App Purchase is disabled in settings
print("In-App Purchase is disabled in settings")
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
var products = response.products
if (products.count != 0) {
let newProduct = products[0]
switch newProduct.localizedTitle {
case "DOLLAR":
product = newProduct
case "five dollars":
product2 = newProduct
case "twenty five":
product3 = newProduct
case "one hundred dollars":
product4 = newProduct
case "thousand dollars":
product5 = newProduct
default:
print("none of the above")
}
print("titile : \(newProduct.localizedTitle) \n description: \(newProduct.localizedDescription)")
}
else {
print("Product not found")
}
let invalids = response.invalidProductIdentifiers
for product in invalids {
print("Product not found: \(product)")
}
}
Then you can click a button to make the purchase of one of the items, for example:
#IBAction func productOne(_ sender: Any) {
let payment = SKPayment(product: product!)
SKPaymentQueue.default().add(payment)
}
My PROBLEM appears next, when the paymentQueue method is called:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case SKPaymentTransactionState.purchased:
// self.pointsBought()
SKPaymentQueue.default().finishTransaction(transaction)
case SKPaymentTransactionState.failed:
SKPaymentQueue.default().finishTransaction(transaction)
print("transaction failed")
default:
print("ESTDO: \(transaction.transactionState)")
break
}
}
}
This method is called twice when I click once the button. The first time the transactionState is neither "purchased" or "failed", so it calls the default option. The second time the result is "failed". However, I don't know what am I doing wrong.
Relevant information:
I am using an iOS device (iphone 6) to run the app
I am using the developer account (of the app) in my iphone
You are failing to cover every case. You need to cover all of them, and you need to call finishTransaction for certain cases. This is the format of a correct updatedTransactions method:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for t in transactions {
switch t.transactionState {
case .purchasing, .deferred: break // do nothing
case .purchased, .restored:
let p = t.payment
if p.productIdentifier == whatever {
// ... do stuff ...
queue.finishTransaction(t)
}
case .failed:
queue.finishTransaction(t)
}
}
}
Ok, so I'm working in Swift and I just need help. I have followed 4 different tutorials on how to implement in app purchases in sprite kit with Swift, copied code verbatim, and nothing is working for me.
Here are the steps I have taken:
Gone in Itunes Connect and created an in app purchase under my app's record. My in app purchase's product Id is "GameOverSaveSavior"
In Xcode, I've turned my app's In app purchase capability to ON, made sure my team is set to my account, and made sure my Bundle Identifier under info is set to my app's bundle identifier in ITunes Connect
Before writing any code, I have import StoreKit in my GameScene.swift file
As for code, this is what I have done:
(1) In Gamescene.swift, at the end of my didMoveToView func, I have:
// Set IAPS
if(SKPaymentQueue.canMakePayments()) {
println("IAP is enabled, loading")
var productID:NSSet = NSSet(objects: "GameOverSaveSavior")
var request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as Set<NSObject>)
request.delegate = self
request.start()
} else {
println("please enable IAPS")
}
This outputs "IAP is enabled, loading" when the app is run.
(2) In GameScene.swift, within the class but outside of didMoveToView, I have all the functions and variables others have used for in app purchases:
var list = [SKProduct]()
var p = SKProduct()
func purchaseMade() {
println("they bought it!")
}
func buyProduct() {
println("buy" + p.productIdentifier)
var pay = SKPayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
}
func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {
println("product request")
var myProduct = response.products
for product in myProduct {
println("product added")
println(product.productIdentifier)
println(product.localizedTitle)
println(product.localizedDescription)
println(product.price)
list.append(product as! SKProduct)
}
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue!) {
println("transactions restored")
var purchasedItemIDS = []
for transaction in queue.transactions {
var t: SKPaymentTransaction = transaction as! SKPaymentTransaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "GameOverSaveSavior":
purchaseMade()
//Right here is where you should put the function that you want to execute when your in app purchase is complete
default:
println("IAP not setup")
}
}
var alert = UIAlertView(title: "Thank You", message: "Your purchase(s) were restored. You may have to restart the app before banner ads are removed.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
println("add paymnet")
for transaction:AnyObject in transactions {
var trans = transaction as! SKPaymentTransaction
println(trans.error)
switch trans.transactionState {
case .Purchased, .Restored:
println("buy, ok unlock iap here")
println(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID {
case "GameOverSaveSavior":
//Here you should put the function you want to execute when the purchase is complete
var alert = UIAlertView(title: "Thank You", message: "You may have to restart the app before the banner ads are removed.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
default:
println("IAP not setup")
}
queue.finishTransaction(trans)
break;
case .Failed:
println("buy error")
queue.finishTransaction(trans)
break;
default:
println("default")
break;
}
}
}
func finishTransaction(trans:SKPaymentTransaction)
{
println("finish trans")
}
func paymentQueue(queue: SKPaymentQueue!, removedTransactions transactions: [AnyObject]!)
{
println("remove trans");
}
This outputs "product request" to the console when the app is run.
(3) In GameScene.swift, in my touchesBegan func, I have the following for when the right button is touched:
//In app purchase
if touchedNode == saveMeBtn {
println("button touched!")
for product in list {
var prodID = product.productIdentifier
if(prodID == "GameOverSaveSavior") {
p = product
buyProduct() //This is one of the functions we added earlier
break;
}
}
This outputs "button touched!" when the button is touched.
I do not know what I am doing wrong. The code compiles with no errors, yet nothing occurs when my button is touched. There is no alert asking the user to sign in or to purchase anything- nothing.
I have mainly followed this question:
in app purchase in SKScene
Is there anything else I should have done prior to writing the code? Am I missing something in my code?
I think I can help you:
I had your issue in the past. And another similar one which I fixed here: My IAP isn't working. Bugs at func Paymentqueue
Here is the solution I had found:
Delete
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
everywhere you have it and put it once (ONLY ONCE) in a place where it will be executed each time your app boots up ( I put it in viewDidLoad()).
This will check for all unfinished transactions and terminate them once the app has loaded, thus removing any possible errors before your users triggers an IAP.
(If this answer helped you, don't forget to upvote ;))
P.S.: Also, this wasn't my issue, but make sure to finishTransaction() for each PurchaseState, like here:
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("Add Payment")
for transaction:AnyObject in transactions{
let trans = transaction as! SKPaymentTransaction
print(trans.error)
switch trans.transactionState{
case .Purchased:
print("IAP unlocked")
print(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID{
case "IAP id":
print("Keep on")
keepOn()
default:
print("IAP not setup")
}
queue.finishTransaction(trans)
break
case .Failed:
print ("Buy error")
queue.finishTransaction(trans)
break
default:
print("default: Error")
break
}
}
}
Never forget this:
queue.finishTransaction(trans)
And make separate case for .Restored.