13

I'm implementing in-app purchases for my app (to be released), and providing support for iOS6 and iOS7. My question has to do with differences between non-renewing subscription mechanisms across iOS6 and iOS7, and more specifically about how a restore is implemented. To accommodate both iOS6 and iOS7, I have implemented a server-side solution to enabling a restore. I optionally allow the user to create a username/password that can be used on a different device (or the same device if data is lost) to do a restore. I have most of this basically working but as I've been progressing with my testing, I've found something curious.

The initial part of my restore process for iOS7 uses SKReceiptRefreshRequest to refresh the receipt in the app. When I delete the app from an iOS7 device, re-install (there is no receipt at this point; tested using iExplorer), and do a restore, SKReceiptRefreshRequest restores 10 purchases (that I've created during testing, for this particular user). One of these is a non-consumable, and nine of the receipts are non-renewing. This confuses me. From the Apple docs, I expected only to see non-consumable purchases in the refreshed receipt. From https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html:

“Consumable Products and Non-Renewing Subscriptions: The in-app purchare receipt for a consumable product or a non-renewing subscription is added to the receipt when the purchase is made. It is kept in the receipt until your app finishes that transaction. After that point, it is removed from the receipt the next time the receipt is updated—for example, when the user makes another purchase or if your app explicitly refreshes the receipt.”

As relating to non-renewing subscriptions, from https://developer.apple.com/in-app-purchase/In-App-Purchase-Guidelines.pdf:

Use iCloud or your own server to track purchases and allow user to restore purchased subscriptions to all iOS devices owned by a single user

And the following table from (https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Products.html)

enter image description here

Anyone have any insights?

Question: Why does SKReceiptRefreshRequest leave purchases for non-renewing products in the receipt? Is this a bug in the Apple docs or is there something else going on?

2/23/14; Update: I have recently put a bug report into Apple on this. No word back yet. Though, in reality, I don't want this "bug" to go away!

10/20/15; Update: It seems that Apple has actually resolved this "bug". Now, when I do a restore using SKReceiptRefreshRequest (which seems to be Apple's recommended method of doing a restore, see https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Restoring.html), I now am not getting the non-renewing subscription purchases showing up in the receipt. I am only getting non-consumables (my app has only non-renewing subscriptions and non-consumables purchases). I'm going to submit a bug report to Apple immediately after I write this as at a minimum, their docs are ambiguous on the expected behavior.

So far my testing of this has included my app running on iOS 8.4 and iOS9 (9.1 beta actually as I don't have the right device running the production release), and so it appears this is a server side change with Apple and not strictly speaking an iOS/device side change. Also note that all of my testing so far is on a development build of my app, and so is in the in-app purchase sandbox. I'll be running a production test shortly.

12/3/15; Update; At the prompting of Apple Tech Support, I ran some more tests. This time, on an iPad running iOS9.2 beta 4 (13C75). It seems we're back to "normal" now with non-renewing subscriptions. That is, when I do a restore, I'm seeing non-renewing subscriptions in the receipt again.

Chris Prince
  • 7,288
  • 2
  • 48
  • 66
  • Did you ever hear anything back on this? I too am looking at this, and like you can see all the non-renewing receipts within the in-app purchase section of the receipt. However, I would also mention the apple docs here https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1 where if you look at In-App Purchase Receipt you will see it states "the value of this key is an array containing all in-app purchase receipts". – NeilMortonNet Jun 06 '14 at 19:33
  • Nope. Haven't heard anything back. Last time I submitted a bug report, I heard back on the same day as the iOS7 release. So, perhaps I'll hear back on the iOS8 release date? – Chris Prince Jun 06 '14 at 21:09
  • Interesting. So I have actually written (okay it needs tidying up) the code to read the receipt entires within In App Purchases, work out the expiry, taking into account all renewals, and any lapses in service, or overlaps etc.. Further, using SKReceiptRefreshRequest I can grab a new copy of the receipt to another device. So it is entirely possible to manage Non-Renewing subscriptions on all user devices without any backend or syncing. It seems there is contradictory information from Apple themselves? – NeilMortonNet Jun 06 '14 at 21:33
  • Also, I have done separate code to write expiry to KeyChain, along for that matter with latest receipt, which would work without issue, however, using the receipt data allows one to validate the receipt periodically, checking for any cancellation and even restoring transactions without having to secure the data in iCloud KeyChain or a backend server. In my mind, the functionality exists. But I am concerned that I shouldn't use it as they could pull the carpet on it. However, it would seem they should document it and keep it, as it would be more secure and more sensible? Welcome your thoughts. – NeilMortonNet Jun 06 '14 at 21:36
  • Personally, I wouldn't rely on this undocumented behavior. Particularly since the behavior is Apple-server side and they could change it at their whim. – Chris Prince Jun 07 '14 at 15:10
  • I agree! I won't use it. Would be nice to get it bottomed out though! And thanks for your comments. – NeilMortonNet Jun 07 '14 at 16:41
  • Can you link to the bug report? I'm having the same issue. – GoldenJoe Aug 25 '14 at 05:45
  • I was just testing non-renewing subs, and stumbled upon the same "problem". Good to see I'm not crazy. Any update on the situation? If you delete the app, install it again, and restore purchases, do you get all of the old receipts/purchases? – nikolovski Oct 20 '14 at 14:37
  • I've not heard back from Apple. On the deletion question, I think so, but why don't you try it and report your results? – Chris Prince Oct 22 '14 at 16:12
  • I've just finished implementing my in-app non-renewing subscriptions. I've tested purchase restorations (both "new device" and "app delete-reinstall" cases). Everything works fine. The only thing I don't understand is that a `Purchase Date` field for any of my purchases is always equals to the most recent `Original Purchase Date` among all my purchases (no matter what product id). Literally I have 18 different purchases with same one `Purchase Date` and different 18 `Original Purchase Date`s. Strange one... – kas-kad Mar 10 '15 at 18:49
  • I see that someone else has submitted a bug report on this. Good to see! http://openradar.appspot.com/23180869 – Chris Prince Oct 22 '15 at 15:11

3 Answers3

4

Be careful, persisting non-renewing subscriptions in App Receipt gives you no guarantee to be accepted by App Store reviewers. The success depends on particular reviewer. I've got this message from Apple recently:

We found that your app includes a feature to restore previously purchased In-App Purchase products by entering the user's Apple ID and password. However, Non-Renewing Subscription In-App Purchases cannot be restored in this manner.

It would be appropriate to revise your binary to remove this feature. If you would like users to be able to restore Non-Renewing Subscription In-App Purchase products, you will need to implement your own restore mechanism.

So the last attempt to submit the app was unlucky. Unlike the previous few.

So now, my plan is to store the App Receipt on iCloud Key-Value Storage and automatically restore all the purchases. It would satisfy Apples requests:

For non-renewing subscriptions, use iCloud or your own server to keep a persistent record. (c) Store Kit Programming Guide

here is the Apple's code provided for these purposes:

#if USE_ICLOUD_STORAGE
NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
#else
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
#endif

NSData *newReceipt = transaction.transactionReceipt;
NSArray *savedReceipts = [storage arrayForKey:@"receipts"];
if (!receipts) {
    // Storing the first receipt
    [storage setObject:@[newReceipt] forKey:@"receipts"];
} else {
    // Adding another receipt
    NSArray *updatedReceipts = [savedReceipts arrayByAddingObject:newReceipt];
    [storage setObject:updatedReceipts forKey:@"receipts"];
}

[storage synchronize];
kas-kad
  • 3,736
  • 1
  • 26
  • 45
  • transactionReceipt has been deprecated in iOS 7. – user965972 Jul 20 '15 at 13:52
  • @user965972 the code was provided by Apple through the StoreKit documentation. In my app, I use bundle receipt, which can be found at `[[NSBundle mainBundle] appStoreReceiptURL];` – kas-kad Aug 13 '15 at 20:02
  • So? Some of Apple's documentation is way out of date. You have to look at the API's to know if it has been deprecated or not. The app store receipt is not the transaction receipt. The bundle receipt contains all the transactions. The transaction receipt refers to a particular transaction, which has been deprecated. – user965972 Aug 14 '15 at 10:18
2

This has also been puzzling the heck outta me. After looking at the docs many times, I finally saw the following in the receipt field notes for the Receipt Validation Programming Guide:

The in-app purchase receipt for a consumable product is added to the receipt when the purchase is made. It is kept in the receipt until your app finishes that transaction. After that point, it is removed from the receipt the next time the receipt is updated—for example, when the user makes another purchase or if your app explicitly refreshes the receipt.

The in-app purchase receipt for a non-consumable product, auto-renewable subscription, non-renewing subscription, or free subscription remains in the receipt indefinitely.

Whereas the section on "Persisting Using the App Receipt" in the In-App Purchase Programming Guide still indicates that consumables and non-renewing subscriptions are treated the same.

I'm guessing from the behavior that we are observing that the IAP document is out of date? I hope so.

jstevenco
  • 2,913
  • 2
  • 25
  • 36
  • 2
    Yes, I believe this is the case. I finally got a reply from Apple about the receipt field note doc change. At the time they emailed me, the change wasn't yet in the production docs. Looks like they forgot to update the IAP document. Bad Apple! ;). – Chris Prince Feb 11 '15 at 17:35
  • Apple has fixed the IAP document. In Table 1-2 of `Comparison of subscription types`, in the [IAP Guide](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Products.html), non-renewing subscription used to have `once` in `Appears in the receipt`. The `once` is now changed to `always`. – Daniel May 14 '16 at 15:26
1

Apple recommends the following (Read the Overview here)

... Receipts for auto-renewable subscriptions can grow over time since the renewal transactions stay in the receipt forever. To optimize performance, the App Store may truncate sandbox receipts to remove old transactions. When validating receipts for transactions made in the sandbox environment, consider creating new test accounts instead of reusing old accounts to test subscription purchases.

Peter Kreinz
  • 7,979
  • 1
  • 64
  • 49