IAP Receipt for app bundles

We're validating app receipts and use the original_purchase_date and original_application_version to determine if the user is a legacy user and thus entitled to certain privileges. (We moved from paid to freemium and give legacy users free access to features now requiring a subscription.)

Now we're wondering what values the above fields have if the user purchased an app bundle (consisting of two apps).

  • Is the receipt for both apps the same?
  • If yes, which bundle_id and which original_application_version is sent in the receipt?
  • If not, how is original_application_version set? (According to the version of each app at purchase time?)
  • How is original_purchase_date set? (According to the bundle purchase date? Or the first installation date of each app? – one of the apps could be installed much later!)

Accepted Reply

Is the receipt for both apps the same? No - each app will have an appStoreReceipt the same as if the app were not part of an app bundle. The fact that multiple apps are being sold as a bundle does not change the contents of the appStoreReceipt. The responseBody.receipt fields remain the same as if the app were purchased individually for example. The bundle id is the same and the original_application_version reflects the first version of the specific app installed by the iTunes user.

If not, how is original_application_version set? (According to the version of each app at purchase time?) answered above

How is original_purchase_date set? (According to the bundle purchase date? Or the first installation date of each app? – one of the apps could be installed much later!) For your purposes, I would not rely on the original_purchase_date. In managing a paid-to-freemium situation, you should already know what the application version is for those who paid for the app and newer versions to indicate that the app was installed for free.

The following is a canned response which I have on the subject of transitioning a paid-to-freemium app.

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 to the main part of the app.

The gotcha here is that when you install the app using Xcode, there will not be an appStoreReceipt. This is true whenever the app is installed by some other means than by the app store. This also happens in App Review which uses a custom App Install process. 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 on 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.

  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

Replies

Is the receipt for both apps the same? No - each app will have an appStoreReceipt the same as if the app were not part of an app bundle. The fact that multiple apps are being sold as a bundle does not change the contents of the appStoreReceipt. The responseBody.receipt fields remain the same as if the app were purchased individually for example. The bundle id is the same and the original_application_version reflects the first version of the specific app installed by the iTunes user.

If not, how is original_application_version set? (According to the version of each app at purchase time?) answered above

How is original_purchase_date set? (According to the bundle purchase date? Or the first installation date of each app? – one of the apps could be installed much later!) For your purposes, I would not rely on the original_purchase_date. In managing a paid-to-freemium situation, you should already know what the application version is for those who paid for the app and newer versions to indicate that the app was installed for free.

The following is a canned response which I have on the subject of transitioning a paid-to-freemium app.

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 to the main part of the app.

The gotcha here is that when you install the app using Xcode, there will not be an appStoreReceipt. This is true whenever the app is installed by some other means than by the app store. This also happens in App Review which uses a custom App Install process. 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 on 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.

  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

Thanks, Rich. Great answer. I've one followup though: You mention "The bundle id is the same". Do you mean

  1. it is the same as for an individual purchase (with each app having its own bundle id in the receipt) or
  2. the bundle id is the same for both apps in the bundle?

I assume the first, but I wanna be 100% sure.

Each app in the bundle will have it's own app ID which which remains the same before the bundle and in the bundle.

rich kubota developer technical support CoreOS/Hardware/MFI