1

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)
                    }
                }
            }
        }
    }
}
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256
  • I'm having a same problem, has anyone fixed this? I am using `Xcode 11.5 and swift 5`. Also using the `SwiftyStoreKit` latest version `0.15.0` – Zubair Ahmed Jun 24 '20 at 09:05
  • @ZubairAhmed it’s not the closure, this code works fine even without the closure being hit. I have 2 apps on the AppStore using this same exact code. I would still like to know why the closure is being hit but what you should do is ask another question, add your code, and detail what you did to sign up. I went through whoops and hurdles and had to contact Apple to figure out the issue. Please link your question here when you are finished so I (or someone else) can try to help you. I have a feeling you made the same mistake that I did. – Lance Samaria Jun 24 '20 at 23:48
  • Hey Lance. Would you care to share your mistake instead of leaving it open ended? – Lukas Bimba May 02 '21 at 21:30
  • @LukasBimba hi there, that was so long ago I can’t remember exactly. I have to think this through, gimme a second. Question, did you get rejected or did you come across this question worrying about getting rejected? – Lance Samaria May 02 '21 at 21:34
  • @LukasBimba If I remember correctly, I had to make sure that I was using the correct Sandbox Tester email. On your iPhone, go to: `Settings > Your Username (AppleID, iCloud, iTunes & App Store) > iTunes & App Store > SANDBOX ACCOUNT` -this Apple ID should be whatever you created in AppStoreConnect for the sandbox email. Make sure that you log into the your iPhone and iCloud as the same sandbox tester for whatever APP ID email sandbox tester that you created in AppStoreConnect. Again, it's vague, but I believe that is how I got everything to work correctly. – Lance Samaria May 02 '21 at 21:46

0 Answers0