SwiftyStoreKit: Apple always rejects app because of in app purchase

Bug Report

Apple says,

We found that your in-app purchase products exhibited one or more bugs when reviewed on iPad running iOS 11.4.1 on Wi-Fi connected to an IPv6 network.

Specifically, we are still unable to purchase IAP in your app. Error dialog appears.

Expected Results I didn’t expect to have a problem when Apple checked in-app purchases.

Actual Results A problem happened. Error dialog always appears when apple checks the app. errorAlertDialog()

Additional Context

  • Platform: iOS
  • Purchase type: non-consumable
  • Environment: production
  • SwiftyStoreKit Version: 0.13.3

Related Issues

  • #369 IPv6 Network Rejection & Network Error
  • #81 IPv6 network issue
  • #515 Sandbox testing delays in URL loading

Code

let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "2030202")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
    switch result {
     case .success(let receipt):
          let productId = "xxxxxxxxxxxxxxx"
          let purchaseResult = SwiftyStoreKit.verifyPurchase(
               productId: productId,
               inReceipt: receipt)
          switch purchaseResult {
               case .purchased(let receiptItem):
                // Success
                case .notPurchased:
                    SwiftyStoreKit.restorePurchases(atomically: true) { results in
                         if results.restoreFailedPurchases.count > 0 {
                              self.errorAlertDialog()
                        } else if results.restoredPurchases.count > 0 {
                              print("success")
                        } else {
                              print("Nothing to Restore")
                              //purchase      
                              SwiftyStoreKit.purchaseProduct("xxxxxxxxxxxxxxx", quantity: 1, atomically: true) { result in
                              switch result {
                              case .success(let purchase):
                                   SwiftyStoreKit.finishTransaction(purchase.transaction)
                              case .error(let error):
                                   self.errorAlertDialog()
                            }
                        }     
                    }
                }
            }
      case .error(let error):
          self.errorAlertDialog()
       }
}

About this issue

Most upvoted comments

Hope I’m not too late for some of you. I’ve had a similar problem and used the apple technical support to give me some insight into the issue.

What is the cause of this problem? -> Somewhere in the app, I would try to verify the receipt before the user ever made purchases in the app. Why? Well I wanted to check if the user might have bought something already.

The issue with this is that on the devices that the apple testers use, a call to “verifyReceipt”, “verifySubscription” etc… from within SwiftyStoreKit/ regular StoreKit, will automatically return an “Error could not connect to iTunes”. Even if ´forceRefresh´ is set to false for those functions. Since their devices are always reset to factory settings and they initially do not have iTunes Accounts on them before testing, their devices do not have a receipt. Not even locally. For normal users, this is impossible, since you download your apps through the app store, however the testers at apple install the app via an appLoader and have not signed in to itunes ever. Because of this the app will try to fetch a receipt even when the forceRefresh value is set to false, as it wants to get the initial receipt.

Solution: Don’t verify anything before the user has made a purchase. Apart from initialisation and such, the first call to Storekit/SwiftyStorekit should be ´SwiftyStoreKit.purchaseProduct´ or similar.

If your app depends on verifying the user receipt before allowing purchases, because of some reason -> You can’t! The app will be rejected for reasons stated above.

Yes… (well, I’m pretty sure)

I am about to submit an update, so well see for sure, but I noticed some of the same issues complained about by Apple and I was able to solve them (for the most part) with a little trickery…

What you are probably doing that is causing the issue:

  • Checking the status of and refreshing the current receipt from the Apple Store on FIRST RUN.

This will work completely fine in production, but it would seem, that after some time, it starts to get really crazy in sandbox mode. It caused some very strange behavior on my devices and I’m sure Apple review’s as well, including, being unable to make purchases, no purchase window appearing, locking up your iTunes & App Store (in settings), locking up your App Store app.

I think something must happen there, where it just sits and waits to verify purchases and just gets stuck in some kind of loop. For me, while sandbox testing, I had to reboot the device before it would function normally again, and whenever I sandbox tested, the above listed would happen.

So, what I did to avoid it seemed to work OK. I followed some advice from some other posts and basically, you do the following:

