Swift iOS: IAP Non-Renewing Subscription appStoreReceiptURL not found on devices

I am facing an issue with validating user subscription in production environment only, in development environment with sandbox user was and is working fine.

Here is the code snippet where I validate the finished transaction.

Code Block  
  #if DEBUG
        private let verificationUrl = "https://sandbox.itunes.apple.com/verifyReceipt"
    #else
        private let verificationUrl = "https://buy.itunes.apple.com/verifyReceipt"
    #endif
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
            FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
            do {
                print("begin")
                let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
                let receiptString = receiptData.base64EncodedString(options: [])
                let requestData = ["receipt-data": receiptString, "password": "########", "exclude-old-transactions": true] as [String: Any]
                var request = URLRequest(url: URL(string: verificationUrl)!)
                request.httpMethod = "POST"
                request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
                let httpBody = try? JSONSerialization.data(withJSONObject: requestData, options: [])
                request.httpBody = httpBody
                URLSession.shared.dataTask(with: request) { data, _, error in
                    DispatchQueue.main.async {
                        if data != nil {
                            if let json = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) {
                                self.parseReceipt(json as! Dictionary<String, Any>)
                                return
                            }
                        } else {
                            print("error validating receipt: \(error?.localizedDescription ?? "")")
                        }
                    }
                }.resume()
                // Read receiptData
            } catch { print("Couldn't read receipt data with error: " + error.localizedDescription) }
        }


