I have an Android app where users can buy 1 inapp product to unlock some features.
I've read extensively the guide at: https://developer.android.com/google/play/billing/billing_library_overview#java
I understand that in order to let users buy the inapp product I have to:
Retrieve the list of available SKUs (in this case, my only 1 inapp product) using the querySkuDetailsAsync() call. This is just to double check the user's device is capable of managing inapp products.
Show the BUY button if the SKU appears in the result of the previous call (which means the local Google Play instance in the user's device can handle inapp products).
Call launchBillingFlow() passing the SKU of my inapp product, to initiate the Google payment process flow (Google UI, popup asking for card details etc..)
Listen to the callback onPurchasesUpdated() to get the return code (basically payment denied, payment successful or payment cancelled) and act accordingly. In case of payment successful proceed to verify the purchase token signature either locally (using a local copy of the Play developer's RSA public key) or remotely on my secure server with the same key.
unlock the paid features(s) on my app
That's where things get confusing. From the Google documentation perspective the job is done, they explained to you how to retrieve/purchase/and verify a user payment. However, nowhere it is explained how to remember the payment and unlock the paid feature during the app startup.
The documentation states:
To retrieve information about purchases that a user makes from your
app, call the queryPurchases()
So it seems like the app doesn't need to remember anything, just call the queryPurchases() at startup and check if the SKU is present (user already paid for it) or not (user still hasn't bought the paid version of the app).
So my app is doing just that, calling queryPurchases() at startup and check if the SKU is present or not.
This method works very well, even when the app starts offline. However some users are lamenting the fact that sometimes the app doesn't start in paid mode, because (I debugged the code) the function queryPurchases() fails (sometimes) when the device is offline. Could it be that the queryPurchases() is calling the local Google Play cache which can lose track of previous purchases for some reason? (cache purging, etc...)
What's the appropriate method to remember user purchases and enable paid features at app startup?
You could keep your own "cache" (SharedPreferences or a DB) with the results of onPurchasesUpdated and use queryPurchaseHistoryAsync. When the app first starts you can show paid content if your cache is telling that the user purchased the product and call queryPurchaseHistoryAsync at the same time to get the most recent purchase made by the user for each SKU, when onPurchaseHistoryResponse you can update your cache and hide the paid content if the purchase expired.
Take also into account that it's recommended for security purposes to go through purchases verification on your backend.
If you don't want to manage your own server, it may be worth using a tool like RevenueCat, that offers a purchase/subscription backend-as-a-service.
How often does it happen? My take is that Google Play client is taking care of caching, so there is no direct way how to handle that if you are just relying on this service. This can cause troubles if users re-install or change their devices.
Another approach is to build your own back-end, which will be necessary if you want to grow your app anyway.
i offer 1 in app purchase (full functionality). I save the result of the purchase in a boolean variable in sharedpreference and also use queryPurchases on app start, so if someone deleted and reinstalled the app, he gets his full functionality back.
That works properly, but i also would like to block full functionality, if it turns out, that someone did NOT purchase the full functionality (and cheated the app with a rooted device). That would also include deleting some data that he is not supposed to have without the in app purchase.
However, i am confused about the description of queryPurchases. It says
"Upon a successful purchase, Google Play's In-app Billing service caches the user's purchase data locally."
How long does it store that information? Does it update it automatically if an internet connection is available? How can i avoid getting no purchase even though the user bought the in app purchase and falsely blocking his full functionality? What is there
Is there a way to get the clear information, that the user in fact did NOT purchase a certain item, rather than just no information about a possible purchase? I really want to avoid blocking features if the user paid.
Anything else wrong with my approach (Saving the purchase in sharedpreferences
to have the information available immediatly and additionaly query purchase)
Edit: I also noticed, that if i refund an in app purchase, de- and reinstall the app, queryPurchases still finds that purchase. There must a way to avoid that?
Don't bother storing purchases in shared preferences. They are already on the device via the billing library. The purchases are stored in the Play store data which the billing library retrieves for you.
You can work by assuming that full functionality is enabled until you receive the result of query purchases which will tell you definitively whether the user has bought it or not. If you prefer, you can do that backwards and assume trial mode until you know a purchase exists.
Either way, the query for purchases will return quickly as no network connection is required because billing library simply connects to play store on the device for the data.
If you refund in the developer console, it may take a while for that to filter to the users device but it will eventually. It isn't a real time system and a purchase will remain cached on the device until the refund is fully processed and sent to the users device.
You can consume a purchase directly on a device which removes it instantly from the user account.
Is there any way to see if an in-app transaction concerns with an item already owned by a user? Our reports get a bunch of duplicate transactions due to people re-purchasing stuff (after removing the app or switching user accounts) – they don't get charged on Google side for purchasing the unconsumed items multiple times and we need a way to know that.
We tried the following ways:
OrderId field from purchase receipt – seems to be unique every time,
even if the item is already owned.
PurchaseToken – despite having a
number of formats, it seems to be the same for transactions that
involve purchasing the same item by the same person, however a
few of our users still get unique purchase tokens every time they
"purchase".
Is there any way to tell if in-app purchase of this product has already happened in the past (like referring to transaction_id field from original iTunes receipt in Apple ecosystem)?
Is there any way to tell if in-app purchase of this product has
already happened in the past?
AFAIK the In-app billing API doesn't provide such info for consumed products (I assume you use products and not subscriptions).
The cases are:
For non consumed products you should get RESPONSE_CODE with value 7 - ITEM_ALREADY_OWNED from the getBuyIntent method, so you might just not consume the products on the client side and just keep them indefinitely.
For consumed products you might want to save the product SKU (productId) on your app, or associate it with some kind of user account if your system uses user sessions. That way, with the next attempt to buy the product you can match the SKU and just programatically disallow the app to start the intent sender for buying a specified SKU product.
Both orderId and purchaseToken should be unique for a given transaction. They are for further use, for example with the Google Developer API, ie: https://developers.google.com/android-publisher/api-ref/purchases/products/get (see token in request params).
Alternatively you might use the said developer API and optional developerPayload field (however you wish to do that) to match users of your system and play store transactions.
I have working app that sells in-app products (InApp billing v3).
Recently I've made an update for this app and made a terrible mistake there: I consumed some of purchases I didn't want to consume.
I issued an update and users updated the app and then wrote me they have no purchased access. (As I found later, I consumed wrong items).
So I have some questions now:
Is there a way to restore consumed purchases or I should return payments to my users?
Is there a way to find out how many and which purchases were consumed?
If returning payments, is there a way to find out if user still has the product or is it consumed with out changing the app (through google services)?
Thanks.
According to what I have understood from your question, It seems like you want to purchase product only once.
What I wished to ask: is there a way to request information about consumed products like about owned products.
you can get response or it's information in any type(Managed Product, Unmanaged Product, Subscription) in the onActivityResult method() check my below link.
In-app billing-v3 error in activity result
but I would not prefer you to manage it customly as you told for one time purchase product(not consumable product). You should go with the Manage product.
Managed In-app Products:
Managed in-app products are items that have their ownership
information tracked and managed by Google Play. When a user purchases
a managed in-app item, Google Play stores the purchase information for
each item on a per-user basis. This enables you to later query Google
Play at any time to restore the state of the items a specific user has
purchased. This information is persistent on the Google Play servers
even if the user uninstalls the application or if they change devices.
for more information about product type
you can query any time you want and you will get the product information, and even you don't need to manage if user has already purchase this product or not.
Is there a way to restore cnsumed purchases or I should return payments to my users?
Better option is you should give the payments back to the user and for the next time check the whole app once using dummy product "android.test.purchased" and update your app.
Is there a wat to find out how many and which purchases were consumed?
You have to check in the Google wallet because all transaction should be handle by the it, check if it gives you product type or not. Using your google developer console credential you can signed into google wallet.
If returning payments is there a way to find out if user stil has the product or is it consumed with out changing the app (through google services)?
as per above answer you have to check in google wallet, according to my knowledge they are provide us all of information about product type with user detail.
Let me know if I have not properly understood your question.
Hope it will solve your problem.
I'm using version 3 of the in-app billing API. I have a single, managed, non-consumable item. I have not released this feature in my app yet, so I want to decide on the purchase payload contents before there are any purchases.
From "Security Best Practices":
Set the developer payload string when making purchase requests
With the In-app Billing Version 3 API, you can include a 'developer
payload' string token when sending your purchase request to Google
Play. Typically, this is used to pass in a string token that uniquely
identifies this purchase request. If you specify a string value,
Google Play returns this string along with the purchase response.
Subsequently, when you make queries about this purchase, Google Play
returns this string together with the purchase details.
You should pass in a string token that helps your application to
identify the user who made the purchase, so that you can later verify
that this is a legitimate purchase by that user. For consumable items,
you can use a randomly generated string, but for non-consumable items
you should use a string that uniquely identifies the user.
When you get back the response from Google Play, make sure to verify
that the developer payload string matches the token that you sent
previously with the purchase request. As a further security
precaution, you should perform the verification on your own secure
server.
Rightly or wrongly, I have decided not to take the "further security precaution" of setting up a server to perform purchase verification. And I do not store my own record of the purchase -- I always call the billing API. So is there really any reason for me to do this payload verification? The verification API itself certainly verifies the identity of a user before reporting an item as purchased, and if an attacker has compromised a device (either the app or the google play API), I don't see any benefit of doing an additional check on the user's identify on the device where it can easily be circumvented. Or is there a reason to do this that I'm not thinking of?
If you don't keep a record there is no way to verify that what you get is what you sent. So if you add something to the developer payload, you can either trust that it is legitimate (which is a reasonable assumption if the signature verifies), or not trust it completely and only use it a reference, but not for validating license status, etc. If you store the user email, for example, you can use the value instead of asking them to enter it again, which is slightly more user friendly, but your app won't break if it is not there.
Personally, I think that this whole 'best practices' part is confusing and is trying to make you do work that the API should really be doing. Since the purchase is tied to a Google account, and the Play Store obviously saves this information, they should just give you this in the purchase details. Getting a proper user ID requires additional permissions that you shouldn't need to add just to cover for the deficiencies of the IAB API.
So, in short, unless you have your own server and special add-on logic, just don't use the developer payload. You should be OK, as long as the IAB v3 API works (which is, unfortunately, quite a big 'if' at this point).
You should pass in a string token that helps your application to identify the user who made the purchase...
If your application provides its own user login and identity, which is different from what Google accounts the phone is connected to, then you would need to use the developer payload to attach the purchase to one of your accounts that made the purchase. Otherwise someone could switch accounts in your app, and get the benefit of purchased stuff.
e.g.
Suppose our app has login for userA and userB. And the phone's Android google account is X.
userA, logs into our app and purchases life membership. The purchase details are stored under google account X.
userA logs out, and userB logs into our app. Now, userB also gets the benefit of life membership, as android google account is still X.
To avoid such misuse, we will tie a purchase to an account. In the above example, we will set developer payload as "userA" when userA is making the purchase. So when userB signs in, the payload won't match to signed in user (userB), and we will ignore the purchase. Thus userB can't get benefits of a purchase done by userA.
There is also another approach to the developer payload handling. As Nikolay Elenkov said it is too much overhead to require user ID and setting additional permissions for user profile to your app, so this is not a good approach. So let's see what Google says in the latest version of TrivialDrive sample app in In-App Billing v3 samples:
WARNING: Locally generating a random string when starting a purchase and
verifying it here might seem like a good approach, but this will fail in the
case where the user purchases an item on one device and then uses your app on
a different device, because on the other device you will not have access to the
random string you originally generated.
So the random string is not a good idea if you are going to verify the purchased item on another device, but still they don't say this is not a good idea for verifying the purchase response.
I would say - use developer payload only for verifying the purchase by sending a random unique string, save it in preferences/database and on the purchase response check this developer payload. As for querying the inventory (in-app purchases) on Activity start - don't bother checking developer payload since that might happen on another device where you don't have that random unique string stored. That's how I see it.
It depends how you verify the developerPayload. There are two scenarios: remote verification (using server) and local (on device).
Server
If you're using a server for developerPayload verification it can be arbitrary string that can be easily computed on both the device and server. You should be able to identify the user who has performed the request. Assuming every user has the corresponding accountId, the developerPayload may be computed as combination with purchaseId (SKU name) like this:
MD5(purchaseId + accountId)
Device
developerPayload shouldn't be user email. A good example why you shouldn't use email as payload is Google for Work service. Users are able to change their email associated with the account. The only constant thing is accountId. In most cases email will be OK (e.g. Gmail addresses are immutable at the moment), but remember to design for future.
Multiple users may use the same device, so you must be able to distinguish who's the owner of the item. For device verification developerPayload is a string that uniquely identifies the user e.g.:
MD5(purchaseId + accountId)
Conclusion
Generally the developerPayload in both cases may be just the accountId. For me it looks like security through obscurity. The MD5 (or other hashing algorithm) and purchaseId is just a way to make the payload more random without explicitly showing that we're using id of the account. The attacker would have to decompile the app to check how it is computed. If the app is obfuscated even better for you.
The payload doesn't provide any security. It can be easily spoofed with 'device' approach and without any effort seized in 'server' checking. Remember to implement signature checking using your public key available in the Google Publisher account console.
*A must-read blog post about using account id instead of email.
In the Google IO video about IAB v3 given by the author of the trivial drive sample himself, this was briefly addressed towards the end of the video. It's to prevent replay attacks, e.g. attacker sniffs the traffic, steals the packet containing a successful purchase, then tries to replay the packet on his own device. If your app doesn't check the identity of the buyer via the dev payload (ideally on your server) before releasing the premium content (also ideally from your server), the attacker will succeed. Signature verification can't detect this since the packet is intact.
In my opinion, this protection seems ideal for apps with online account connectivity like clash of clans (payload comes in naturally since you have to identify users anyway), especially where hacking compromises multiplayer gameplay with far reaching effects other than a simple localized case of piracy. In contrast, if client side hacks on the apk can already unlock the premium content then this protection is not very useful.
(If the attacker attempts to spoof the payload, the signature verification should fail).
Late 2018 update: The official Google Play Billing Library intentionally does not expose the developerPayload. From here:
The field developerPayload is a legacy field, kept to maintain the compatibility with old implementations, but as mentioned on Purchasing In-app Billing Products page (https://developer.android.com/training/in-app-billing/purchase-iab-products.html), this field isn't always available when completing tasks related to In-app Billing.
And since the library was designed to represent the most updated development model, we decided to don't support developerPayload in our implementation and we have no plans to include this field into the library.
If you rely any important implementation of your in-app billing logic on the developerPayload, we recommend you change this approach, because this field will be deprecated at some point (or soon).
The recommended approaches is to use your own backend to validate and track important details about your orders. For more details, check the Security and Design page (https://developer.android.com/google/play/billing/billing_best_practices.html).
I struggled with this one. Since a Google Play account can only own one of any "managed" item, but could have several devices (I have three), the above comment from somebody that you sell a "per device" won't work... they'd be able to put it on their first device, and no others ever... If you buy a premium upgrade, it should work on all your phones/tablets.
I despise the notion of getting the user's email address, but I really found no other reliable method. So I grab the 1st account that matches "google.com" in the accounts list (yep, a permission to add to your manifest), and then immediately hash it so it's no longer usable as an email address but does provide a "unique enough" token. That's what I send as the Developer Payload. Since most people activate their device with their Google Play id, there's a good shot all three devices will get the same token (using the same hash algorithm on each device).
It even works on KitKat with multiple "users". (My developer id is on one user, my test id on another, and each user in their own sandbox).
I've tested it across six devices with a total of 3 users and each users devices have returned the same hash, and the different users all have distinct hashes, satisfying the guidelines.
At no point am I storing the user's email address, it's passed straight from the code to get the account names to the hash function and only the hash is saved in the heap.
There's probably still a better solution out there that respects users privacy even more, but so far I haven't found it. I'll be putting a very clear description of how I use the users Email address in my privacy policy once the app is published.
This often responds to a product definition (Your application).
For example for the case of subscriptions. Will the same user be able to use the subscription on all the devices he / she has? If the answer is yes. We did not check the payload.
For consumables. Suppose a purchase in your application gives you 10 virtual coins. Will the user be able to use these coins on different devices? 4 on one device and 6 on another?
If we want to work only on the device that made the purchase we have to check the payload for example with a self-generated string and locallly stored.
Based on these questions we have to decide how to implement payload check.
Regards
Santiago