I am trying to understand if Google Play allows "Delayed Consumable Items". Something I made up:
Allow user to purchase a Golden-Key (for example).
This Golden-Key will be repurchasable when it loses its shape.
It should be valid on all the devices that is being used with the same Google Play account.
It must not require a brand new membership system that works on my own server.
It must not require me to query for the primary account on the device.
Technically:
Normally, I can always keep track of a non-consumable purchase as Google keeps it forever and as soon as user restores the purchases, all of them will be available for me on any device user have.
Things get tricky if I also want to add consumable items which I do not want to consume right away as consuming it will prevent me from tracking that purchase on other devices of the same user or if the user reinstalls the app. I can do it by first registering an account on my server and saving the consumed item on that account, but I do not want to show the user a signup or login system. I can also do it by retrieving the primary user account on the device and save it serverside, but I do not want to frighten user by requesting permission to those APIs.
So, I noticed that "I" am the person who manages the in-app items on the documentation, but I am not sure if I can assume the following:
- Let user buy an in-app item, but do not consume it until the user wants to purchase another one which may happen even after 3 years. (I will keep the order id on my server and prevent the user from accessing certain things if I mark that order id as used serverside. So, the user will intentionally hit "replace" button in the app and that button will consume the current in-app and start a new transaction.)
To cut it short, maybe I could summarise it in 1 sentence:
Can I consume an in-app item even after (for ex) 3 years, like a manual non-autorenawable subscription system where the duration is variable?
Related
I've been setting up in app billing the last couple of days and been trying to go the right way about it. The guides recommend using a secure back-end server to store the purchase token rather than storing the data locally. The documentation on verifying the purchases on a server is very thin and I'm not sure whether am going the right way about it.
I am using cloud fire store to store the purchase information mentioned in the title. I have a couple of questions:
Should I be reading my product id from a server rather than having it hard-coded in the apk?
How often should I/Do I need to read the device id in this case and search on the DB to make sure the user has a valid purchase? Just the once when making the purchase or intermittently?
If I am to do this, what happens when the buyer changes their phone? The device ID will be different and I wont have a record of their new device making a successful purchase. Or here do I query a skupurchase and it returns the item is already owned, write these new details to the DB?
and finally should I store a successful purchase flag in shared preferences or something so I am not constantly reading the DB and the user can use the device offline?
I was going to go down the route of getting users email using this answer here but there is a lot of comments saying this is very intrusive and I only need it for a simple thing. So I went the device ID route.
BTW I only have one product that unlocks full features and is non-consumable.
What is the correct way to go about this?
This was my approach to the same problems. It may not be relevant to your scenario. Hope it helps.
Should I be reading my product id from a server rather than having it
hard-coded in the apk?
You should store productIDs in the code as they will be used to provide features coded into the app.
How often should I/Do I need to read the device id?
You should not rely on the device id as you have raised the concern about the user changing the phone. You would want to implement the login system and make user login into the app before purchasing the product. This will make your subscription device independent.
The process should be:
User tries to use the locked feature.
App asks for login. Make user register and log in.
User clicks on the buy button again and completes the purchase.
Your server stores the user login information with the purchase information.
User changes the device.
User tries to use the locked feature.
App asks for login. User logs in.
The server returns purchase details with user info.
The app unlocks the feature.
How often should I check purchase details from the server?
You should check for purchase details intermittently.
Why? The user may ask for a refund after some time or the payment gateway would void the purchase for some reason.
IMO, there should be two types of sync methods silent and forced.
In my approach, silent sync would check for internet every 9 days. If the internet is not available, it would not do anything. While the forced sync would check for internet every 25 days from the last sync. If the internet is not available it would ask the user to turn it on otherwise, the user wouldn't be able to use the app.
I was using the subscription period of one month but as you have a non-consumable product you can afford 2-4 months forced sync period.
Should I store a successful purchase flag in shared preferences or
something so I am not constantly reading the DB and the user can use
the device offline?
The syncing process and the login would solve this problem.
I think you might be reluctant to implement a login system for such a small thing and think it would make fewer users buy your product. But by implementing Google authentication it would be fast and users would be less frustrated by it.
Implementing this approach involves a lot of server-side logic.
My system consists of a mobile app (a Cordova app), and a webservice, providing all the relevant data. When a user buys the app in the appstore (or playstore, if android), a user account should be created on the webservice, ideally without any user interaction (no registration). The user account could be linked with the gmail account, apple id, ... This is required, to only allow people who have paid to use the webservice.
My Problems:
I did not find a way to get the user id of the user. (Android seems to have a way: https://github.com/loicknuchel/cordova-device-accounts , but iOS not).
I only want exactly one registration per user. This saves me from using something like a registration page, when the app is first started - this could easily be bypassed and lead to multiple registrations.
The user account should be linked to the user and not the device (so no device UUID or so, as this would not be portable between devices).
Ideas that I had:
(Favorite, doesn't seem to be possible) I have a method "getUserID()" in the app, which returns the right user on the phone. Additionally, I have access to an API to check who bought my App. I can easily cross check, to make sure that the user has permission to use the webservice.
(Unnecessary complicated, seems wrong) Make the app free, use a single in-app purchase to buy access to the webservice. When I searched, I found that it seems that in app purchases give you more information, so there might be the chance to link the app with a user.
(Even worse than 2.) Make the app free, use an own payment system/registration.
My question:
What does the Android/iOS app-store eco system provide, so that I can ensure that one user buying the app creates exactly one user account on my webservice, and this user account is linked to the user and not the device?
You should generate a secret api key for each paying user.
Then the user should use this key to auth into your API and get a token back (you can make it expire after some time if you want a stronger protection). User should attach this token to all of his api calls.
I'm implementing Google Smart Lock into my app. I've got it logging the user in automatically with a single account and it works well.
However, once I add a second account to the mix it always shows the resolution dialog to pick which account I want. This continues to show even after I have selected and account and restarted the app.
This dialog shouldn't show anymore once I have selected the account I want smart lock to use, so what are some possible reasons for it still showing?
-Thank you
I work on the Smart Lock team at Google, hopefully this answers your question:
as you note, if the CredentialsApi.request() API method is called and there are multiple saved accounts for the app, stored in either one or multiple Google Accounts on the device (or auto sign-in has been disabled by the user for one of their accounts or by the developer by calling CredentialsApi.disableAutoSignIn()) then the API will call back with a CredentialRequestResult with getStatusCode() of RESOLUTION_REQUIRED, meaning that UI must be shown for the user to pick a credential to continue. startResolutionForResult() will show the dialog, as described in the API overview.
once the app has a credential, either from auto sign-in or after the user has selected one from the dialog, most apps will use this credential to sign the user in to a backend service and establish a session with a cookie or token for the app and manage this independent of the Smart Lock API. Thus, most apps won't call the API again after the user has signed in and a current session exists, hence the dialog won't be shown again after the initial sign-in.
when the user signs out, calling CredentialsApi.disableAutoSignIn() will prevent the user from being automatically signed back in when the authentication activity is started again, and the disabled state is automatically managed and cleared when the user selects an entry from the credential picker UI or a successful CredentialsApi.save() call is made, and at this point, a user session is established and there is no need to call the API again until a sign-out event.
I think the confusion in this question stems from calling the API after the user has signed in and they might already have an active session. Was that your intent? If you need to manage credentials of the app on the device after a password has been retrieved or input by the user, you can use the Account Manager on Android, which is independent of any Google accounts or APIs (whereas Smart Lock is geared toward saving sign-in info in the cloud for use across platforms on new or separate devices to bootstrap the auth process).
In the future, we are considering remembering a user preference if multiple accounts are stored and the same one is always picked, thereby allowing automatic sign-in, but right now, user action is always required when there are multiple saved accounts available.
Do leave a comment with any questions or feedback if you need more info or clarification or if this doesn't answer your question!
When you have multiple account, you will always show this resolution dialog.
Look this :
from : https://developers.google.com/identity/smartlock-passwords/android/overview
With version 3 of the Billing API, Google has removed the distinction between consumable and non-consumable products. Both have been combined into a new type called "managed" and behave somewhat like a hybrid: Your app needs to actively call a method to "consume" the items. If that is never done for a set of skus, those items basically behave as if they were non-consumable.
The documentation describes the intended purchase flow as follows:
Launch a purchase flow with a getBuyIntent call.
Get a response Bundle from Google Play indicating if the purchase completed successfully.
If the purchase was successful, consume the purchase by making a consumePurchase call.
Get a response code from Google Play indicating if the consumption completed successfully.
If the consumption was successful, provision the product in your application.
I see two problems with this approach. One is fairly obvious and more a "bug" in the documentation than the API, but the other is rather subtle and I still haven't figured out how to best handle it. Let's start with the obvious one for completeness:
Problem 1: Lost purchases on single device:
The docs say that an app should call getPurchases every time it is launched to "check if the user owns any outstanding consumable in-app products". If so, the app should consume these and provision the associated item. This covers the case where the purchase flow is interrupted after the purchase is completed, but before the item is consumed (i.e. around step 2).
But what if the purchase flow is interrupted between step 4 and 5? I.e. the app has successfully consumed the purchase but it got killed (phone call came in and there wasn't enough memory around, battery died, crash, whatever) before it had a chance to provision the product to the user. In such a case, the purchase will no longer be included in getPurchases and basically the user never receives what he paid for (insert angry support email and one-star review here)...
Luckily this problem is fairly easy to fix by introducing a "journal" (like in a file system) to change the purchase flow to something more like this (Steps 1 and 2 same as above):
If the purchase was successful, make entry into journal saying "increase coins from 300 to 400 once purchase <order-id here> is successfully consumed."
After journal entry is confirmed, consume the purchase by making a consumePurchase call.
Get a response code from Google Play indicating if the consumption completed successfully.
If the consumption was successful, provision the product in your application.
When provisioning is confirmed, change journal entry to "purchase <order-id here> completed".
Then, every time the app starts, it shouldn't just check getPurchases, but also the journal. If there is an entry there for an incomplete purchase that wasn't reported by getPurchases, continue at step 6. If a later getPurchase should ever return that order ID as owned again (e.g. if the consumption failed after all), simply ignore the transaction if the journal lists this order ID as complete.
This should fix problem 1, but please do let me know if you find any flaws in this approach.
Problem 2: Issues when multiple devices are involved:
Let's say a user owns two devices (a phone and a tablet, for example) with the same account on both.
He (or she - to be implied from now on) could try to purchase more coins on his phone and the app could get killed after the purchase completed, but before it is consumed. Now, if he opens the app on his tablet next, getPurchases will report the product as owned.
The app on the tablet will have to assume that the purchase was initiated there and that it died before the journal entry was created, so it will create the journal entry, consume the product, and provision the coins.
If the phone app died before it had a chance to make the journal entry, the coins will never be provisioned on the phone (insert angry support email and one-star review here). And if the phone app died after the journal entry was created, the coins will also be provisioned on the phone, basically giving the user a purchase for free on the tablet (insert lost revenue here).
One way around this is to add some unique install or device ID as a payload to the purchase to check whether the purchase was meant for this device. Then, the tablet can simply ignore the purchase and only the phone will ever credit the coins and consume the item.
BUT: Since the sku is still in the user's possession at this point, the Play Store will not allow the user to buy another copy, so basically, until the user launches the app again on his phone to complete the pending transaction, he will not be able to purchase any more virtual coins on the tablet (insert angry support email, one-star review, and lost revenue here).
Is there an elegant way to handle this scenario? The only solutions I can think of are:
Show a message to the user to please launch the app on the other device first (yuck!)
or add multiple skus for the same consumable item (should work, but still yuck!)
Is there a better way? Or am I maybe just fundamentally misunderstanding something and there really is no issue here? (I realize that the chances of this problem ever coming up are slim, but with a large enough user-base, "unlikely" eventually becomes "all-the-time".)
Here's the simplest way to fix all this, that I have come up with so far. It's not the most elegant approach, but at least it should work:
Generate a globally unique purchase ID and store it locally on the device.
Launch a purchase flow with getBuyIntent with the purchase ID as the developer payload.
Get a response Bundle from Google Play indicating if the purchase completed successfully.
If purchase was successful, provision the product and remember the purchase ID as completed (this must be done atomically).
If the provisioning was successful, consume the purchase by making a consumePurchase call(I do this in a "fire-and-forget" manner).
Every time the app is launched, go through the following:
Send a getPurchases request to query the owned in-app products for the user.
If any consumable products are found, check if the purchase ID in the developer payload is stored on the device. If not, ignore the product.
For products with a "local" purchase ID, check if the purchase ID is included in the completed-list. If not, continue at step 4 above, otherwise continue at step 5 above.
Here's how things can go wrong on a single device and what happens then:
If the purchase never starts or doesn't complete, the user doesn't get charged and the app goes back to the pre-purchase-state and the user can try again. The unused purchase ID still is in the "local"-list, but that should only be a fairly minor "memory-leak" that can be fixed with some expiration-logic.
If the purchase completes, but the app dies before step 4, when it gets restarted, it finds the pending purchase (the product is still reported as owned) and can continue with step 4.
If the app dies after step 4 but before the product is consumed, the app finds the pending purchase on restart, but knows to ignore it as the purchase ID is in the completed-list. The app simply continues with step 5.
In the multiple-device-case, any other device will simply ignore any non-local pending purchases (consumables reported as owned) as the purchase ID is not in that device's local list.
The one issue is that a pending purchase will prevent other devices from being able to start a parallel purchase for the same product. So, if a user has an incomplete transaction stuck somewhere between step 2 and 5 (i.e. after purchase completion, but before consumption completion) on his phone, he won't be able to do any more purchases of the same product on his tablet until the app completes step 5, i.e. consumes the product, on the phone.
This issue can be resolved very easily (but not elegantly) by adding multiple copies (5 maybe?) of each consumable SKU to Google Play and changing step 2 in the first list to:
Launch a purchase flow for the next available SKU in the set with getBuyIntent with the purchase ID as the developer payload.
A note on hackability (in order of increasing difficulty for the hacker):
Completing fake purchases via Freedom APK or similar:These apps basically impersonate the Google Play Store to complete the purchase. To detect them, one needs to verify the signature included in the purchase receipt and reject purchases that fail the check, which most apps don't do (right). Problem solved in most cases (see point 4).
Increasing in-app account balance of consumable via Game Killer or similar:These apps will try to figure out where in memory (or local storage) your app stores the current number of coins or other consumable products to modify the number directly. To make this harder (i.e. impossible for the average user), one needs to come up with a way to store the account balance not as a "plain-text" integer, but in some encrypted way or along with some checksums. Problem solved in most cases (see point 4).
Killing the app at the right time and messing with its local storage: If someone purchases a consumable product on their phone and manages to kill the app after the product has been provisioned but before it has been consumed (likely very difficult to force), they could then modify the local storage on their tablet to add the purchase ID to the local list to have the product awarded once on each device. Or, they could corrupt the list of completed purchase IDs on the phone and restart the app to get the award twice. If they again manage to kill the app after provisioning but before consumption of the product (easy now by simply setting the phone to airplane mode and deleting the Google Play Store Cache), they can keep stealing more and more product in this way. Again, obfuscating or checksumming the storage can make this much harder.
Decompiling and developing a patch for the app:This approach, of course, allows the hacker to pretty much do anything they want with your app (including breaking any countermeasures taken to alleviate points 1 and 2) and it will be extremely hard to prevent entirely. But it can be made harder for the hacker by using code obfuscation (ProGuard) and overly complex logic for the critical purchase-management code (might lead to buggy code, though, so this is not necessarily the best idea). Also, the code can be written in a way that its logic can be modified without affecting its function to allow for regular deployment of alternate versions that break any available patches.
Overall, signature verification for the purchases and some relatively simple but non-obvious checksumming or signing of the relevant data (in memory and in the local storage) should be sufficient to force a hacker to decompile (or otherwise reverse-engineer) the app in order to steal product. Unless the app gets hugely popular this should be a sufficient deterrent. Flexible logic in the code combined with somewhat frequent updates that break any developed patches can keep the app a moving target for hackers.
Keep in mind that I might be forgetting some other hacks. Please comment if you know of one.
Conclusion:
Overall, this is not the cleanest solution as one needs to maintain multiple parallel SKUs for each consumable product, but so far I haven't come up with a better one that actually fixes the issues.
So, please do share any other ideas you might have. +1`s guaranteed for any good pointers. :)
First of all I want to say I agree with everything you wrote. The problem exists and I would try to solve it similarly to how you did it. I would really suggest to find someone from Google Play relation team and make them aware of it.
Now back to your solution. This is probably the best standalone solution involving no server I could think about. It's simple but fairly good. One place where it can be misused would be when attackers would fake journal file and "buy" whatever they want, because getPurchases won't return anything from a manipulated journal file.
Otherwise, what else I would try to do is to reduce a probability the app gets killed by the system. For that you might extract purchasing and consumption logic into a smaller foreground service running in a separate process. This will increase probability the service finishes its work, even when Android will kill the bigger game application. More complex, but also a more reliable solution would be to implement journal on the server and share it between devices. With this solution you can always check whether someone is cheating with the purchases and even solve the issue when multiple devices are involved.
I am adding iab to my application this days but after reading all the docs at google and doing some tests I have a doubt. When should I init the service? Is it a good time to do it at application initialization? Should I init the system every time the user wants to buy a product?
How do you handle this?
Cheers.
Okay so it's time for a full-flavoured answer I guess.
You bind to/unbind from the IAB service when you need to perform a transaction or retrieve information/are finished. No need to worry about performance there because this is a local service which does not necessarily connect to Google servers when you bind to it (only exception: a purchase, but that'll take some time anyway); it follows a different strategy to decide when to go online.
It's a good idea to sync your app's internal idea of what the user owns with the idea of IAB, as tjPark rightly says. Whether that's at the startup of your app or only after the user makes a couple of choices depends on your app. If you need to know for your splash screen what IAB items the user owns, then do it in your splash screen Activity. If it's only becoming relevant later, it makes more sense to query the IAB service later.
You should also carefully think of a consumption strategy which suits your need if your IAB items can be consumed.
Always be aware that IAB V3 uses caching extensively so even synchronizing your app with the IAB service does not necessarily get you the latest information. E.g. when a user buys an in-app product on device 1 and wants to use it on device 2, there will be a delay until it shows up. Or if you cancel a transaction in Google Checkout/Wallet and the device is offline, you also won't know immediately.
Don't use Google example code without refining it to achieve product maturity.
Know that IAB service responses can be subject to re-play attacks because you cannot provide a nonce with your request.
Know that if you don't have a server-side validation then your whole IAB code could be replaced by dummy code which simply returns positive responses.
http://developer.android.com/google/play/billing/api.html,
from above, Google said that
When your application starts or user logs in, it's good practice to check with Google Play to determine what items are owned by the user. To query the user's in-app purchases, send a getPurchases request. If the request is successful, Google Play returns a Bundle containing a list of product IDs of the purchased items, a list of the individual purchase details, and a list of the signatures for the purchases.
Checking on every init would give more protection for your products I guess