I should have responded earlier on this issue, but hope the following will be useful.
With regards to the question about the security of appStoreReceipt validation - the appStoreReceipt is signed by Apple. If the appStoreReceipt is validated by the App Store verifyReceipt server, the results indicate what App Store charges in the receipt. For the case where the in_app array indicates that there is an auto-renewing subscription item, then there is additional information provided in the "latest_receipt", "latest_receipt_info", and in the "pending_renewal_info" fields.
The following is a canned response I provide to developers who submit questions on how to support apps which offer auto-renewing subscription in-app purchases items.
To understand the in app purchase of the auto-renewing subscription item, lets start at the beginning. The user launches the in app purchase application. As per Tech Note 2387 IAP Best Practices
<https://developer.apple.com/library/content/technotes/tn2387/_index.html#//apple_ref/doc/uid/DTS40014795>
the application will call addTransactionObserver - which will activate the StoreKit transactionObserver. The transactionObserver checks the iTunes Store for incompleteTranactions. Refer to the StoreKitSuite iOSInAppPurchaseSample code for an example on implementing the StoreKit API in a shared process, rather than in the ApplicationDelegate class.
The application will validate the registered in app purchase identifiers using the SKProductsRequest to obtain the current description and pricing for the in app purchase identifiers - for example “mycompany.myapp.autorenewsubscription1month”.
At some point, the user decides to make the in app purchase. The user presses the buy button and the addPayment call is made to the iTunes Store to charge the current user for the purchase of “mycompany.myapp.autorenewsubscription1month”. Let’s assume that this is the first time the user has attempted to purchase the 1 mo auto-renewing subscription item and that there is a free trial associated with the purchase. The user’s account is charged and the updatedTransactions delegate method is called with the SKPaymentTransactionStatePurchased.
The app then validates the applicationReceipt, if it chooses to do so - by accessing the receipt at appStoreReceiptURL. The app encodes the receipt in base64, then forwards it to the developer server for processing. (some apps send the receipt directly to the Apple Receipt Validation server process - but this is against recommendation as it can lead to a man-in-the-middle attach). The developer server forwards the base64 encoded receipt with shared secret to the Apple Receipt Validation server process. The validated receipt is returned. The app inspects the in_app array and should find one item with the product_id field set to “mycompany.myapp.autorenewsubscription1month”. The app calculates the length of the subscription by the difference of the expires_date and the purchase_date. The result should be the length of the free trial period - not the one month period plus the free trial period. The app notes that the expire_date is later than the current date so it knows that the subscription is active.
One note on detecting whether the auto-renewing subscription is in the free trial period. You will find in the in_app array record, (as well as in the latest_receipt_info record) that there is the "is_trial_period" field. The value of this field is either "true" or "false". This field can be present in all in-app purchase types, not just for auto-renewing subscription records and has no current meaning for non auto-renewing subscription records.
Lets assume that the user does not cancel the free trial. On the last day of the free trial, the iTunes store will bill the user for the first month of the auto-renewing subscription (I may be off as to the exact time that the user account is billed). The iTunes store will set up an incompleteTransaction on the store for the user and applicationID. At some point, the user will launch the application and assuming that the application has called addTransactionObserver in the startup process, and there is an active network connection, the application immediately detects the incomplete transaction. The updatedTransactions delegate method is called with the SKPaymentTransactionStatePurchased. The applicationReceipt is validated. Now when the app inspects the applicationReceipt, there will be a second item in the in_app array. The app cannot assume that the in_app array contents are in any order. The current purchase might be the first element in the in_app array. The app finds the iAP item where the expire_date is later than the current date and knows that the subscription is still active. This is how the app will detect that the auto-renewing subscription is current. When the validation process is complete, make sure to call finishTransaction on the transactionIdentifier so that the store will mark the incompleteTransaction as “completed”
So what happens when a user cancels a subscription. There are 2 cases to study here. There is the case that the user decides to let the current subscription expire. this happens when the user enters their iTunes account, to the “Manage Subscriptions” section and chooses to allow the current subscription to expire. In this case, on the next auto-renew date, iTunes will not post an incompleteTransaction to the user account. When the application validates the applicationReceipt, there will no longer be an in_app array item with the expire_date greater than the current date. If the application knows that the subscription was active, it can alert the user that the receipt does not show that the subscription is active and can ask the user whether to refresh the receipt using the SKReceiptRefreshRequest. If the user agrees, then the app makes the call, validates the updated receipt and scans for a current subscription item in the in_app array. So long as there is no in_app array item where the expire_date is greater than the current date, the subscription is expired.
The other cancellation case is where the user contacts Apple Care and requests a refund for the in_app purchase. When this happens, the date of the refund is noted. When the applicationReceipt is validated following this event, the cancellation_date field for the corresponding in_app array item will be set. Note that as per TN 2413, iAP FAQ <https://developer.apple.com/library/ios/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-RECEIPT-HOW_DO_I_USE_THE_CANCELLATION_DATE_FIELD_>
the cancellation_date field is only added for non-consumable and auto-renewing subscription purchase types. Note that the cancellation_date applies to the specific in_app array item, and not to all of the in_app array items. It’s important to note - the cancellation_date field is set, only for the auto-renewing subscription in app purchase item for the period in which the cancellation occurred. For example, if the applicationReceipt contents are current, and the user contacts Apple Care and receives a refund, then the cancellation_date will be set for the most current auto-renewing subscription item in the in_app array. However, if the applicationReceipt has not been updated since the original purchase and the in_app array does not contain the most current auto-renewing subscription item, then the cancellation_date field will not be set in the original auto-renewing subscription purchase item.
However, in this latter case where there is only the original applicationReceipt, see the following.
Note that the applicationReceipt is only updated when the receipt is refreshed, when the updatedTransactions delegate method is called for a successful or restored transaction or when the application is installed from the iTunes store (in the case that another copy of the app is to a second device).
The above description describes the process envisioned by StoreKit engineering for how an application works with the iTunes Store server to detect auto-renewing subscription renewal.
It turns out that there is a second method - which is not so clearly documented; it is hinted at via comments in the Receipt Validation Programming Guide. The alternate means for receipt validation allows for a server based validation process independent of the app. The app must be used to make the initial purchase of the auto-renewing subscription item so that a copy of the applicationReceipt can be passed to the server. The server passes the base64 encoded applicationReceipt to the iTunes Store verifyReceipt server. On a successful validation, the server looks for the latest_receipt and latest_receipt_info fields of the JSON results. In addition, there is the new field - "pending_renewal_info" to assist developers in understanding the status of auto-renewing subscription item pending renewal. Within this field there is
"expiration_intent" - documents the reason why a subscription has expired
"is_in_billing_retry_period" - documents whether or not Apple is still attempting to automatically renew the subscription
"auto_renew_status" - This key is only present for auto-renewable subscription receipts, for active or expired subscriptions. The value for this key should not be interpreted as the customer’s subscription status. You can use this value to display an alternative subscription product in your app, for example, a lower level subscription plan that the customer can downgrade to from their current plan.
As documented in the Receipt Validation PG
<https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Introduction.html>
latest_receipt - Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions. The base-64 encoded transaction receipt for the most recent renewal.
latest_receipt_info - Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions. The JSON representation of the receipt for the most recent renewal.
The value of the latest_receipt_info - once this information is captured for an auto-renewing subscription in-app purchase item, the latest_receipt_info can be used independently to view the renewal status of the auto-renewing subscription item (and items in the group). I refer you to the statement in the “Receipt Validation Programming Guide”
“The values of the latest_receipt and latest_receipt_info keys are useful when checking whether an auto-renewable subscription is currently active. By providing any transaction receipt for the subscription and checking these values, you can get information about the currently-active subscription period. If the receipt being validated is for the latest renewal, the value for latest_receipt is the same as receipt-data (in the request) and the value for latest_receipt_info is the same as receipt.”
The above statement is from the iTunes Store Server engineering team and explains that once the latest_receipt field has been captured, that field is base64 encoded and can be resent to the appropriate verifyReceipt server. The JSON response can be parsed and will provide the latest information about the auto-renewing subscription item. The result can contain multiple items, and for this reason. As such the status result 21006 does not apply. This is because there can be multiple items in the response and the expired status may not apply to all items. When the status of 0 is returned, the receipt was successfully validated and the app (or server process) must process the returned results individually.
Once you have the latest_receipt_field from the initial purchase, you can validate the latest_info_results contents and watch as the auto-renewing subscription item renewed from that point on. If a user switches to another auto-renewing subscription item in the group, then you validate the same original receipt, the change to the different auto-renewing subscription group item is recorded.
If all you keep is the initial latest_receipt contents from the first purchase, and continue to use this saved latest_receipt to validate the auto-renewing subscription you will find on validation that there will only be the single auto-renewing subscription item in the in_app array. However as the auto-renewing subscription renews, you can inspect the contents of the latest_receipt_info field to find that the renewals will be listed.
One additional note, a user has the option to terminate an auto-renewing subscription item in their iTunes account. The server process can only detect an expired auto-renewing subscription when there is no active auto-renewing subscription item in the JSON results (in_app array or in the latest_receipt_info). In addition, the user can restart an expired subscription in their iTunes account. By validating the latest_receipt, a server process can detect that the expired auto-renewing subscription item has been restarted.
This mechanism makes it possible for a server process to track the auto-renewing subscription renewal process rather than do so on the app. It also turns out that the cancellation of the auto-renewing subscription item can be detected in the same manner. The Receipt Validation program reference describes that a cancellation_date field is set for an auto-renewing subscription in_app array item for the period which is cancelled. The same is true in the contents of the latest_receipt_info. Lets be clear as to the requirements for detecting the cancellation_date. The applicationReceipt must contain in the in_app array, the in-app purchase identifier for an auto-renewing subscription item. When the receipt is validated and the “shared-secret” is included in the validation request sent to the iTunes verifyReceipt server, the server will return in the latest_receipt_info, the latest information for that auto-renewing subscription item to include the cancellation_date for the period in which the cancellation was requested. You will also note that the contents of the in_app array element for the auto-renewing subscription doesn't reflect the cancellation_date since you are working with a non-updated appStoreReceipt. Were the app to have the user refresh the appStoreReceipt following the cancellation event, then the resulting appStoreReceipt would show the cancellation date for the auto-renewing subscription item in the in_app array.
Note also that the cancellation_date may be earlier than the expiration_date for the transaction record in which the cancellation_date field is set.
Also note that the cancellation_date only applies to the period in which the cancellation request was approved. The user can decide later to start the auto-renewing subscription item again.
rich kubota - rkubota@apple.com
developer technical support CoreOS/Hardware/MFI