I have users that have purchased items (SkuType.INAPP) from my application.
In the previously recommended implementation of in app billing the IabHelper.QueryInventoryFinishedListener would return an Inventory that contained no items that they owned.
In the new Play Billing Library I use the following method:
Purchase.PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
Assuming the response code is successful, I query:
purchasesResult.getPurchasesList()
For this small percentage of users, the list is empty.
As a fail-safe, for negative results above, I continue to try:
mBillingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP, this);
The onPurchaseHistoryResponse(final int responseCode, final List<Purchase> purchasesList)
successfully details the purchase.
Perplexed by this, I asked the affected users running both implementations to confirm the following:
They only had one Google account on the device
The one account received the purchase confirmation email
This is the only account option in the Google Play Store app
The Google Play Store app 'order history' detailed the purchase
Taking suggestions from this thread, I asked them to:
Clear data & cache from the Google Play Store app
Reboot
Log back into the Google Play Store
Open my app again
Despite the above, still, only queryPurchaseHistoryAsync correctly identified that their account owned items.
Question: Why?
The Billing Library implementation states:
queryPurchaseHistoryAsync() returns the most recent purchase made by
the user for each SKU, even if that purchase is expired, canceled, or
consumed. Use the queryPurchases() method whenever possible as it uses
the local cache, in preference to the queryPurchaseHistoryAsync()
method.
I have confirmed in my Order Management, that the purchases are valid and have not been cancelled. I do not consume them, therefore they cannot expire.
Now I have a problem
Given that for these users I can only identify their purchases in the above way, I am forced to implement this fail-safe for all.
Apart from being frustrated by the necessity of having to do this, it wouldn't seem like too much of a problem, until you read again:
queryPurchaseHistoryAsync() returns the most recent purchase made by
the user for each SKU, even if that purchase is expired, canceled, or
consumed
Therefore, in my standard implementation, I need to check that this purchase remains valid.
Unfortunately, the Purchase object has no such method to check its validity.
So, I probably need to perform an external check.
All when my app starts and my logic decides whether or not to show ads.
Help. Please.
Related
In the Android Developer Console, I saw this message
Resubscribe isn't currently available for your users because your app
does not use Billing Library 2.0 in all active APKs
But, I was puzzled. Currently, we are using Billing Library 1.2.2.
This is how we decide, whether to show subscription button to user or not.
During queryPurchases, We will perform List<Purchase> purchases = PurchasesResult.getPurchasesList(). If the subscription's SKU is not found in purchases, we will show the subscription button. If not, we will hide the subscription button.
If this is a new subscriber, there will be no SKU in his purchases. Hence, he will see the subscription button, and allowed to subscribe again.
If this is a previous subscribed, and already cancelled user, we assume there will be no SKU in his purchases too!!! Hence, he will see the subscription button, and allowed to subscribe to the same sku again.
As you can see, even with old Billing Library 1.2.2, we are still allow user to resubscribe to same SKU again, to his previous cancelled subscription.
If that is so, why there is a special feature called "Resubscribe" (https://developer.android.com/google/play/billing/subs#resubscribe) in Billing Library 2.0? How does it different from our current Billing Library 1.2.2 flow?
To be perfectly honest, there isn't any enormous difference between resubscribing to the same sku with old and new approach using the resubscribe feature. Why? (None of these are solidly tested by the way, this is a logical explanation.)
In both cases, the subscription elements will stay the same and they will both return in queryPurchases method as long as the subscription is active.
This relates to the queryPurchases method, I haven't tested this yet, but it is possible that, in the old way, multiple purchases with the same sku might return, which may create a confusion. After resubscribing while canceled subscription is still active, the queryPurchases method will return only 1 subscription, causing queryPurchaseHistoryAsync method to return nothing. In the old way, if queryPurchases method returns only 1 purchase after getting a subscription over a canceled subscription with same sku, queryPurchaseHistoryAsync might actually return the old subscription that was canceled for, even if it was still active in a canceled state.
On Google Play Developer API side, there is a method that links a purchase token to the older one. For this, the Purchase.getLinkedPurchaseToken() function might return different values after subscribing to same sku, between the old way and new way. I presume, resubscribing to an active canceled subscription with the old way will generate a new purchase token, and getLinkedPurchaseToken(). This does not affect the BillingClient itself since there is no getLinkedPurchaseToken() method but logically this should be the result.
Bottom line: The only difference I can say is that resubscribing with the new way might reduce confusions, while on the old way there might be unnecessary data that you want to avoid from. As long as you have a subscription that's returned from queryPurchases where it matches your sku, you can consider that the user has an active subscription.
https://developer.android.com/google/play/billing/subs#resubscribe
Users can resubscribe in a number of different scenarios:
Before the subscription has expired, users can repurchase the same subscription in your app. This generates a new subscription and
purchase token.
Before the subscription has expired, users can restore the subscription in the Google Play subscriptions center. This keeps the
same subscription and purchase token.
After the subscription has expired, users can also repurchase the same SKU up to 1 year after expiration through the Google Play
subscriptions center. This generates a new subscription and purchase
token.
More details are provided in the release notes of 2.0 here: https://developer.android.com/google/play/billing/release-notes
I think the API is for subscriptions made outside your app (for example, from Google Play Subscriptions Center (mentioned in bullet point 3), or at a physical store).
Based on your question, it seems that you already handle the other scenarios regarding a user not having a subscription or having cancelled their subscription - but these flows apply to within the app, not outside. To gracefully handle purchases made outside the app you must use 2.x or higher.
The ability to process subscriptions outside the app, such as Google Play subscription Center or a physical store is not available in 1.x. It is available from 2.x+
Presuming you don't confuse the flow of subscriptions , that will remain as it is. Additional features have been added to Google play billing . As we talk , Google play billing 3.0 is up and ready. Follow this link
https://developer.android.com/google/play/billing/release-notes#3-0-0-summary-changes
Resubscribe feature will make restoring subscriptions easier.
There are scenarios where managing subscription should be made easy .
Subscription Restore and Resubscribe
Lets assume a user for some reason cancels the subscription renewals and before the subscription is expired and wants to subscribe it again. Now if user wants to resume it again. This newly feature will allow to resume the subscription as if they were never cancelled. For this the condition is that the subscription must not have expired . If it has expired , then here it is . Users will have to resubscribe instead. To resume any paused subscription users will need to resubscribe , you will have to treat this as you have been treating it.
Account hold feature
A user subscribes and sometimes users are unable to pay their subscription, whether due to financial woes or an expired credit card. In these cases, developers can initiate an Account Hold instead of cancelling it . This will allow users to manage the subscriptions until they fix the payment at their end.
Developers with existing apps will need to integrate Account Hold and Subscription Restore by November 1st. Unless they opt out, they’ll also need to integrate Subscription Pause and Resubscribe. If they fail to do so by the deadline, future updates may be rejected, thus delaying the launch of new features, bug fixes and metadata.
Most of our purchases keep getting canceled after 3 days 30 minutes exactly, which I presume is the result of pending purchase state. However, there is so little documentation about how to handle pending purchases exactly. And, for some reason, even though I am a tester myself, I cannot test it because there is no "Slow card" option on purchase methods. Also, we do not have a backend server to well, back us up.
This is the explanation I've found from the official documentation:
void handlePurchase(Purchase purchase) {
if (purchase.getPurchaseState() == PurchaseState.PURCHASED) {
// Acknowledge purchase and grant the item to the user
} else if (purchase.getPurchaseState() == PurchaseState.PENDING) {
// Here you can confirm to the user that they've started the pending
// purchase, and to complete it, they should follow instructions that
// are given to them. You can also choose to remind the user in the
// future to complete the purchase if you detect that it is still
// pending.
}
}
Look at the explanation on the PENDING state. What does "To complete the purchase, they should follow instructions that are given to them" mean? What are these instructions exactly? Do we need to redirect the user to Google Play or what? It is not specific about what to do and is bugging me out because purchases are getting cancelled for no reason, or for this reason. How does one complete a pending purchase? There is nothing about it, or I cannot find it, hence I ended up here.
I hope you can help me figure this out. Thanks.
I agree the documentation is poor, especially since the one time you want to know exactly what's happening is when you're handling other people's money!
It looks like a 'slow card' transaction is actually a 'pending purchase', something Google have been rolling out in 2019. Here's the probable flow...
In your app the user taps 'Buy now'
They see the Google checkout overlay
They choose "Pay at Freddina's Grocery, Accra" (a local store that's signed up to deal with Google pending purchases)
Google checkout displays a code to show to Freddina
User pays Freddina in cash and Freddina processes the payment using that code
10 mins or so later, the purchase update will land in your app
As for handling unpredictably timed update events, we have an app with a handlePurchaseUpdated method listening out from the moment the app starts, and makes changes based on the Purchase object that comes with it. Here's an example flow:
We listen for purchase updates as soon as the app starts up
User makes a slow purchase
We get a purchase update
The passed Purchase object has purchase state PENDING
We tell the user that we'll notify them when the purchase is complete
At some point in the future (e.g. next day after an app restart) the purchase update comes in
If the purchase state is now PURCHASED we finalise the purchase and tell the user
Note: Ours is a ReactNative app, not native java, but the flow should be the same.
It's complicated to implement because you have to pick your time to make your purchase changes and display the purchase result at a time that makes sense, not necessarily when the purchase update arrives 5 secs after startup (or any other weird time). And it gets more complicated if there are errors during your grant-entitlement or acknowledgement steps, urgh.
Also, you might not be seeing the 'slow card' tester option because you didn't allow it?
Update for 2022: Personally I feel the documentation is still so poor, and the complexity so great, that I would recommend using a service like IAPHUB to manage iaps and subscriptions.
It sounds like you are not acknowledging the purchase. See below from the Google Play Billing documentation:
If you use the Google Play Billing Library version 2.0 or newer, you must acknowledge all purchases within three days. Failure to properly acknowledge purchases results in those purchases being refunded.
According to Google's documentation (https://developer.android.com/google/play/billing/integrate#pending), you need to call enablePendingPurchases() and then your PurchasesUpdatedListener will be notified when the state changes from PENDING to PURCHASED.
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'm not specifically looking to have the option to refund a user for an in app purchase of a consumable, but just in the odd case that a user asks for a refund, either through google, or through the developer of an in app purchase of a consumable.
There does not seem any way of handling refunds through the In App Billing api V3. All documentation points to V2 of the api using broadcast receivers. This does not help me in least bit.
So how are refunds handled in the In App Billing v3 api? I'm assuming for a non consumable, the refund is processed, and then google handles the processing through their backend, and then when a getPurchaseState() is conducted, it would return a value of 2 (refunded). If so, this works great for a NON CONSUMABLE product.
When a getPurchaseState() call to a consumable purchase is made, it returns null. That's assuming if the purchase was previously successful, and that the item was consumed and provisioned. I figure I could store the purchase items locally within the app, but then there is still no way of receiving a refund notification for that product. I can check for purchase state, but again, it will return null, as long as it was consumed and provisioned already.
A slight workaround I can think of right now, is to not consume the purchase on provisioning. So that it remains "unconsumed" according to google, and remains in their database. Now, if a refund is requested, I am assuming a call to getPurchaseState() for that consumable will return refunded, thus the application logic can then subtract/deduct the provisioned consumable from user's inventory. If a user would want to repurchase that consumable again, then before the IAB api call is made for the purchase, get the purchaseState() and/or hasPurchase(), and if there is already a purchase there, consume that item without provisioning, this will let the user repurchase that consumable. Only issue with this workaround, is that if A user wants to re purchase the same consumable product, as soon as the select the option to repurchase, the item must be consumed regardless of whether they successfully repurchase the product or not. So if the user does not complete the purchase, the product is already consumed, and I end up back where I am now with inability to check for refund state on the consumable.
I can't think of any other way to do a refund for a consumable in app product, and that is what I ask here. Is the way I thought of sufficient, or is there a proper way to handle refunds using IAP v3 on consumables?
I was thinking I have 3 options:
Don't offer refunds for consumables, no/little exceptions, but the issue with this is if a user requests refund through google, and not through developer, leading to option 3 below)
Find a workaround (either properly through the api, or with my proposed workaround)
Offer refunds, but have no application logic to subtract/deduct the consumable. In this scenario, users could end up taking advantage of free purchases.
I know this is a super late response to this issue, but it took me quite some time to track down info on this issue, so hopefully it can help someone else out. There are a few ways of doing this... In the Google Play Developer console you can click on the left nav saying "Order Management" which will bring up a list of all recent orders though your app. If you need to refund one you can click on it, or multi select them and refund them in this view. Just a heads up... you will most likely need permissions from your account admin to see this view and refund users.
Your other option is though the API docs here. You will need to set up your API account though the Android console, which I will say is a massive pain and not clear. But once that's set up and your "Server Applications" is set up you will need to make sure it has proper permissions as well. Then you should be able to use a library such as this to do the heavy lifting for you, because from what I've read the JWT auth process though the google API is not super straightforward, and their docs are a mess. I hope this helps someone and comment if you have questions... I will do my best to answer.
This same API also can be used to find items that were purchased by users and then refunded, so you can remove these items from those users' apps. Also you can do server side purchase validation through this as well, which is highly recommended, because rooted phones can inject fake purchase requests making your app believe that it's hearing back from the google play store, but in reality it's not. You then send back to your server the purchase token and SKU of the item being purchased and then on your server you just run this. This will give you back a 400 error if it's invalid, and a 200 with a some JSON data if it's a successful purchase.