Restoring In-App Purchase Transactions - iphone

The idea is whether rain or shine, wet or fine, user must get that he paid for all out.
From Apple:
Store Kit provides built-in functionality to restore transactions for non-consumable products, auto-renewable subscriptions and free subscriptions
For these transactions Apple Store Kit has good build-in tools. I want to focus on other types (consumable in particular).
One and only transaction information is an identifier and a receipt data which we receiving by Store Kit after successful purchase.
Our application uses server-side model to deliver products to it. But there still much cases of losing purchase data, such as if the server lay down while user is making purchase via App Store so its not possible to send receipt to server to complete verification process.
Current workaround is:
Server returns a list of product identifiers
User selects one; app saves its identifier on device (via SQLite or Core Data). Standart Apple Store transaction process goes right after that.
In case of success application saves receipt data in conjunction with its identifier on device and send it to server. If there were failure or cancelation the identifier is immediatelly removed from device.
If server's response is OK then app removes identifier with receipt data from device. Otherwise it will send requests to server periodically until successful response behaves.
But this approach still has leaks. For example, user can remove application from device not waiting for transaction delivering to server, so there will not any proof about his purchase at all.
Your suggestions?

The fundamental rule is that you not call finishTransaction: on the payment queue until you have successfully delivered content. That means that you make the request to your verification and content servers and they come back with valid responses. Only after those proper responses do you call finishTransaction:. Note that bad purchase receipt is valid just not good. You will get people trying to ripoff goods - don't lose sleep over it but do put in proper receipt checking.
As I understand it (from my non-consumable items), as long as you do not call finishTransaction, the store will continue to retry it on your app installation. For that reason, I do not think you need your application to save the receipt on the device. However, for consumables, the server has to store the data if you want to be able to restore it later. A non-trivial problem is what key to store it under.
BTW, your first line is absolutely correct and worth losing sleep over.

Related

What is the recommended logic to use when checking authenticity in Auto Renewal Subscriptions

I have an app where I’m offering Auto Renewal Subscriptions. Here is how it currently works, when the user subscribes, it saves a Bool in UserDefaults as true, after that I can make a receipt request to see if the subscription has expired. My issue or what I don’t fully understand is the logic on how often to make that request, checking for authenticity every time the user launches the app seems like a lot, or do I need to save the expiration date in UserDefaults and just check a couple of days after the expiration date to see if the user renewed? This way we limit the requests we make to retrieve the Apple receipt.
Can someone share the logic used when checking for authenticity in an app using Auto Renewal Subscriptions?
The "right" way to do it is to manage everything server side (with some caching on the device in case the user is offline). Refreshing the receipt on the device is insecure, slow, and could make that annoying "Login with iTunes" prompt keep popping up for the user :)
It also prevents you from ever having complete analytics of your subscriber base since you can only get data points when they are active in the app.
What you would do is store the receipt file in a database after purchase, and refresh it there to keep the subscription status up-to-date. Then your app is asking your database if the user is subscribed (and caching the value in UserDefaults).
Here is a good blog post that outlines what the setup should look like: iOS Subscriptions are Hard

Backend Server for In-App Billing Purchase Verification

The google documentation for the BillingClient queryPurchases method states the following:
"It's recommended for security purposes to go through purchases verification on your backend (if you have one) by calling the following API: https://developers.google.com/android-publisher/api-ref/purchases/products/get"
Here is the link:
https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#queryPurchases(java.lang.String)
I have set up an API, established communications with it from my server and have done some initial testing, but the more I look into it, the more I question the need for it. If your code can be de-compiled, then whatever verification you are doing on your back end could certainly be subverted within your app's code.
My understanding is that google caches these purchases on the local device and refreshes that cache periodically and this cache is where queryPurchases pulls the purchases from.
Exactly what type of attack would I be trying to prevent by doing back end verification on these purchases?
Google play handles the transaction and keeps a record of the purchase, your back end presents purchase receipts and it gets a response from Google , the user cannot inject a fake record on Google's billing systems and this is what your back end relies on, if the user did indeed purchase you in app item and their credit card was charged by Google then that record is reliable and there's no way a user can alter it even if they decompiled your application they wouldn't gain anything apart from being exposed as a malicious actor. A well implemented in app billing system is watertight and extremely difficult if not impossible to game.

Strategies for battling iOS in-app purchase piracy?

