queryPurchases() vs queryPurchaseHistoryAsync() in order to 'restore' functionality?
Asked Answered
M

4

37

I'm using the Play Billing Library in order to trigger and manage purchases which in turn unlocked extra functionality within an app. This part is working.

However, what is the best way to 'restore' purchases. Say for example someone who has bought the app buys a new phone. Logs in to the Play Store, downloads my app and then finds that the payment screen to 'upgrade' is being displayed. iOS has a specific method for this but I'm not aware of one for Android.

My thoughts are to query the Play Store and confirm whether the account has previously SUCCESSFULLY purchased the item, if so then I will call the local upgrade function within the app.

It appears there are two similar methods. But which one should I used in this scenario? Where a user has either wiped their phone or bought a new one?

queryPurchases()? Or queryPurchaseHistoryAsync()?

Matheson answered 26/1, 2018 at 8:30 Comment(1)
The docs have been updated and are much more thorough. See my answer below, and note there is outdated and inaccurate information in the previous answers.Howbeit
C
16

Per documentation queryPurchases uses the Play Store app cache to get the results while queryPurchaseHistoryAsyncactually checks the Purchase AP for the most recent purchases. So, in your case you should check the Asyncmethod.

queryPurchases

Get purchases details for all the items bought within your app. This method uses a cache of Google Play Store app without initiating a network request.

queryPurchaseHistoryAsync

Returns the most recent purchase made by the user for each SKU, even if that purchase is expired, canceled, or consumed.

Also, make sure to check the documentation. It recommends to Cache purchase details on your servers. https://developer.android.com/google/play/developer-api.html#practices

Cither answered 6/3, 2018 at 15:6 Comment(4)
@Sophisticate we can make queryPurchaseHistoryAsync and check every purchase in the loop with Purchases API from Google Developer API. I use it in my case. No need to check purchases from cache.Ceratodus
Careful because queryPurchaseHistoryAsync will return purchases even if they were canceled. From API: Returns the most recent purchase made by the user for each SKU, even if that purchase is expired, canceled, or consumed.Lauretta
@Lauretta yes one should verify all purchases before consuming them or allowing access to features. That includes purchases from queryPurchaseHistoryAsync and purchases made directly with launchBillingFlowHowbeit
Such a bad answer. It doesn't return valid info about current statuses of purchases: developer.android.com/google/play/billing/integrate#history. It's just history. You can't use it to check if purchase was made or not. queryPurchaseHistoryAsync() returns the most recent purchase made by the user for each product, even if that purchase is expired, canceled, or consumed..Brassard
S
25

You should use queryPurchases. That gives you all the current active (non-consumed, non-cancelled, non-expired) purchases for each SKU.

queryPurchaseHistoryAsync won't do what you need because it will only give you a list of the most recent purchases for each SKU. They may have expired, been cancelled or been consumed, and there's no way to tell. Therefore this response can't be used to tell what purchases to apply in your app.

So far as I can see, the only valid use for queryPurchaseHistoryAsync is to provide a user with a list of their purchase history. It's a bit of an oddball.

Note also: queryPurchases is synchronous so in most cases it needs to be run in some kind of background worker thread. I run mine in an AsyncTask.

Sophisticate answered 7/3, 2018 at 22:48 Comment(13)
Or it would be nice if queryPurchaseHistoryAsync could tell you if the purchase is is expired, canceled, or consumed.Lauretta
queryPurchases is not enough. The user tries to purchase, gets ITEM_ALREADY_OWNED so I call queryPurchases but it uses the cache which doesn't have the item.Blanketyblank
@Blanketyblank I didn't get you, you can try my solution.Julissajulita
@Sophisticate While based on my experience I agree with this answer, I was wondering if you have found any documentation that backs this up?Myrticemyrtie
queryPurchaseHistoryAsync: developer.android.com/reference/com/android/billingclient/api/…Sophisticate
queryPurchases: developer.android.com/reference/com/android/billingclient/api/…Sophisticate
@Myrticemyrtie The above two links largely give the same infoSophisticate
@Sophisticate as with many parts of Google's documentation, it's not explicit in what these methods do. Nowhere in the description of queryPurchases does it say it will return currently active purchases. It is described as "Get purchases details for all the items bought within your app", which equally sounds a lot like what queryPurchaseHistory would return. I've read all of this documentation also, but I was wondering if you had found any explicit descriptions or examples of how these two methods should be used.Myrticemyrtie
I thought that querypurchases returns what's cached in the play store app. What if you clean the app cache and storage. Do you get nothing?Jolandajolanta
From Google's documentation, methods of BillingClient need to be called on UI thread. Even if your code works perfectly fine currently, you can't be sure whether it'll be the same in the future.Chrissa
As a backup of @Jenix: "All methods are supposed to be called from the Ui thread and all the asynchronous callbacks will be returned on the Ui thread as well." BillingClientCavalla
@Chrissa @Cavalla check the BillingClient link again, currently it states All methods annotated with AnyThread can be called from any thread and all the asynchronous callbacks will be returned on the same thread. Methods annotated with UiThread should be called from the Ui thread and all the asynchronous callbacks will be returned on the Ui thread as well.Howbeit
@Sophisticate The documentation has been updated. queryPurchaseHistoryAsync is the most useful method, but you have to do the work of verifying the purchase to know its state. Google's instruction is to perform verification on a new purchase made within your app as well, so yes it actually can and should be used to determine what to apply in your app. Also note that AsyncTask is deprecated since your advice as well.Howbeit
C
16

