I am in the process of updating my app to new iOS7/8 in-app purchase libraries. I see now that as of iOS7, we have access to appStoreReceiptURL as part of NSBundle.
It appears I can access this URL, and its concomitant data, at any time, without interacting or interfacing with the SKPaymentQueue.
Previously, when a customer installed our app and wanted to restore his in-app subscription, the app would call the restoreCompletedTransactions method of [SKPaymentQueue defaultQueue], and from that obtain receipt information, one transaction at a time, that the app would upload to our server one transaction at a time.
However, while testing in the iOS App Store Sandbox, now I appear to be able to obtain one piece of master receipt data from [[NSBundle mainBundle] appStoreReceiptURL], upload that to my server, obtain a complete history of every in-app transaction this user has made, and then record transactions on my server as appropriate and send notification back to my client.
Consequently, why or when would anyone need to call restoreCompletedTransactions? In my app, I sell a single, auto-renewing in-app subscription; are there other use cases today in iOS7/8 for which this API is still helpful?
If a user has made a purchase on a different device, it can be useful (or necessary) to "restore" that purchase on a new device. restoreCompletedTransactions could enable this restore operation. By default, on this new device, I believe the >= iOS7 style receipt will not have those prior purchases. On >= iOS7, SKReceiptRefreshRequest will work in many cases.
From Apple:
In most cases, all your app needs to do is refresh its receipt and
deliver the products in its receipt. The refreshed receipt contains a
record of the user’s purchases in this app, on this device or any
other device. However, some apps need to take an alternate approach
for one of the following reasons:
If you use Apple-hosted content, restoring completed transactions
gives your app the transaction objects it uses to download the
content.
If you need to support versions of iOS earlier than iOS 7,
where the app receipt isn’t available, restore completed transactions
instead.
If your app uses non-renewing subscriptions, your app is
responsible for the restoration process.
See https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Restoring.html#//apple_ref/doc/uid/TP40008267-CH8-SW9
Related
I have a side project and I recently worked on my receipt manager to make it stronger and to relies more on the receipt of the app rather than persistently storing a value after a transaction.
However, there are 2 main points which although I read Apple docs and other answers on the web, I'm still confused about:
1. When a user restore their purchase, does the receipt get refreshed?
I made several tests in sandbox, and I have seen that when restoring, the receipt gets refreshed, and when I verify the receipt through the iTunes server verification, it returns a JSON including the latest transactions. This is very helpful because even if I close/open the app, the app receipt is updated and I can always verify it without refreshing it.
However, in production, this didn't work. The app receipt wasn't refreshed after restoring purchases and my users got asked to restore purchases continuously. Can anyone answer on this point?
2. Does the refresh receipt request triggers an alert asking for the Apple ID's password in production?
From the previous point, I thought ok, I will force receipt refresh after a user restores their purchases. However, in development / sandbox, I get asked to insert my sandbox user's pass every time I try to refresh the receipt (although I could restore purchases without a password request before asking for the refresh). I read a lot about this and someone says that might not happen in production. Does any of you have a clarification on this?
Note:
I know that when restoring / purchasing I get back a transaction with the receipt, however, I need to use the App Receipt to verify transactions (and this is also what Apple says to do).
Thank you in advance.
1. Refreshing the receipt
In theory, calling restore purchases should get the latest receipt. In the instances where you are experiencing issues, take a look at SKReceiptRefreshRequest. Typically, I use this in production when a call to restore purchases has encountered errors.
Use it wisely, triggering that API can lead to showing the Sign In prompts for the App Store.
2. When is the user asked to sign in?
Sadly, I have seen this vary so I cannot give a definitive answer. More often than not, a call to restore purchases should not trigger a sign in. Explicitly using SKReceiptRefreshRequest will.
If the user is not signed in to the store, calling any Store API like attempting a purchase or restoring purchases could trigger the sign in flow.
What Apple Says
From the docs
Refreshing a receipt doesn't create new transactions; it requests the latest copy of the receipt from the App Store. Refresh the receipt only once; refreshing multiple times in a row has the same result.
Restoring completed transactions creates a new transaction for every transaction previously completed, essentially replaying history for your transaction queue observer. Your app maintains its own state to keep track of why it’s restoring completed transactions and how to handle them. Restoring multiple times creates multiple restored transactions for each completed transaction.
My Recommendation
Store the hash of the last receipt you used on device. You can use this hash to check against the latest receipt so you know if anything has changed. Whenever your App resumes you can always check if the current receipt hash is different from the last cached value.
Try and submit the receipt as soon as possible. Typically when the App has launched.
If a user tries to manually restore purchases, I would start with a call to restoreCompletedTransactions. This can trigger an App Store sign in but is often less likely. Most of the time this is enough as the receipt on the device is often pretty up to date.
If the user tries another restore purchases, OR if the call failed, then move to SKReceiptRefreshRequest to guarantee a fresh receipt.
When using SKReceiptRefreshRequest, I would recommend wrapping this behind UIAlertController. I normally show something that indicates it has failed and have a "Retry" button that uses the request. This will trigger a new store sign in.
Use restoreCompletedTransactions to get a playback of all completed transactions the device is aware of.
When a user restore their purchase, does the receipt get refreshed?
Yes, it should. But it also sounds like you're doing some server-side validation? If that's the case, you can send any receipt from the user to the /verifyReceipt endpoint to get the latest status. You don't need to send the latest receipt, since /verifyReceipt will also refresh it.
Does the refresh receipt request triggers an alert asking for the Apple ID's password in production?
There's no clear Apple documentation on this, but it definitely will if there's no receipt file present in the app (rare in production). But if you're doing server-side validation (see #1), then you can send up any receipt you have, no need to refresh it. So you're only refreshing the receipt if nothing is present, which will trigger the sign-in. Keep in mind a receipt file is not present on the device after installing in sandbox - only after a purchase. This differs a lot from production where a receipt file is generated after installation.
From what it sounds like you're trying to do, my recommendation would be to check if any receipt file is present at launch, send it to /verifyReceipt to get the latest status for the user and cache the result. You can do this on every app launch.
In a perfect world you're storing the receipt server-side and keeping it up-to-date there, but you mentioned side project so that sounds like overkill. However, an out-of-the box solution that correctly implements all of this and will scale with you - such as RevenueCat - is another alternative (Disclaimer: I work there).
After many tests and after I sent my app in production, I'm now able to answer my questions properly:
1. When a user restores their purchase, does the receipt get refreshed?
YES, this is immediate as for Sandbox, BUT the problem is that the receipt DOESN'T include non-consumable purchases.
This means in other words that the receipt will include the purchased subscriptions, but you won't find purchases of non-consumable products.
However, when the user restores or purchases, you get the transactions in return, and you can extract the non-consumable products, and store this info somewhere like UserDefaults or Keychain, so you can use them when the user opens your app.
For the rest, the best approach is to always validate and check the receipt when the app is opened.
2. Does the refresh receipt request trigger an alert asking for the Apple ID's password in production?
YES. For sure it does the first time.
Thank you to Daniel and enc for their answers that can still be useful.
I am developing a iOS app that uses In App Purchase, and I am not sure I have a good understanding of SKReceiptRefreshRequest. Is it for use only in the testing environment, or can I use it in the final app version on the store? Many features in IAP rely on having an updated receipt, but one problem is that, in the testing environment, firing a SKReceiptRefreshRequest causes the app to present a request for Apple ID credentials. If SKReceiptRefreshRequest is for use on the final app version too, will it trigger a credentials request also on the user devices?
You shouldn't be calling SKReceiptRefreshRequest to keep the receipt up to date. As you've seen, any time you access the receipt it could trigger a credentials request. This is because receipts are linked to the iTunes Account on the device.
If you need information from the receipt frequently, what you should be doing is storing it on a server and keeping it up-to-date there. You can use Apple's /verifyReceipt endpoint to update the receipt on your server so there's no need to get the user credentials from the device.
We currently developing an iOS app which uses auto-renewbale subscriptions purchased via IAP. We would like to be able to get either a receipt OR a list of previous transactions from the user w/o requiring that they enter their iTunes password.
The documentation says the following:
Users restore transactions to maintain access to content they’ve already purchased. For example, when they upgrade to a new phone, they
don’t lose all of the items they purchased on the old phone. Include
some mechanism in your app to let the user restore their purchases,
such as a Restore Purchases button. Restoring purchases prompts for
the user’s App Store credentials, which interrupts the flow of your
app: because of this, don’t automatically restore purchases,
especially not every time your app is launched. In most cases, all
your app needs to do is refresh its receipt and deliver the products
in its receipt. The refreshed receipt contains a record of the user’s
purchases in this app, on this device or any other device.
We have interpreted this to mean that the restoreCompletedTransactions API will require iTunes credentials be entered and the SKReceptRefreshRequest will not.
Unfortunately, in the sandbox, the exact opposite seems to be the case. My Sandbox users are NOT required to enter credentials when they are restoring transactions, but they are required when they are requesting a fresh receipt.
Does anyone have any idea if its the documentation that is incorrect, or if the IAP sandbox has different behaviors than the production environment?
Both refresh receipt mechanism and restore purchases require user authentication, in production and sandbox environment.
As from this, we assume the behaviour is that if the receipt is already present on the device (stale receipt), refresh doesn't ask for credentials, but if the receipt is absent it does. (1)
I have verified the above behaviour myself in the sandbox environment.
Also, as per this Apple doc, receipt is always present in production. (2)
Both (1) and (2), combined should mean refreshReceipt should not ask for iTunes login in production.
But as per this, (1) may be applicable only for Sandbox and in production it might always require login.
Also, the behaviour may change from time to time and between OS versions and devices. You should basically not depend on the behaviour for critical features of your application.
I had been going through the documentation many times and still not sure how to go about this. From In App Purchase Programming guide - Restore Purchased Product:
In most cases, all your app needs to do is refresh its receipt and
deliver the products in its receipt. The refreshed receipt contains a
record of the user’s purchases in this app, on this device or any
other device. However, some apps need to take an alternate approach
for one of the following reasons:
If you use Apple-hosted content, restoring completed transactions
gives your app the transaction objects it uses to download the
content.
Does that means if I am using Apple-hosted content, I don't need to refresh its receipt and call restore completed transaction straight to retrieve my previous download product?
You still need to do it.
What the text you quoted means is that the receipt contains the information about what in-app products the user purchased, but the receipt does not contain the content.
So if you use Apple-hosted content, you need a mechanism to download said content. That content is not provided when you refresh the receipt. It is downloaded however when you use the various restore functions.
I've been implementing auto-renewing In App Purchases and, using the info found here, have had little trouble the purchasing, renewing, & restoring transaction flows.
The problem I am having is finding a way to test when a user renews (after, presumably, canceling some time in the past) from their account page in either iTunes or the App Store app (Detailed here). I'm assuming, since those exist outside of the sandbox and when you log into one of them using a sandbox account that account is invalidated as a test account, there is no way to actually test this use case so I'm just looking for more information on the expected behavior to try and account for it.
I know that keeping a copy of the receipt around to validate will give the latest receipt as part of the JSON payload, I'm more curious for information on how StoreKit will handle this renewal. Will paymentQueue: updatedTransaction: fire with a new SKPaymentTransaction as soon as I add a TransactionObserver or will it stay silent until calling restoreCompletedTransactions and then the new SKPaymentTransaction will be part of that?
Yes, you are correct; paymentQueue:updatedTransactions: will fire, just as if you were making the initial subscription purchase.
You can actually test this since renewals within the sandbox kick in at a much faster pace (up to six times a day) as explained here (scroll down to the bottom under "The Test Environment").
Also when you verify the transaction with Apple's verification service right after you made a test subscription purchase, look for the expires_date field in the response, it will tell you when the next renewal will be triggered.