The problem is in the first condition
Code Block  if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
            FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {

In development env with sandbox user, it works fine and the file exists, printing the
Code Block
Bundle.main.appStoreReceiptURL?.path

yields:
Code Block
Optional("/private/var/mobile/Containers/Data/Application/BE5EB9FD-3704-488D-96E8-ADB805300ED7/StoreKit/sandboxReceipt")

In both cases! which is strange, I expected that in production env I would not find sandboxReceipt in the url, but something else. but maybe I am wrong

So the issue is that in production, the FileManager.default.fileExists condition fails as file is not found (I made an else and printed this value which was false) which creates the problem that now if an actual user downloaded the app and payed for the subscription, the transaction will succeed and money will be payed but then the app will not verify and the features will not be unlocked - which is terrible!

I tested with my device and with a friend's device in production mode after 48 hours of releasing the app and the problem happened, I immediately removed the app from sale and now I am trying to find the root cause of the issue. Please help.

Note: I know that local validation is not recommended and I will very soon refactor the code to integrate server side validation (once I have the time and money) so please do not reply back mentioning this point as I am already aware of it.

Tech details:
  1. minimum IOS 13.3

  2. Xcode: Version 11.5 (11E608c)

  3. macOS Catalina: 10.15.5 (19F101)




As I review your code, I suspect that the issue is different. If an App Store in-app purchase transaction is successful and there is no appStoreReceipt, this would be a bug report issue - one which I've never seen reported. What I do see in your code is an error in not following the Receipt Validation algorithm as described in the Apple Developer doc "Validating Receipts with the App Store" <https://developer.apple.com/documentation/storekit/in-app_purchase/validating_receipts_with_the_app_store>

There is the following important note -

Verify your receipt first with the production URL; then verify with the sandbox URL if you receive a 21007 status code. This approach ensures you do not have to switch between URLs while your application is tested, reviewed by App Review, or live in the App Store.

A StoreKit app which validates the appStoreReceipt with the App Store verifyReceipt server must be able to work in both the sandbox and production environments. The app is reviewed by App Review in the sandbox environment so in-app purchase appStoreReceipts must be processed with the sandbox verifyReceipt server. Once the app is approved, the app now must handle validating production in-app purchases with the production verifyReceipt server. As it appears that your app is handling the receipt validation process itself, you'll need to update the validation code to implement the receipt validation algorithm described in the note.

rich kubota - rkubota@apple.com
developer technical support CoreOS/Hardware/MFI
Dear Rick,

Thank you for your support and the time taken to view my question. I believe I did not clarify the issue enough as I understood from your reply. The issue is not with the validation part where I would use the prod or sand URL, the issue comes in the first part where the "appStoreReceiptURL = Bundle.main.appStoreReceiptURL" and "FileManager.default.fileExists(atPath: appStoreReceiptURL.path)" are evaluated. The part you mentioned about the verification URL is called here
Code Block
var request = URLRequest(url: URL(string: verificationUrl)!)

which is inside the code block, and the block is never reached in product.


As far as I understand the "Bundle.main.appStoreReceiptURL" should be present on the device immediately after the user downloads the app from the store. And in my case it wasn't so the whole logic was just ignored, the process did not enter this code block at all in production environment. But entered normally in sandbox environment.

When faced with that issue I created an else at the end of this code block to catch if the
Code Block
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,            FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {

block passed or not and if not (which is the case in production) I printed the values of "FileManager.default.fileExists" and "appStoreReceiptURL.path" which yielded false and the url with "sandboxReceipt" at the end.

I apologise for repeating myself but I need to clearly state that the issue is mainly Bundle.main.appStoreReceiptURL not existing on a device in production

If you judge that this should be reported as a bug, please let me know who/where should I report this.

Thank you,
Eslam

As the issue is - the appStoreReceipt is not present in a production app, this is most unusual. Whenever any user installs an app from the App Store, an appStoreReceipt is also written to the app. This is true for all apps, even those app which do not offer in-app purchases. There is one case where a production app might be present without an appStoreReceipt, in the case that the user backs up the device to a local system, then restores the device from the local backup without internet access. In this specific case, the restored apps on the device will not have an appStoreReceipt.

One other check - when you submit a TestFlight app - which is signed with a production certificate, the TestFlight app will be installed and run in the sandbox environment - and will not have an appStoreReceipt. The same is true for App Review environment. My thought is that when you say that this is a production app, that you've replicated this issue by installing the app from the App Store.

If this issue is happening with a customer - ask them if they can delete the app and re-install the app to see if the issue re-occurs. If it does, please submit a bug report as described below. Please see if you can obtain their iTunes user ID to include with the bug report.

In your case, if the user installed the app from the App Store and the appStoreReceipt is nil, this is a bug report issue. You would use the Apple Developer Feedback Assistant web page <https://feedbackassistant.apple.com> to report this issue. the tricky part in submitting the bug report is providing the evidence of this issue.

If this issue is happening with a customer - ask them if they can delete the app and re-install the app to see if the issue re-occurs. If it does, please submit a bug report as described below. Please obtain the iTunes user ID to include with the bug report.


If you are able to replicate this issue, do the same thing - delete the app, then re-install the app from the App Store - is the appStoreReceipt still missing. If this is the case, the App Store Server team will be interested in the bug report.

Here's the instructions to submit the bug report.

To submit a bug report, please use the Apple Developer Feedback web page -

<https://feedbackassistant.apple.com/>.

Enter the “Feedback Assistant” page and login
Click on the Compose icon to start a new bug report

Start by clicking on the appropriate OS button - “iOS and iPadOS”, “tvOS”, or “macOS”
  • In the “Descriptive Title” field, enter an appropriate description.

  • In the “Problem Area” field select “StoreKit”

  • In the “Type of Feedback” select “Incorrect / Unexpected Behavior”

  • In the “Describe the Issue” section enter the following

    • application ID

    • production environment

    • iTunes user ID


rich kubota - rkubota@apple.com
developer technical support CoreOS/Hardware/MFI
Thank you for your support. This is now clear. Before I proceed with re testing the issue I have a couple of questions.

  1. Do I have to reactivate my app on the App Store to achieve the production env ? is there a workaround or can I limit its availability by user email or Apple ID or something ? The reason for this is that I want to avoid the risk of actual clients buying and losing their money.

  2. I want to confirm this with you, When I downloaded the app, and the IAP failed, then ran the same app from Xcode into the device, I was able to detect the issue, but I wanted to confirm, is that attempt considered in production env or not ?

  3. If I reactivated the app, is it enough just to use my device for testing, I would like to avoid asking other people to spend money, but if its better, I am sure I can work something out with a friend or two :)

In response -
Q. Do I have to reactivate my app on the App Store to achieve the production env? is there a workaround or can I limit its availability by user email or Apple ID or something?
A. This is a question for App Store Connect. However, in order to check this issue, the app must be installed from the App Store. As to limiting access to the app, I think that promo codes could be provided to users - but I'd check with App Store Connect.

Q. I want to confirm this with you, When I downloaded the app, and the IAP failed, then ran the same app from Xcode into the device, I was able to detect the issue, but I wanted to confirm, is that attempt considered in production env or not?
A. When you run the app from Xcode, you are also installing the app from Xcode. This would result in the appStoreReceipt being absent on first launch. If you are trying to diagnose a production app on this issue, implement an NSLog statement to indicate the absence of the appStoreReceipt. After the app is approved, you can install the app from the App Store and see if the log statement is output when the app is first launched. You can connect the device to a macOS system and open the Console App to monitor the console output from the device.

Q. If I reactivated the app, is it enough just to use my device for testing, I would like to avoid asking other people to spend money, but if its better, I am sure I can work something out with a friend or two :)
A. As per the first question, this is more of a question for App Store Connect.
Maybe off topic, but I thought it was not allowed to call verifyReceipt directly from the app. Is this a supported configuration?

I suppose you’d have the same issue trying to find the receipt to upload to your server.
Swift iOS: IAP Non-Renewing Subscription appStoreReceiptURL not found on devices
 
 
Q