I have an Android app and I also offer 1 inapp purchase to unlock such app to the Pro version.
I know how to do use the inapp purchase API and such but I found discordant ways on how to check if the app should start as Free or Pro.
Many people suggest that after a successful purchase the app should store the Google Play receipt or other information in a local database and let the app check the presence of that information at startup (in order to start properly as Free or Pro)
My question is, instead of bothering saving the purchase information and retrieving it from a local database why not calling the restore purchase API RestorePurchases(), have a look at the returned object if the InApp item is present and unlock the app accordingly?
As far as I know the call doesn't require internet connection, it's just a local call to the local Google Play authority... am I missing something?
Let me explain how we manage it at QuitNow!, an app with the same behavior than yours.
We only have one SKU called unlock_all_pro_features. If the user has it, it means that the user bought the PRO features before.
So, in the Android side, everytime the app is started we try connecting to IInAppBillingService. When onServiceConnected() is called, we ask it for all the user owned SKU's. If it has our lovely SKU, we store in a SharedPreference that the user was a PRO one. And then, if it wasn't PRO before doing all this magic, we update the screen to show the brand new features.
Bad things there: the user can return the SKU!
To face that, when we consider that a user was a PRO one, we also ask if the user has the needed SKU. If that check fails 20 times, we reset the features to the FREE version.
Why checking it 20 times instead of just one time? Sometimes, we found that the service said that the user had any SKU, while he actually had the PRO one. Why? Don't know. So, checking it 20 times is a simple way to assure that we don't kick PRO users when unneded.
Related
I have a paid app in the Google Play Store. I'm considering reducing the price of that app (somewhat; not all the way to free) and offering one of the features as a separate in-app purchase.
If I did that, I wouldn't want to yank the feature away from anybody who's already bought it.
Is there any way to figure out either the date that the user bought my app, or the original version of the app that they bought, or something like that? I'd like to say something like, "If the app was before the price change (either by date or by version), they should have the feature for free; otherwise, require IAP to unlock the feature."
For example, iOS does have a feature like this; the app receipt includes an "originalVersion" field which can be used to control access to features.
Unfortunately for your customers, this is impossible. There is no API call or anything else to Google Play where you can get the time on which the app was bought.
I know there is an android-publisher API in existance, however it doesn't offer any feature to check that.
The functions you want to use are not public availible and only used by the Playstore internally!
Workarounds which you could do:
1. Get the time the app was installed
On the first start you could check that and unlock the features.
Warning: This system could be abused by changing the time on the device
long installed = context
.getPackageManager()
.getPackageInfo(context.getPackageName(), 0)
.firstInstallTime;
2. Give users free keys
You could give every user who's using the app atm a free key via mail or push notification
3. Unlock the inapp purchase now
Publish an app update which unlocks the inapp purchase for free. After a few weeks you could pusblish your new version with the lower price and just unlock the features as if your current customers had bought your extension.
You might be able to hack your way around this if you're using some sort of persistent storage.
For SharedPreferences, on the first run, do a check for one of your preferences using SharedPreferences.contains(). If it contains it, the app must have already been installed. If not, set another preference that marks the user as new, and set yet one more so it doesn't do the check every time.
That might only work if the preference doesn't have a "default" value, I'm not entirely sure if setting a default in xml will mark it as contained.
You could do something similar if you have any assets that get transferred to SD, or any similar one-time setup. Just check to see if it's already done before doing it the first time.
If you're using an SQLite DB, you could increment the DB version and mark as "paid" in onUpgrade() if coming from the current version(or earlier).
There are some pitfalls here, though. For instance, if a previous paid customer completely uninstalls before installing the new version, or if it's on a new device. For that reason you should:
4. Provide Support
In the about or FAQ section of your app and on first run of your new version set a support mail adress which customers can use if they have any problems because the new features were not unlocked for them.
They could provide any proof (bill) for their purchase.
Like I said, those ideas are workarounds, but I don't know of any "official" way to check to see an app install is an upgrade or an initial install.
Your best option may be a combination of those four.
FYI: I've opened a feature request/idea in Google Cloud Connect for work which you could vote: https://connect.googleforwork.com/ideas/9392 (You can only vote if you have a paid Google Buisiness Account)
I hope this helps you at least a bit.
As far as i know, the best you can do is find the date it was installed. http://developer.android.com/reference/android/content/pm/PackageInfo.html#firstInstallTime
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 building an application which I wish to upload to the app store as a paid app. But i would like to post it as a trial app for a set amount of time. I looked at all the options in the app billing version 3. I have found a few problems.
If I create my app with a non-consumable inapp item then i will not be able to keep it for a set duration as a trial app(Please let me know if my assumption is correct).
I f I create it via a subscription based model then is there a way I can make the subscriptions last forever and not yearly?
I think I know what you're asking, but if I'm wrong let me know.
What you want to do is let the user download the app for free, let them use it for say 7 days, then require them to pay for the upgrade IAP (in app purchase) or prevent them using it?
If the above is correct there is one main problem that you're going to encounter. To track the date the user started using the app you will have to use something like a shared preference, an entry in a sql db or some local file etc... The only problem with all of these options is the user can erase the data and install the app again to use it free for another 7 days. The only way around this is to implement your own web registration service which I imagine would be a little too difficult.
The best way, assuming you're not too bothered about the people who will bypass it, is to capture the date/time when the app first starts and save it. Each time the app starts check if they have purchased the upgrade, if not check the current date/time against the stored value and if your trial period has elapsed, prompt them to buy the upgrade or close the app.
The main problem here is the user uninstalling / reinstalling the app and losing the shared preferences.
The easy way around this is to use Google's SharedPreferencesBackupHelper
Android SharedPreferences Backup Not Working
You simply save the install date as a preference. Even if the app is un-installed, if they install it again, the orig install date will still be available for you to query.
I am making an app.Which has a free trial version and a premium version.The free version runs as premium for 7 days and after that it will prompt user to buy premium and exit.To do so I have come up with some ideas such as:
1)Identify the device uniquely and send that unique id and date to a server.And on start up check if this device's app is more than 7 days old if so block it.And when the user uninstalls the app and installs again upon registering to the server the app will be blocked.I have read about some ways of identifying a device uniquely.But as this link suggests none of them is reliable and won't work on every device and OS version.
2)I can write a file to sdcard indicating the app installation and check for that particular file on first run to detect re-installation.But that file can be easily deleted by the user and the app re-installed thus obtaining another trial for 7 days which is not acceptable.
Is there a way I can attain my goal?
These things can be done but, I encourage you to think again. What you're proposing is not good for your users and not good for users usually means lower app sales.
The app has to check with your server every time it starts. So I can't use it if I have no data connection or your server is down? Even if I paid for it?
Whatever you do, it can be defeated, especially if using date bound stuff.
I recommend that you think about adding value to the premium version, which is not in the free version, that will encourage people to buy. Or support the free one with advertising, which a lot of people will gladly pay a reasonable amount to get rid of. I recommend NOT releasing a free version which is in someway crippled but rather to make your paid version valuable to the user.
My suggestion is that -
Trial version -
Send a time stamp key(a key as hex string which holds current time and device serial number and trial or premium id) to the server when it is started. In server if any key is not stored, that means the app is started first time and save it for future checking. Server will send a time stamp key(key with server current time) to the device. And device will save this key in local database.
Next time when the app wii start again, the key will be sent to server and server will extract time and serial number from key and check with the first key(as identified first launch), if it exceeds 7 days as identified that the app has been expired.
If network is not available don't start the app.
Premium version -
Extract the trial or premium id from key, it is premium don't expire the app just keep continue the app.
It will work for all cases if user change the device time won't hamper this logic.
There are a few apps on the market that are set up to have a free main component(which is a trial limited to 7 days lets say) then "recharge" apps that will add a certain amount of subscription time to an account for the user that allows them to keep using the main app. These "recharge" apps are available in the market as well. What I would like to know is how to make it so that once the user has paid for one of these "recharge" apps and used it to add time to their subscription, they are unable to uninstall it and re-download it(for free since they paid for it once). Basically how do I set my application up so that you only get 1 successful download of the app from the market per payment. Once the time has been added to the users account I would like the market to behave as though the "recharge" app has never been purchased.
What I would like to know is how to
make it so that once the user has paid
for one of these "recharge" apps and
used it to add time to their
subscription, they are unable to
uninstall it and re-download it(for
free since they paid for it once).
You cannot prevent them from uninstalling and re-downloading it. At most, you might work out your own mechanism to prevent the app from applying a new "recharge".
Once the time has been added to the
users account I would like the market
to behave as though the "recharge" app
has never been purchased.
This is not possible. In fact, it works in the reverse -- the user will forevermore be able to download it, on as many devices as they want, so long as they are using the same Google account for each device. Purchases of apps are for the lifetime of the Android Market, not for a developer-selected lifetime.
Check out the new in app billing functionality, you may be able to leverage some of it's functionality to sell additional functionality/subscription time.
Setup a server and once the user downloads the app, on the first start the app shall connect to your webserver and send the IMEI oder device serial number to the server and the server will send a code which enables all the features.
Since the date of the first activation is stored on your database on your server, the user won't be able to change it until he puts in a new SIM Card (hence changing his IMEI number) even if he redownloads the application several time, the IMEI basically never change unless you change the SIM.