Per documentation queryPurchases uses the Play Store app cache to get the results while queryPurchaseHistoryAsyncactually checks the Purchase AP for the most recent purchases. So, in your case you should check the Asyncmethod.

queryPurchases

Get purchases details for all the items bought within your app. This method uses a cache of Google Play Store app without initiating a network request.

queryPurchaseHistoryAsync

Returns the most recent purchase made by the user for each SKU, even if that purchase is expired, canceled, or consumed.

Also, make sure to check the documentation. It recommends to Cache purchase details on your servers. https://developer.android.com/google/play/developer-api.html#practices

Cither answered 6/3, 2018 at 15:6 Comment(4)
@Sophisticate we can make queryPurchaseHistoryAsync and check every purchase in the loop with Purchases API from Google Developer API. I use it in my case. No need to check purchases from cache.Ceratodus
Careful because queryPurchaseHistoryAsync will return purchases even if they were canceled. From API: Returns the most recent purchase made by the user for each SKU, even if that purchase is expired, canceled, or consumed.Lauretta
@Lauretta yes one should verify all purchases before consuming them or allowing access to features. That includes purchases from queryPurchaseHistoryAsync and purchases made directly with launchBillingFlowHowbeit
Such a bad answer. It doesn't return valid info about current statuses of purchases: developer.android.com/google/play/billing/integrate#history. It's just history. You can't use it to check if purchase was made or not. queryPurchaseHistoryAsync() returns the most recent purchase made by the user for each product, even if that purchase is expired, canceled, or consumed..Brassard
S
9

I know it's a bit late, but I just discovered this myself. Sharing my answer for others to benefit.

So I learned that queryPurchases() is cached locally on your device, but is updated when you call queryPurchaseHistoryAsync()

I discovered from this Stackoverflow answer here

So my solution, is when wanting to restore a purchase on a new device, or a fresh install of my app. Call queryPurchaseHistoryAsync() Then in the callback onPurchaseHistoryResponse() call queryPurchases() and look within the List<Purchase> from the PurchasesResult for the purchase status of any of the user's past purchases.

If there's an expected purchase your app can grant the entitlements of their past purchase.

Sonnier answered 25/5, 2021 at 17:24 Comment(3)
@kralvaredo queryPurchases() is deprecated. I don't think they are guaranteeing the local cache to be current for your process.Howbeit
yes, look at the github issue reported in the post you linked - github.com/android/play-billing-samples/issues/139 The local cache call to queryPurchases() will not immediately reflect things like cancellations even after calling queryPurchaseHistoryAsyncHowbeit
The correct approach as detailed by Google is still to verify purchases and use queryPurchaseHistoryAsync() for this scenario developer.android.com/google/play/billing/security#verifyHowbeit
H
2

The documentation has been updated for the latest versions of BillingClient. queryPurchases() is deprecated. Also note that queryPurchases() only ever returned purchases made by the current device, so that method will not inform your scenario of a new wiped phone. To get accurate information use the async calls.

queryPurchasesAsync() will return all active subscription and unconsumed one-time Purchase objects for the sku type provided. It gets the purchases from the local Play Services cache, so there are no guarantees the cache will contain purchases from another device in your "new phone" scenario, and no guarantees the cache will yet be current in your "wiped phone" scenario.

queryPurchaseHistoryAsync() will make a network request and return the most recent PurchaseHistory object for each sku matching the provided sku type, even if cancelled or consumed.

Also, to perform the upgrade call launchBillingFlow() with the appropriate BillingFlowParams and BillingFlowParams.SubscriptionUpdateParams for the sku you now know to be purchased.

Howbeit answered 11/4, 2022 at 6:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.