What's the easiest way to check when user has downloaded your app?

I'm switching my business model to freemium and now I need a clean way to keep giving access to all features for my existing userbase.

I know I probably should do something with receipt validation but which receipt do I check and is there a very clean and straightforward way of doing this with StoreKit 2 maybe?

I would have loved to see something like: AppStore.originalPurchaseDate property in StoreKit 2 but would love to hear some thoughts and input!

Answered by hiddevdploeg in 715834022

The answer to your question involves appStoreReceipt validation. This is something that both a StoreKit 1 as well as a StoreKit 2 app can perform. When a developer has an existing paid application, but would like to make the app Free with In-App Purchase the transition process is called the “Paid to Freemium” process. The free version of the app will be a upgrade to the existing "paid" version with an upgraded version string. You will modify the app so that at first launch, the app validates the appStoreReceipt. Assuming that the validation status is 0, the app process then checks the JSON payload, for the “original_application_version” value to determine which version of the app, the iTunes user first installed. If the “original_application_version” value is for a previous “paid” app version, the application provides the user with access to the premium content. If the “original_app_version” value is for the current “free” version, then the user must make the In-App Purchase to have access to the premium content. If the user is a “free” app customer, then the user has access to only minimal functionality, but they can access the “Buy” button to obtain premium content access. The “paid” app version customer should never be presented with the “Buy” button.

To determine whether a customer should have access to the content check the "original_app_version" field.

The specific process for a paid to freemium app

  1. on first launch, the app determines whether it has checked the applicationReceipt pointed to by

NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];

(The following step is primarily a sandbox environment issue) If the receipt is not present, the app alerts the user that the applicationReceipt may need refreshing and asks whether the user want to refresh the receipt. If the user agrees, the app makes the SKReceiptRefreshRequest call and process the resulting receipt. See step 2. If the user declines to refresh the receipt, the application makes the assumption that the user is a new user who has no privileges to access premium content and exits the receipt validation process and proceeds to the main part of the app.

The gotcha here is that when you install the app using Xcode,or TestFlight there will not be an appStoreReceipt when the app is first installed. This is true whenever the app is installed in the sandbox environment and not by the production app store. This also happens in App Review which reviews the app in the sandbox environment. One other note, if the app finds no appReceipt, it must not force the issue to require the use of SKReceiptRefreshRequest to have a receipt installed. The SKReceiptRefreshRequest presents the user with an Authentication dialog. If the user cancels this dialog, then treat the user in the same manner as if the appStoreReceipt was present, but the contents of the in_app array is empty or that the user is using the most recent version of the app. The contents of the in_app array is an array of purchase records of In-App Purchases made by the user within the app.

  1. Validating the receipt. I refer you to the Apple Developer documentation.

https://developer.apple.com/documentation/storekit/in-app_purchase/validating_receipts_with_the_app_store

There are 2 validation methods - static or App Store Server receipt validation. For an iOS app, the static method is more complicated in that you are required to obtain and build your own OpenSSL library to use to validate the receipt with. The recommended process is to forward the base64 encoded appStoreReceipt to your server. Your server then forwards the receipt to the iTunes Store verifyReceipt server for validation (known as server validation). An application may send the base64 encoded appStoreReceipt directly to the verifyReceipt server (which makes the validation process susceptible to a man-in-the-middle attack.

The advantage with static receipt validation is that no network connection is required. However, if your application offers an auto-renewable subscription In-App Purchase type, server validation provides specific validation support advantages which is not available under static validation.

What url should I use to verify my receipt?

Use the sandbox URL https://sandbox.itunes.apple.com/verifyReceipt while testing your application in the sandbox and while your application is in review. Use the production URL https://buy.itunes.apple.com/verifyReceipt once your application is live in the App Store.

Important: The App Review team reviews apps in the sandbox.

Always verify your receipt first with the production URL; proceed to verify with the sandbox URL if you receive a 21007 status code. Following this approach ensures that you do not have to switch between URLs while your application is being tested or reviewed in the sandbox or is live in the App Store. The 21007 status code indicates that this receipt is a sandbox receipt, but it was sent to the production service for verification. A status of 0 indicates that the receipt was properly verified.

  1. Parsing the validated receipt.

Assuming that the validation status result is 0, then parse the JSON results and look for the field “original_application_version”. The field “original_application_version” is a UTF8String and must be compared to the CFBundleVersion for the paid version of the app. If the original_application_version is equal to or less than the paid version of the app, the user is a paid app version user and should be given access to the paid content of the app. If the original_application_version is greater than the paid version of the app, then this is a new user who must purchase premium access using In-App Purchase.

The gotcha here is that in the sandbox, using SKReceiptRefreshRequest to install an appStoreReceipt will result in one where the original_application_version is “1.0”. What you want to make sure, is to compare this field with the paid-app version - lets say that the paid app version is “4.0” make sure to compare against this value in the production build of the app. So the issue becomes, if there’s no appStoreReceipt, and the app uses the SKReceiptRefreshRequest call to refresh the receipt, the original_app_version field with be “1.0” - so use that as your test case for a paid app customer. Use the lack of receipt as the new app user test case so that you can test the in app purchase process.

When the app makes it to the production App Store, and a paid user installs the app from the app store, the appStoreReceipt will be present and the original_app_version will match the version the user first installed - or it could be the current version of the app, if for a new user.

rich kubota developer technical support CoreOS/Hardware/MFI

Thank you for the answer Rich. However, this is an extreme amount of work, and most of it is error-prone and brittle. The "original_app_version" and corresponding checking functionality should have been made available through a simple API on StoreKit 2. Please consider adding this functionality to StoreKit. Regards.

Thanks Rich for this clear and extensive answer 😊

However, I do agree with SuperWomble, this is quite a common situation to be in as a developer where you want to change business model so a simple API in StoreKit 2 would make a lot of sense. I’ll file a feedback item to request this

Accepted Answer
What's the easiest way to check when user has downloaded your app?
 
 
Q