I am using Android anjlab in-app billing Library, and have noticed this behavior:
When a purchase (they are all subscriptions) is made and the card is charged right away, everything works fine:
the method onProductPurchased() gets called, and I can then call my server to give the user Premium access to my app.
But, looking into the transaction logs at the Merchant tools (Google console), I noticed that a lot of the times, the credit cards are not immediately charged - they stay in a chargeable state for some hours to a couple days, when they actually move to charged.
The problem is, when the payment goes into this chargeable state, the onProductPurchased() method does not get called and thus neither my server.
In these cases, my users get charged (hours after having bought the subscription in my app) but they never get the Premium access as I can't identify the payments in my app (just by looking into the merchant logs)!!
How to fix this?
Do I need some sort of check running on my app, to verify if the user bought the subscription??
In general, your app should always check, whenever possible, like at startup, if the user has valid receipts in their wallet, that haven't been converted to an upgrade yet.
This is useful also in case of errors during the purchase process, or to restore the purchases when a user changes his phone, or uninstalls and reinstalls the app.
It's been a while since I last dealt with this stuff, but I think that, according to the Google Play Policy, you are actually required to provide a restore mechanism.
Related
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.
I do а premium upgrade of applications (ad free version), and use for this In app purchase v3 http://developer.android.com/google/play/billing/billing_integrate.html. Type of purchase is not controlled by Google. The main feature, I just did not consume the purchase, and a check make when you run the application, and advertising was removed. I`m testing this using the inAppid "android.test.purchased". But after some time (day, week, month) buying disappears and reappears advertising. Therefore, how to make a premium upgrade to be able to do the checking after purchase, without using your own server. SharedPreferences and SQLite are not suitable for this, because after you uninstall an application, data is lost, and purchase too. What type of purchase is better to choose, so that it is preserved for user forever. Need to make binding to Google Play account. Please advise how best to do so as it is very serious.
You are going to have to use your own server. The simplest option might be to use something like Firebase instead of writing your own server code & DB.
It is very strange that your purchase has disappeared after some time. Remember that test purchases will be cancelled by Google after 14 days.
If product was not consumed it will always restore and can't be purchased twice.
A good option is to have restore button somewhere in your app, forcing restore and providing feedback from any errors which may occur.
In my opinion you don't need a server or event local storage. Just check for purchase every launch
I've gotten IAB v3 working and I was able to make a purchase for a managed item. However, to continue developing and testing I wanted to refund the purchase so I could try making the same purchase again. I logged into my Google Checkout Merchant account and successfully refunded the purchase. However, the app still thinks that the user has the item purchased. It has already been several weeks since I made the refund so its not a delay issue.
Basically, in my QueryInventoryFinishedListener implementation, inventory.hasPurchase(SKU_REMOVE_ADS) always returns true, even after the refund (SKU_REMOVE_ADS is the SKU for item I'm selling). I was expecting it to return false after the refund had been processed.
If you look at the 'Handling Refunds' section of the IAB reference, it says that your app needs to be listening to the IN_APP_NOTIFY messages. However the documentation for IN_APP_NOTIFY is specific to v2 of in-app billing. It doesn't seem to be something that's available in v3 since its not mentioned anywhere in the v3 reference nor can I find any reference for it in the sample TrivialDrive app that they are using to demonstrate IAB v3.
So does v3 of IAB support refunds/cancelling purchases? Has any one tried it and got it working?
There really is no difference between a consumable item and a non-consumable item so far as Google Play is concerned; this distinction is entirely based on what you implement within your app. So even though the SKU you are testing is intended to be non-consumable (e.g., a permanent premium upgrade), for testing purposes, you can treat it as a consumable and consume it, so that it can be purchased again.
A convenient approach is to set up a temporary testing menu within your app (e.g., by adding a menu item during testing onto your app's main options menu), and then to have that item's handler invoke the consumeAsync() method of your IabHelper instance for the SKU that you want to test buying again. This will consume the item and thus make it immediately available for repurchase from your device.
You will, of course, still want to refund the purchase from Google Checkout, so that you won't be spending your own money just to test your app.
I would add that consumeAsync() also seems to work just fine for resetting the test SKU android.test.purchased, if you are testing using such static values.
Regarding the updating of purchase state to reflect a refund, I have personally experienced (and there are many similar reports posted by other developers) that manually initiating a refund via Checkout (e.g., for a test purchase from the TrivialDrive app) takes days to result in a change to the purchase state of the product (to INAPP_PURCHASE_STATE_REFUNDED).
(Knowing that misery loves company, some of those additional reports can be found on this discussion thread:
https://plus.google.com/+AndroidDevelopers/posts/R8DKwZDsz5m)
At least part of this is due to Google Play's caching of purchase data on the device.
In my experience, re-booting a device can sometimes cause Google Play to refresh its cache from the GP servers. So it may be that changes due to cancellation or refunding of an order via Checkout could also be detected after a reboot.
It might seem that such a long turnaround period would do you no good, since you can't know when users will reboot. But then again, you know that every device will, eventually, get rebooted, and so if your concern is that a user who receives a refund should eventually be blocked from using the refunded IAB product, a few days of delay may not matter much, so long as it eventually happens.
Of course, remember that this notion that cache will refresh on a reboot is undocumented and anecdotal (like quite a number of IAB3 and TrivialDrive behaviors, thus far). Folklore, they call it.
Another thing that triggers an update is when the user attempts to purchase the product. As soon as the purchase is launched, the system has to be sure that the product is not already owned, and so it updates the Google Play cache. In my personal experience, this has always occurred. But again, this is not a very practical way to check for a refund, because that would involve showing the purchase dialog unbidden, and also an error message that tells the user "you already own this, " (if they do own it).
Where this does come in handy is when the user pays for an IAB item on one of her devices, and then attempts to access that item on a different device that is owned by the same account as was used to buy it. The purchase information in that case has very often not yet been cached. But you can just put a little note in your purchase dialog that if the item has already been purchased, attempting a re-purchase should make it available on the present device at no additional charge. Sometimes it takes two (user-initiated) purchase attempts to finally get the IabHelper.BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED response. Yes, a bit klugy, but I think in human terms it will work with appropriate highlighting of the message and apologetic wording of the confirmation dialog telling them that they own the item, etc. :-) ).
As a practical matter, you can see how Google might not want every instance of every IAB app in the world to access its servers every time the app's purchase data is being accessed, especially given that they are advising developers to do a check for what has been purchased each time the app is started. It's also a performance issue for your app - that's what caching is all about. So you need to be aware of the triggers for updating the cache, and I haven't found a single place where this is officially documented (except, we presume, in the code). So get ready to put your hands out in front of you and start feeling around in the dark.
For some additional information regarding Google Play buffering, see this page:
Under What Conditions are In-App Billing Version 3 Server Changes Made Available on Client Devices?
I would note that in your post's code snippet you are calling inventory.hasPurchase(SKU_REMOVE_ADS), but that will only tell you if the purchase is in the list of purchases returned in the inventory object; it will not tell you the state of the purchase for that SKU. I know that this is the approach used by the TrivialDrive app, but that app is not dealing with refunds and cancellations. To detect refunds and canceled orders, you'll need something like this:
Purchase removeAdsPurchase = inventory.getPurchase(SKU_REMOVE_ADS);
if(removeAdsPurchase != null) {
int purchaseStateForRemoveAds = removeAdsPurchase.getPurchaseState();
if(purchaseStateForRemoveAds == 1) {
//Do cancelled purchase stuff here
}
else if(purchaseStateForRemoveAds == 2) {
//Do refunded purchase stuff here
}
}
The good news about refunds and canceled orders is that both are, AFAIK, entirely at the option of the developer. So, if you find that users who get these are able to continue using your app for a long interval thereafter, and if you find that lots of users are taking advantage of this, then you can decide if you want to continue providing the refunds in all cases. My best guess is that it will not be a problem; even if some user who gets a refund gets to use your app for a while after that, that doesn't seen like a very big deal.
It is for testing that you need the ability to re-try a purchase very rapidly, and using consumeAsync() definitely works for that purpose.
I will suggest you to use static product ids while your app is in development phase.
Now make sure you are testing the app with same Gmail Id for which you have refund? To test the refund scenario I think you can use android.test.refundedas product id.
If this is not working then you can first check total purchased item(s) and Available item(s) in google play at first launch of your app and if you are getting same product id in both the calls(which should not be the case if this is the case please report this bug to google) then make api call to make same item as consumed.
Since posting this question, its been brought to my attention that I need to call getPurchase(...).getPurchaseState() and check for its value. Possible values are 0 (purchased), 1 (canceled), or 2 (refunded).
However, in my case its still retuning 0 (purhcased) even though the item is refunded. I'm posting this information here in case it helps someone else.
I did implement the v3 api of google play in app purchase in my application.
Everything goes right but the strange thing is when i am testing it using my test account and canceled the order from my merchant account. in my app it still shows the item is owned by test user and he is able to use all premium functions.
Any suggestions on how can i get rid of this.
i tried it using complete uninstall and reinstall.
I believe what you may be seeing is Google Play's device-side cache. If the user reboots his phone it will clear it, otherwise you must wait for Google Play to update it's cache from the Google Servers. I have the opposite problem sometimes... user purchases, but for some reason Google Play gets an invalid response from Google's servers and caches a not purchased result for a couple of days. Rebooting the device, and in severe cases clearing the Google PLay data in the applications list causes Play to resync everything from Google.
If you have bought before by your account and it`s a non-consumable item, when you try again to buy the server will send you a status 'item bought'. (like a restore trasaction)
Else check all fields of the response from server.
Check the responde code:
http://developer.android.com/google/play/billing/billing_reference.html
BILLING_RESPONSE_RESULT_USER_CANCELED 1 User pressed back or canceled a dialog