So we want to support in-app billing via Google's billing API and via AliPay for China. I've written a method that should return either a GooglePlay or an AliPay billing client (whichever is available). I need a way to determine whether Google's billing service is available to the user so I know which client to return.
So far I've come across a few different options and I'm unsure which one is the one I need:
Create a ServiceConnection and check the result of IInAppBillingService.Stub.asInterface(service)
.isBillingSupported(3, context.packageName, "inapp")
Here's the full code: https://gist.github.com/first087/9088162
This is a bit tedious since I need to wait for the service to connect, get the asynchronous result and then return the correct billing manager, but at first glance seems to be exactly what I need.
Use the GoogleApiAvailability class and check the result of isGooglePlayServicesAvailable(context)
This option is a lot cleaner than the 1st one, but I'm unsure whether it returns what I need and also requires me to add the com.google.android.gms:play-services-base library to my project.
Check if the GooglePlay app is installed on the device.
This is the most unreliable option (I think), because you can manually install the app, even though it's not been pre-installed by the manufacturer, and then you might not be able to make purchases since you're in China and they don't allow that.
Has anybody had similar experience? How do I correctly determine whether the user can make purchases via the PlayStore?
So after testing the methods in China, with a phone that did and didn't have a the PlayStore app installed, here's what we found:
With PlayStore app installed and without VPN
GoogleApiAvailability.isGooglePlayServicesAvailable() returns code 2 - ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED
IInAppBillingService.isBillingSupported() returns code 3 - BillingResponse.BILLING_UNAVAILABLE
Without PlayStore app installed and without VPN
GoogleApiAvailability.isGooglePlayServicesAvailable() returns code 9 - ConnectionResult.SERVICE_INVALID
IInAppBillingService.isBillingSupported() returns code 3 - BillingResponse.BILLING_UNAVAILABLE
With PlayStore app installed and with VPN
GoogleApiAvailability.isGooglePlayServicesAvailable() returns code 0 - ConnectionResult.SUCCESS
IInAppBillingService.isBillingSupported() returns code 3 - BillingResponse.BILLING_UNAVAILABLE
Conclusion: The safest way to determine whether billing is actually available is via the isBillingSupported() method. If you don't want to use it via the "hacky" way shown in option 1 of the question, you can just instantiate a new BillingClient and wait for the callback of its startConnection() method.
Here's a gist of the coroutine I wrote which gives you one of the two implementations of a BillingManager depending on whether in-app billing via the PlayStore is available.
Related
I'm trying to implement the in app billing service in my app (for test-purposes currently). Have tried to check out resources and documentation but I haven't found any on what the status means and for what purpose.
This is what I'm talking about in the console:
If I set it as Active, does it mean that I can only make a real transaction?, where as I want to test only. Kindly help me understand this in detail.
If the status is inactive, it means that the item won't be available for purchase nor for testing. Whereas if the item is active, it means the product is available for purchase for testing purposes(not published on the play-store) and for commercial purposes(published).
Check out this link creating a managed product
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.
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
I have following problem:
My app is downloadable for free but can be upgraded through in-app-purchase.
The purchase is working fine. Now I'm checking at startup if the app is upgraded or not in order to initialize the free or the upgraded features.
At default the app assumes that it is NOT upgraded, using a bool which is set to 'false' as default. At startup the app queries the purchased items and sets the bool to true if the upgraded version was purchased.
The problem is that a connection to google play with a service is done asynchronously. That means that at startup is some cases the app is considered as not upgraded although it is, because of the asynchronous service connections which takes too long.
Is it possible to wait for the service connection to finish before checking for the app version?
The docs say that with API-Version 3 the purchase informations are cached locally. Isn't it possible to retrieve the cached informations without using an asynchronous service connection?
Of course I could save the upgrade-informations by myself using preferences but if the user
likes to install the app on another device an upgraded version would be treated as not upgraded.
Did anybody faced the same problem and found a way to solve this issue?
If you look here:
It says "Version 3 API calls will be serviced through cache lookups instead of through a network connection to Google Play".
Meaning, Google Play Service optimizes your requests to return cached results (if any available), although this happens asynchronously because of Inter Process Communication between your app and Google Play Services.
I would suggest when your app is installed on a new device. Start as if the free version is loaded while checking previous purchases in the "background" and gracefully upgrade if the user already owns an upgrade.
As Google stores your purchases of Android apps, I was wondering if they are somehow offering a webservice that can be used to check if a certain app has been purchased. To me this seems the most secure way of distinguishing free and paid users from within my app.
It would not only defeat piracy, but would also allow for managing a database of legally registered users, by a one-time check through this service.
What are your solutions to this matter?
Google provides a library for you. See here.
Note that this has been compromised in unprotected apps, so you should use something like ProGuard. The link above contains more information.
Finally, keep in mind that the Android Market is not the only app market for Android out there. Amazon has their own DRM.
At this particular moment there is one way to check.
The Android Market authenticates purchases through Google Checkout, but Google has not implemented the Checkout API to synchronize Market purchases. So calls like that check the status of an order do not work. Maybe Google doesn't want dev's to get a hold of customer emails? Either way, I do not know why it does not exist, it seems to me that if Google simply made this service available we could implement a far more superior security system that would better utilize the methods of obfuscation. As it is at the moment, a hacker can simply look for a specific class like the ILicenceService and hook it.
The only way at the current moment is to download a list of current purchases. You can do this using an authenticated http call. The drawbacks to doing this are pretty large though. First, the list it provides only extends back 31 days (so you've got to make sure you keep everything). Second, you would have to call and parse at least every ten minutes. Actually faster than that, most users want to play their games when they buy them. Third, if your service, or server goes down, that is precious information that is being requested almost instantly by your users.
I don't know how many dev's are currently utilizing this process, I considered it, but am just going to keep complaining to Google for a better method.
You could piggy back off of the LVL. I have just implemented a similar system.
Perform Check via LVL.
On success make a post to your own web server and store whatever details you need, i.e. DeviceID etc.
You could also perform checks to your own server even when the LVL check fails and allow things like trial periods etc.