My app uses in-app purchase, and I verify the transaction receipts with Apple. This has shown me that many users are trying to pirate the in-app purchase mechanism by submitting fake transaction receipts, which come up with a product ID of com.zeptolab.ctrbonus.superpower1 (from "Cut the Rope"). Of course I don't let them use the in-app purchase items with a fake receipt. What are some strategies for battling iOS piracy and trying to get these people either to pay or to suffer?
The only way to truly prevent this is to control everything through your own server(s). Even the infamous "com.zeptolab.ctrbonus.superpower1" receipt is an actual valid receipt that Apple's own validation endpoint will tell you is OK. Once a transaction is completed, the app should send the transaction data to a server you control and:
Validate the receipt with Apple from your own server.
If Apple says it's OK, parse the product_id field from Apple's response and make sure it's a product ID from your app.
If the first two items pass, return data to tell your app where to download your content (if it's hosted content).
Even this has a flaw, especially if your IAP content is simply on the device but "locked". There are ways to redirect the validation call from your server to make your app think your server said "everything is OK!". This is much more difficult if your IAP content is hosted remotely as they can't as easily spoof the response with the location of the content if they don't know where the content is in the first place.
The problem in all of this for most people is that controlling your own servers and remote content can get costly, not to mention the need to write your own validation logic. The more difficult you make it for these hacks to be successful, the more it can cost you, so you have to weigh just how much you want to make them "suffer" with how much time, effort and money you're willing to spend vs how much you're making and/or losing. Remember, one "pirated" IAP is not necessarily equal to one lost sale, so it can be hard to gauge just how much you might be losing from this.

In app auto-renewable subscriptions

Sorry for the millionth question about iTunes subscriptions, but I still have a few doubts.
Basically I'm implementing auto-renewable subscriptions in my app and I want to make sure I got it right. Here's a list of steps to take that I came up with:
whenever an user buys a subscription, send the receipt to the server to validate it
if the receipt is valid, save it on the database
on application load, ask the server if a receipt for this UDID exists (this is to figure out if the user has a valid subscription)
if so, check if a new item has been added on the store in a date range from the subscription start date to the expire date
if any, notify the user about those items in some way and mark them as freely downloadable
Are these steps correct? And if so, why does the Apple doc say:
In most cases, your iOS client application should not need to change. In fact, your client application is now made simpler, as you can use the same code to recover auto-renewable subscriptions as you do to recover nonconsumable products. This is described in “Restoring Transactions.”Your application receives a separate transaction for each period of time where the subscription was renewed; your application should verify each receipt separately.
To me it looks like this needs some code to handle all the various cases I mentioned, instead. Or I'm totally wrong about it. Am I?
Plus, how do I know about the subscription expiration date? I can't find a way to get this information anywhere. Am I supposed to save this on my own database?
Update:
I've figured out a few things since I posted this question. Feel free to correct me if I'm wrong.
First of all I guess I'm supposed to store the length of the subscription somewhere on my own database, because as stated on Apple's docs, you cannot retrieve it in any way through Apple's web services. In fact, each subscription length has a different product identifier, so you should have a way to convert a product identifier to a subscription length.
Also, Sylvian has posted details about his implementation of auto-renewable subscriptions, so at least I know my thinking wasn't too much flawed.
Now the only problem is this: how do I know that an user has a valid subscription? I could store this information on my server, yeah, but how do I associate an user with a completed transaction? Should I save the device's UDID?
Here is how we implemented In App Purchases and specifically the new auto-renewable products at my company.
The application transmits the transaction receipt to our webservice, we return OK to the application if we handled it correctly and Apple could verify it. In that case we updated the user account (i.e. the database) to say "yes he has paid and his subscription is valid till the receipt expiration date".
After the OK for this webservice, the application reloads the account info through another webservice, and see there is a valid subscription. That was it... Until auto-renewable products appeared.
We now had to implement some CRON jobs which runs every day: every day we make a list of passes which are supposed to expire, and we ask Apple if the original receipt is still valid: the magic thing is that in their answer, there is a field latest-receipt which embeds the latest receipt. If it is not the same as the one we have, we understand that the subscription has been renewed automatically, we store the latest receipt for the next cron check, and we update the user account to extend the expiration date.
Hope it helps.
I think I found a solution. It doesn't require an additional username/password and it seems to be working.
Note: If you think this is inappropriate, please explain why in the comments. Thanks.
Basically, whenever an user buys a subscription, I validate the receipt against my server and store the receipt data in the user defaults. Then, when the app is opened, or whenever I need to check if the subscription is still valid, I retrieve the previously saved receipt data from the user defaults and validate it against the server.
My webservice just returns whether the subscription is still valid or has expired, plus some other related information such as the subscription length. To do this, it just queries the iTunes server as usual, and checks if the status response is nonzero. 21006 means that the subscription has expired.
If your app has some user management i.e. you use username/password to use the app, then you have to maintain a server to record the purchase/validity of the currently logged in user. This is applicable for normal subscription and non-consumable purchase. But... if you use the new auto-renewable subscription, then it's NOT possible to maintain multiple user in that app, because : this kind of purchase can not be done multiple times within the subscription period using the same Apple ID from the same application and I found it really annoying and finding a better solution for this case i.e where I have multiple child account in the app but I want to use the same Apple-Id to purchase a auto-renewable subscription for each account. And I think I have to use the old subscription model. Any new thoughts ?
as far as I have understand it the apple server will contact you (or the customer with his iPhone) and tell "look here I have a valid purchase for you". Inside your App you read this message and unlock the regarding content for use. The next step is to tell the apple server that you have responded to the receipt and the apple server will not show the message again.
So with a renewable subscription you get for each period a new message. Correct me if I'm wrong, please.

How can my server securely authenticate iPhone in-app purchase?

Look at Apple's diagram for the server purchase model.
In step #9, how can the server know that it is really talking with an iPhone that is entitled to the purchase, and that Eve is not performing a replay with a dishonestly obtained receipt?
The receipt may be valid, but that doesn't prove that the sender is the entitled party.
Is there any notion of a device certificate on the iPhone that can be used to sign the receipt?
Is there any way to bind the receipt to the device, or bind the receipt to both the iTunes account and to the device, so the server can validate?
Apple-Provided Vulnerable Approach
The server can authenticate a purchase by doing the following:
The iPhone application receives a transactionReceipt after the purchase. Have the iPhone base64 encode it (You can use this open-source addition to NSData) and send it to your server. (You could even send it as-is and have the server base64 encode it before validation.)
Have your server send a JSON request with the single key receipt-data with the base64 encoded transactionReceipt to https://buy.itunes.apple.com/verifyReceipt using an HTTP POST. (For directions on how to do this in various server-side languages see this site)
The server will respond with a JSON object with two keys: status which is an integer and receipt which is the receipt repeated.
If status is zero, the receipt is valid should be accepted, a non-zero value means the receipt isn't valid.
Secure Additions to Apple's Approach
However, there are a few security implications. A user could use another user's receipt since devices aren't tied to receipts, or a user could use another product's receipt since the server doesn't verify the product id of the receipt. To ensure this doesn't happen you should also do the following:
When you first get the receipt in the application, immediately send it to your server along with the device's UUID over a secure channel such as HTTPS or an SSL socket. Do not store it anywhere, leave it in memory.
On your server, store the UUID and receipt pair in a database.
When a device sends a UUID and receipt pair, verify with your database that the receipt has not already been used, and make sure the receipt is actually for your product by checking the receipt's product id. The receipt is just a JSON object, so your server can read the contents by decoding the receipt from base64.
Return a response to the device over the secure channel telling it whether the purchase is:
Authenticated as new (wasn't in DB and was valid)
Authenticated in the past (Same UUID and receipt pair was already in DB)
Denied due to wrong product id
Denied due to having already used the receipt with another UUID.
Since the receipt is only ever in memory on the device, and your application uses the device's UUID (can be spoofed by jailbroken devices, see comments), and all purchases of your product are logged with the device's UUID on your server in a secure manner; a user could not use another user's receipt to verify the purchase, nor could they use a receipt from another product, since you check for that.
You can also validate other fields from the receipt if you want to verify other details of the transaction. For example, if your product is a subscription, you'll like want to look at the transaction date as well.
Also, users cannot pretend to be your server by having the device on a private network with a host of the same name as yours, since they won't have your SSL certificate.
Failure Considerations
Since failure might occur between when the user's device gets the receipt and verifying it with your server (for example if the user looses connectivity, or your server is down for maintenance), you should also let the user "re-authorize". Re-authorizing should get the receipt from the store (using a Restored Transaction) and re-send it to the server just as though this was a new purchase. This should rarely need to be used, but should be available to save the user having to re-buy the product in the case of network failure.
Multiple Devices Consideration
This means that if a user wants to use an application on more than one device, they will have to purchase the product multiple times. This might be the desired effect, but you should likely inform your users before they purchase since they might expect to be able to use the content across devices they have associated with their account.
If the receipt also contains the iTunes account information, authentication could use that to allow users to share content between all their devices (but not their friends').
I do not think that can bind the receipt to the device.
My understanding is that you are allowed to install an application on multiple devices without extra cost. Binding it to the device would mean that if you for example upgrade/change your phone you would need to purchase all the apps again.
I believe if you can't read the user's apple ID, your only protection against piracy would be keeping track(server-side of course) of the number of download requests per transaction_id and limit those if they go over a certain value.
So if you limit it to say 50, that gives a reasonable margin for the user to deploy the app and it's contents on multiple devices and restore several times, but makes it hard for whoever wants to distribute a pirated version with a valid receipt for unlimited restores. Of course they can just distribute a version with all your content, but that's nothing you can do about that and at least they're not taxing your servers.
UDID Does Not Work Anymore
Beniot answer is great, however, these days, as mentioned by Joe D'Andrea, UDID is deprecated and the last time I tried, an App that used the call to get the UDID failed to pass validation during upload to iTunes.
Time-limited Receipts as Alternative to Receipt Counters
To add on to hloupyhonza's answer, besides having a "download request" counter for a particular receipt, you can just limit the receipt validity by time. I found anything between 12 to 24 hours reasonable.
This method also allows the purchaser to use the purchase on any other device he owns as long as he logs into the App Store with the same Apple ID. Note: Each time Restore Purchases is done, Apple returns a completely new receipt (with details of the original receipt contained) - this allows purchases to be restored past the time limit we set for a particular receipt.
Preventing "Off-The-Shelf" Hacks
To prevent typical "Googled" hacking solutions (my data shows this constitutes almost all of IAP hacking attempts), I use a checksum (pick your favorite algorithm, doesn't matter unless you want to make it watertight) of the following concatenation:
receipt-data json string
A shared secret key
Validation success status code.
The App will verify the checksum returned by our validation server. This is not watertight though, as the hacker may retrieve the shared key from your App's binary. But it has prevented all "off-the-shelf" hacks thus far and that's good enough for my use.