I have just implemented the In-App purchase on my Flutter App.
I saw on various documentations that we should always verify the purchase prior to provide the benefits, but I can't figure out what that is really means.
The PurchaseDetails object provide the followings properties:
purchaseID
productID
verificationData
transactionDate
status
How are we supposed to use them for the verification ?
Thank you for your help,
Benjamin
but I can't figure out what that is really means.
It simply means that as a service provider, we should be double-checking a successful transaction with the payment processing authority (which is Google/Apple for Android/iOS IAP respectively) to prevent any form of fraud.
How are we supposed to use them for the verification ?
https://pub.dev/documentation/in_app_purchase_platform_interface/latest/in_app_purchase_platform_interface/PurchaseVerificationData-class.html
PurchaseDetails.verificationData exposes two kinds of data (they are used the way they are named):
localVerificationData
serverVerificationData
Purchase receipt verification can be done either locally (i.e. on the client app) or on (your) server side. Server-side implementation is recommended for security reasons.
Now, this is where verificationData behaviour for iOS and Android changes.
iOS verificationData
How iOS IAP verification works
Android verificationData
How Android IAP verification works
Ref 1
Ref 2
Without going too much into the references (I think they are quite self-explanatory):
In case of iOS, localVerificationData=serverVerificationData, and the purchase can be verified either by hitting the verifyReceipt endpoint (can be done either on server-side or client-side), or decrypyting the data on the client/server-side and parse the decrypted data manually.
In case of Android, localVerificationData carries the ProductPurchase data, that is otherwise returned by using the token provided by serverVerificationData to request the aforementioned REST endpoint. A purchase is considered 'verified' if purchase status is PURCHASED.
You may also have take care of other situations, depending on your use case.
Hope that the references linked above are useful.
Related
As the documentation says:
queryPurchaseHistoryAsync():
Returns the most recent purchase made by the user for each SKU, even if that purchase is expired, canceled, or consumed.
So lets say I would like to make an IAP for the user to remove Ads forever from my app.
How do I decide with a fresh install if that user already purchased that IAP if this function also returns the "canceled" IAPS?
I would like to query the actually purchased IAPS, not everything. Becuase that way a user could unlock the Ad free version with a cancelled IAP.
To make it worse, the Purchase class doesn't have status like "cancelled" or "consumed" I cannot decide the state of the user's purchased IAPS.
Am I missing something here?
Thanks in advance
The billing client documentation states it checks for the user.
Then we have the question: Who is the user
If you look at the steps taken for the billing api on https://developer.android.com/google/play/billing/billing_library_overview then you see that a connection to google play is needed.
What happens then is that the current user on android, the account they are signed in with on google play and their mobile device is determined to be the user.
So if they purchased it via google play in app, it will always be assigned to that user on google play and that is what's being queried by this. No matter on which device they are logged in, Their tablet, smartwatch, phone, television, etc...
How to get a list of actually bought products
Instead of using async that gives you everything included abandoned shopping carts I suggest you use https://developer.android.com/reference/com/android/billingclient/api/BillingClient#querypurchases
That way Get purchases details for all the items actually bought and paid within your app. This method uses a cache of Google Play Store app without initiating a network request.
Note: 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
If you read that API link it's easy to check with the online cache if the purchase was valid with a simple GET request.
Personally I would build in a check if the phone is suspect to be rooted before doing the manual background check with the purchases API that sends a request to the store.
Phones that are not rooted have a higher trust level, as the user are probaly not very tech savvy and will not have a shimmer of a clue how to circumvent such checks, and the google play cache will be updated regularly, reflecting trustworthy data.
When a phone is suspect to be rooted(you can read protected directories/write to them), then perform the check online if they bought the stuff each time you deem it neccesary.
This question related to AppStore in-App Purchase Receipt Verification Issues
In short - receipt data from iTunes is encoded in strange kinda-JSON format without any specifications for it. The accepted answer states that there is no need to decode it, it must be just sent to iTunes.
In my case I want decode that data to ensure that bundle_id parameter is equal to my apps bundle_id.
https://buy.itunes.apple.com/verifyReceipt method treats receipt as valid even if it was not generated for my application - so if hacker will send valid receipt from other app to my server - he well succeed.
The question is in how to do such verification properly, that additional step was certainly not intended by iTunes developers (otherwise they would not used pseudo-JSON), but nevertheless I think that it is necessary for protection from such attacks.
Any thoughts on this?
Gill, you should consider the receipt data that you are validating opaque and not try to interpret it. Send it to Apple's verifyReceipt service and examine the response. It will be a non-strange JSON object. If the "status" field is 0 then Apple considers it to be a valid receipt but it is up to your server app to determine if it is being used in a valid context. To do that you can examine the "receipt" object where you'll find the bundle and product IDs among a lot of other things.
If you're determined to some pre-validation of the receipt data before sending to Apple you can do that as well with some extra effort. It is a base64 encoded JSON object that contains two other base64 encoded objects one of which is receipt data as described above. The problem with this approach is the data is not intended to be examined by Apple service users so would be subject to change without notice. It can be a useful technique for debugging however.
If you are using iOS7, you can do receipt validation including bundle_id verification using on-device methods. Take a look at-- iOS InApp Purchase Receipt Validation iOS 7 and A complete solution to LOCALLY validate an in-app receipts and bundle receipts on iOS 7. I'm assuming you are talking about on-device validation and not using your own server to handle validation.
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.
I have developed an iPhone application that was rejected. With this application I offer an SMS transmission service. On my website each user has an account and he can buy credits on the website to be able to send SMS.
The reason for rejections was that my app uses an external service, my website. They say I have to use in-app purchase for the credits.
So now I am about to extend my api. So if purchase takes place in-app the web server needs to know that there was a purchase and what type of purchase. This is done using HTTP-POST.
I could build a simple url and register the purchase in the user-account, since I can verify that an purchase was performed correctly in the app store. But to prevent hacking and just for security reason I think there has to be some kind of encryption.
E.g. if the payment process in the app was successful I send a HTTP-POST to my webserver. It contains some encrypted key that can be encrypted by the webserver.
What do you think about all this? How can I make my api safe regarding in-app purchase and what security algorithm could I use?
Any other suggestions or ideas?
You should probably look into the Server Product Model, rather than trying to invent some way for your app on the device to tell the server that credit was purchased after the fact. The section on Verifying Store Receipts will come in handy; in short, your app transmits the contents of transactionReceipt to your server (ideally via HTTPS). Your server base64-encodes it, embeds it in a simple JSON object, and posts it to Apple to get the status and verified purchase information.
I have a complete implementation of storekit including the server code.
http://blog.mugunthkumar.com/coding/mkstorekit-4-0-supporting-auto-renewable-subscriptions/
Try this
You should check out this wiki article on public key encryption. This is what you'll probably what to be using.
CommonCrypto is a module available for iOS that deals with this type of encryption. Here is a sample project in the iOS developer library that uses this module.
Yes You are correct. That is the best way. When user purchase credit using In-App purchase(IAP) You will have delegate method for purchase succeed. So you can call your Server API in this delegate method and put some flag with credit number in the user table of your server database. You can send this info encrypted and your server would decrypt it and insert in your database.
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.