If - this application is on First Run (use NSUserDefaults to track run counts) - DO NOT try to verify receipts, get the receipt, verify purchases, etc UNTIL, the user tries to initiate a purchase. Lift your restriction on checking and verification once the user selects to buy a product.

What this will do is allow the program to function normally, IE, not lock things up, until a purchase is attempted. Once this happens, the purchase will go thru, no problem and verification will then be allowed since your past the first run.

This should work, unless the reviewer installs your app, buys something, then closes it and tries again. You may be met with this issue again at that point, but hopefully, you pass muster and get approved before they get to that point. Again, all of these issues ONLY happen in sandbox mode, production works flawlessly no matter what happens there in the sandbox.

Here is some code of what I tried. As I said, I am about to send a release, so I will know for sure if this works in a few days/hours… but here is my rough code to get this into action:


In my App Delegate, under the Application func
let currentCount = UserDefaults.standard.integer(forKey: "launchCount")
        
        UserDefaults.standard.set(currentCount+1, forKey:"launchCount")
        
        //print(currentCount)
        
        if currentCount == 0 { //had this sent to 1 and it seemed to work, but first run count is 0, so trying this, needs thorough testing.
            UserDefaults.standard.set(false, forKey: "isAllowedToFetch") //turns off IAP checking until restore or 1st buy
        }

IN my code, if the user presses any “Restore Purchases” or attempts to make a purchase, I will insert this code:

UserDefaults.standard.set(true, forKey: "isAllowedToFetch") //turns on IAP

Which will turn on the ability to use IAP

THEN, I use the flag I’ve set to see if were allowed to do this:

if UserDefaults.standard.bool(forKey: "isAllowedToFetch") { // If IAP is on...
    // print("Allowed to fetch")
    getRecieptData(productId: sub.productID, moc: moc, expired: expired)
} else {
    // print("Not allowed to fetch")
}

And that’s it…


So the crux of it here is DO NOT allow your app to run getRecieptData on and/or AFTER the first run unless and until the user initiates a purchase or presses restore purchases somewhere, then allow it always.

Like I stated earlier, I need to fully test this one more time before I send it, If I find any glaring issues, I’ll reply to this thread…

This solution was cobbled together using suggestions from other posts. I hope this helps you.

-LK

@FlashTang

Yes that is a possible solution, in fact that is how I do it too. Once the user has bought something OR restored his/her purchases, I will allow the app to verify purchases upon every startup after that. I store this variable in the user and gave it the name “bIsSubscriber”.

Upon login/installation/signin I set this value to false. Therefore a returning user with previous purchases must go into the settings menu and click a button called “Restore Purchases” to be a subscriber again. Making the same purchase that the user already owns will just restore it automatically. There is nothing you need to implement. He/she will not be billed.

This is the way Apple intends apps to behave and the review team has not given me an issue since.

Following this post seems to have solved my similar issue as reported in #515

As mentioned by others in this thread, this process solved my issue. Thank you!

I set a UserDefault to “disallow” checking of receipts until a purchase has been initiated or the user presses “restore purchases”. This skips the checking for receipts on first run, which it will find none, as described in this post, or in my case, probably completely overwhelmed by the number of receipts it does find and allows sandbox purchasing to be successful every time.

@njovy try to use pod ‘SwiftyStoreKit’, ‘0.14.1’

and when some error occurs on their network you can reply them something like that:

Good morning! Our developers have verified this problem and found that this error occurs when ”unknown” error is received from Apple request for buying a subscription. It’s sometimes happening on your wifi. Please, we are highly asking for requesting “subscription buy” once again.

Currently getting the ‘Code=0 “Cannot connect to iTunes Store”’ Error.

I placed print statements in every verifyReciept or verifySubscription function in SwiftyStoreKit, and none are being called, yet I am still getting this error on my personal device and my simulators, using both tester accounts and my real Apple ID (with my real apple id trying to make a live purchase). However, when I use a real device that has never had the app installed, it works fine.

I should also note that it was working fine up until about a few weeks ago, at which point it just stopped working. Any ideas?