I am working with auto-renewable subscriptions. When the app has successfully finished a transaction, it sends the receipt to our server to validate it and to check that the last transaction identifier exists in the receipt.
From time to time, our server returns an error saying that the transaction identifier is not found in the receipt. I have confirmed that locally, in the app. Even after refreshing the receipt, sometimes the transaction identifier can't be found.
Also, the purchase has succeeded because trying to buy again the same subscription prompts an iOS pop up saying that the subscription is currently active.
Obviously this strange behaviour is happening in the sandbox environment. Also note, that I am using up to 3 test users switching back and forth between them.
Has anyone experienced a similar problem?
Any ideas about what is going on?
For the last 2 days I've been testing our code with one single iTunes test user, and I haven't come across that problem anymore. Not even once.
I am almost sure the problem is caused by switching between iTunes test users in the sandbox environment. Somehow, in that scenario, the app receipt is not always consistent.
No way to prove it though :(
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.
As title said, I am implementing a flow to fetch a local receipt then try to validate it from our backend server. As what Apple suggested, if the receipt tis nil or invalid. I need to do a receipt refresh request, the problem is, this requires network as well as user login.
So the actual issue we are facing is, from the dev build and test flight build, when we build a fresh new app that has not opened before. Once open, it will show a popup to ask for user login (obviously it's because of my logic - if receipt nil then do receipt refresh request). But we don't want to spam user with this dialog box every time and we have the business need to validate the user's receipt at app start up.
So back to the title question, since the dev and test flight build isn't working as expected (show login dialog because receipt is nil). Does the prod build - the app download from App Store, actually issues a receipt from App Store? In a WWDC video they said it should, but not very specific, so I am here to get some confirmation or some other thoughts from you guys.
Thanks!!
It's been a long time since I've used receipt validation (I used for auto renewable subscription) so what I'm going to tell you could have been changed.
In my experience it never happen to receive a nil receipt in production since the first receipt is downloaded from the App Store along with the application even for free apps. nil receipt happens in sandbox and in adhoc (don't know which should be the right behavior from from TestFlight) and in this case the best way to replicate production is to "restore purchases" or make a refresh request.
There is an old discussion about it on Apple dev forum where an employee clarifies that(probably 2015), I'm not able to find it again, but maybe you can also make a search there.
We've been happily processing in-app purchases in our iOS app since the beginning of time. Because the code has been around a while, it uses the now-deprecated transactionReceipt property on the transaction we get when our paymentQueue:updatedTransactions: method is called by Store Kit. We send that receipt to our server, which validates it through a post to Apple's server, does what we need to do on the server, and reports back "success" to our app. Works great.
Now we want to add a subscription-based product so I'm looking at having to re-implement IAP using the appStoreReceiptURL property on the main bundle and loading my app receipt from there. There are a couple things I don't get.
First and most obvious: I get the same SKPaymentTransactionStatePurchased state when paymentQueue:updatedTransactions: is called. For now, when I submit it to Apple I'm just displaying the JSON I get back. Since the status is non-zero I'm also submitting to the sandbox server and displaying what I get back from it, too. Problem is in both cases I get this:
{"status":21002, "exception":"com.apple.jingle.mzfairplay.validators.DrmInvalidArgumentException"}
I suspect this has something to do with the fact that I'm running a debug build, though not in the sim -- it's running on an actual device. To get around that, I tried uploading a build to the App Store so I could download through TestFlight, but since I'm using my sandbox Apple ID, TestFlight refuses to install it.
So the first question is why am I getting this "DrmInvalidArgumentException", and am I configured correctly to test (debug build on real device, using my sandbox Apple ID to make purchases).
Second question is more baffling to me. As I understand it, I will still get notified via paymentQueue:updatedTransactions, and I will iterate over the transactions I get there (?) but then instead of submitting the receipt in the transaction, I'll submit the app receipt from the URL in the main bundle. It will contain ALL IAP PURCHASES EVER and I will have to iterate over all those to figure out what's new and what I'm interested in, right?
The flow doesn't seem right. I'm getting a notification based on a transaction, but then looking at a dump of ALL my IAP transactions that are contained in the app receipt. So I can't possibly be understanding the flow correctly.
Status 21002 is The data in the receipt-data property was malformed or missing.. The body of the POST to the verify endpoint has to be a JSON dict containing a receipt-data key and a password key that contains your app-specific shared secret that you can get from iTunes Connect.
You might try using this tool to test your receipts. You don't need to use TestFlight, everything should work fine on a debug build as long as you are using a Sandbox iTunes account, which you can create in iTunes Connect.
Your explanation is correct, you will want to send the whole receipt for each purchased transaction. This is redundant, you could potentially keep a app side cache so you don't send the same receipt data each time but it is possible for the contents of appStoreReceiptURL to change at any time.
There are many other tricky edge cases implementing subscriptions. I built RevenueCat because of all these crappy things with subscriptions. Apple really bolted it onto the existing IAP stuff poorly in my opinion.
I have implemented in app purchase in my iPhone application.
But first time while buying product during payment transaction phase I get an error:
cannot connect to iTunes store and it results in unsuccessful payment transaction.
Main thing is that it is occurring randomly means in subsequent attempts transaction gets completed successfully and again after 2 or 3 transactions it gives me the same error.
Weird behaviour?
I have tried all online available solutions like resetting, "Log out" from iTune account and creating new test user account but none is working.
I am trying to solve this problem since last three days(was waiting if problem is from iTune server) but no luck at all.
Before this I have successfully submitted no. of apps having in app purchase. Facing this issue first time.
Please do suggest some solution.
Thanks
In my case the error was because of "TimeOutInterval:".
In my In app purchase implementation workflow, in between there was a call to our local server to make entry of subscription in database.
Later I found, because of slow server I was getting late response from server resulting into "requestTimeout".
To solve this problem I increased the timeoutInterval while making request to server. And It worked for me.
due to the fact that Apple requires at least one auto-renewable subscription or one free subscription for a newsstand app, we changed all monthly subscriptions of our news app to auto-renewable ones while adding newsstand functionality.
When I try to buy one of these subscriptions on device #1 (e.g. my iPad) everything works fine. When I try to buy the same subscription on device #2 (e.g. my iPhone), I get a message from the Store Kit telling me that I am already a subscriber of this certain issue (which is ok as well) but afterwards the process is finished without having bought anything.
Checking the log shows, that the SKPaymentTransactionObserver delegate method paymentQueue:updatedTransactions: first gets a transaction for that product in the state 'SKPaymentTransactionStatePurchasing' (ok so far) followed by transaction in the state 'SKPaymentTransactionStateFailed'. Having a closer look on the transaction error code and message of the last transaction, the transaction error description says "Cannot connect to iTunes Store'. A second purchase of another product (bought the first time on the second device) works perfectly. Both purchases are done against the sandbox, does anybody has a hint how to address this problem.
Thanks for your help in advance
Michael
It shouldn't be possible to to purchase the same auto-renewing subscription on multiple devices, instead you should implement restore functionality, and restore on the second device. It's worth noting that the sandbox uses time compression for testing, so a 1 month subscription, will actually auto-renew every 5 minutes or so, and will only renew 5 times in a day. it's possible the original subscription has expired by the time you try to repurchase for the second time.
Often the sandbox error messages are not useful, the "Couldn't connect to iTunes" is not relevant.
I would suggest reading the whole of this page very carefully, as there are a lot of important details it's easy to miss. The bit about restoring purchases is here:
https://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/MakingaPurchase/MakingaPurchase.html#//apple_ref/doc/uid/TP40008267-CH3-SW1
Best of luck