In my Podfile
I'm using SwiftyStoreKit (0.15.0)
:
pod 'SwiftyStoreKit'
My app uses a tip jar for IAP so I only use Consumable
.
I submitted my app to the App Store this morning and got a rejection saying they couldn't make an IAP when trying to make a Consumable
purchase (a tip).
While testing as a sandbox tester when I checked the purchase I kept getting an error: "Unknown error. Please contact support"
:
SwiftyStoreKit.purchaseProduct(product, quantity: 1, atomically: true) { result in
switch result {
case .success(let product):
// fetch content from your server, then:
if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction)
}
print("Purchase Success: \(product.productId)")
case .error(let error):
switch error.code {
case .unknown:
print("Unknown error. Please contact support")
// failed cases ...
}
}
Upon further inspection even though I had this code below in AppDelegate the closure
never gets called. It's necessary this code runs once in AppDelegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Doesn't enter closure
SwiftyStoreKit.completeTransactions(atomically: true) {
// *** it never reaches this point ***
(purchases) in
for purchase in purchases {
switch purchase.transaction.transactionState {
case .purchased, .restored:
if purchase.needsFinishTransaction {
// Deliver content from server, then:
let downloads = purchase.transaction.downloads
SKPaymentQueue.default().start(downloads)
SwiftyStoreKit.finishTransaction(purchase.transaction)
}
// Unlock content
case .failed, .purchasing, .deferred:
break // do nothing
}
}
}
return true
}
My sandbox tester email is confirmed, I signed out of the device as my real self, I logged in on the device as the sandbox tester, I'm logged into iCloud as the sandbox tester.
What could be the reason for the closure not being entered in AppDelegate?
Here's the code from the vc that I use to make the Consumable
purchase:
var dataSource = [Tip]()
var sharedSecret = appStoreConnectSecretKey
let inAppProductIds = ["com.myCo.myAppName.firstTip", // 0.99
"com.myCo.myAppName.secondTip", // 9.99 ]
override func viewDidLoad() {
super.viewDidLoad()
getInAppPurchaseAmounts()
}
func getInAppPurchaseAmounts() {
// show spinner
let dispatchGroup = DispatchGroup()
for productId in inAppProductIds {
dispatchGroup.enter()
SwiftyStoreKit.retrieveProductsInfo([productId]) { [weak self](result) in
if let product = result.retrievedProducts.first {
let priceString = product.localizedPrice!
print("Product: \(product.localizedDescription), price: \(priceString)")
let tip = Tip(displayName: product.description,
description: product.localizedDescription,
productId: productId
price: priceString)
self?.addTipToDataSource(tip)
if let sharedSecret = self?.sharedSecret {
self?.verifyPurchase(with: productId, sharedSecret: sharedSecret)
}
dispatchGroup.leave()
} else if let invalidProductId = result.invalidProductIDs.first {
print("Invalid product identifier: \(invalidProductId)")
dispatchGroup.leave()
} else {
print("Error: \(String(describing: result.error))")
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .global(qos: .background)) { [weak self] in
DispatchQueue.main.async { [weak self] in
// removeSpinnerAndReloadData()
}
}
}
func verifyPurchase(with productId: String, sharedSecret: String) {
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: sharedSecret)
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
switch result {
case .success(let receipt):
let productId = productId
// Verify the purchase of Consumable or NonConsumable
let purchaseResult = SwiftyStoreKit.verifyPurchase(
productId: productId,
inReceipt: receipt)
switch purchaseResult {
case .purchased(let receiptItem):
print("\(productId) is purchased: \(receiptItem)")
case .notPurchased:
print("The user has never purchased \(productId)")
}
case .error(let error):
print("Receipt verification failed: \(error)")
}
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? TipCell else { return }
guard let indexPath = collectionView.indexPath(for: cell) else { return }
let tip = dataSource[indexPath.item]
purchaseProduct(with: tip.productId)
}
func purchaseProduct(with productId: String) {
SwiftyStoreKit.retrieveProductsInfo([productId]) { result in
if let product = result.retrievedProducts.first {
SwiftyStoreKit.purchaseProduct(product, quantity: 1, atomically: true) { result in
switch result {
case .success(let product):
// fetch content from your server, then:
if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction)
}
print("Purchase Success: \(product.productId)")
case .error(let error):
switch error.code {
case .unknown:
print("Unknown error. Please contact support")
case .clientInvalid:
print("Not allowed to make the payment")
case .paymentCancelled:
print("Payment cancelled")
case .paymentInvalid:
print("The purchase identifier was invalid")
case .paymentNotAllowed:
print("The device is not allowed to make the payment")
case .storeProductNotAvailable:
print("The product is not available in the current storefront")
case .cloudServicePermissionDenied:
print("Access to cloud service information is not allowed")
case .cloudServiceNetworkConnectionFailed:
print("Could not connect to the network")
case .cloudServiceRevoked:
print("User has revoked permission to use this cloud service")
default:
print((error as NSError).localizedDescription)
}
}
}
}
}
}