11

I am trying to create a restore purchase system. I want, the user can reach its bought products whichever device he/she logged in. So I use "queryPurchaseHistoryAsync()" method when app launches. My problem starts here.

With new implementation of Google, On contrary to the documentation, queryPurchaseHistoryAsync() parameters changed. Now it takes list of PurchaseHistoryRecord objects as parameter instead of list of Purchase objects.

Android studio can not resolve the method stated in the documentation. With new queryPurchaseHistoryAsync() I couldn't find anyway to check purchases state.( if it is purchased, canceled or pending). That I was able to do with Purchase object with "purchase.getPurchaseState()" method.

Documentation of queryPurchaseHistoryAsync()

billingClient.queryPurchaseHistoryAsync(SkuType.INAPP,
                                         new PurchaseHistoryResponseListener() {
    @Override
    public void onPurchaseHistoryResponse(BillingResult billingResult,
                                          List<Purchase> purchasesList) {
        if (billingResult.getResponseCode() == BillingResponse.OK
                && purchasesList != null) {
            for (Purchase purchase : purchasesList) {
                // Process the result.
            }
         }
    }
});

My implementation

implementation 'com.android.billingclient:billing:2.0.3'

queryPurchaseHistoryAsync() Method in my app

billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP,
                new PurchaseHistoryResponseListener() {
                    @Override
                    public void onPurchaseHistoryResponse(BillingResult billingResult, List<PurchaseHistoryRecord> purchaseHistoryRecordList) {

                        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                                && purchaseHistoryRecordList != null) {

                            for (PurchaseHistoryRecord purchaseHistoryRecord : purchaseHistoryRecordList) {

                                HandleOldGetting(purchaseHistoryRecord.getSku());
                             }
                        }
                    }

Release Note of Google(05-2019):

"To minimize confusion, queryPurchaseHistoryAsync() now returns a PurchaseHistoryRecord object instead of a Purchase object. The PurchaseHistoryRecord object is the same as a Purchase object, except that it reflects only the values returned by queryPurchaseHistoryAsync() and does not contain the autoRenewing, orderId, and packageName fields. Note that nothing has changed with the returned data—queryPurchaseHistoryAsync() returns the same data as before."

But neither release note nor documentation state how to check Purchase State with PurchaseHistoryRecord.

Thank you for reading this, any help is appreciated.

Eren Tüfekçi
  • 2,463
  • 3
  • 16
  • 35

1 Answers1

2

So far, I have been using queryPurchases() to restore purchase automatically as it does not require any networking.

Google play app's cache related to account is updating for all devices. In many cases you won't need call to queryPurchaseHistoryAsync call for restoration.

As stated in @bospehre comment. It has drawback as it depends on the cache. So we still need to check purchases situations and restore them with network call.

For queryPurchaseHistory Async call, we can get the purchase sku and token. If you are using server to hold subscription datas as Google recommends. You can check this subscription's situations via your server.

Here is an example for restoring the latest subscription of the user.

billingManager.billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.SUBS) { billingResult, purchaseHistoryRecords ->
      
           if (purchaseHistoryRecords != null) {
                var activePurchaseRecord : PurchaseHistoryRecord? = null
                if (purchaseHistoryRecords.size > 0) {
    
    // Get the latest subscription. It may differ for developer needs.
    
                    for (purchaseHistoryRecord in purchaseHistoryRecords) {
                        Log.d(billingLogs, "Purchase History Record : $purchaseHistoryRecord")
        
                        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                            if (subSkuListHelper.getSkuList().contains(purchaseHistoryRecord.sku)
                            ) {
        
                                if (activePurchaseRecord == null) {
                                    activePurchaseRecord = purchaseHistoryRecord
                                } else {
                                    if (purchaseHistoryRecord.purchaseTime > activePurchaseRecord.purchaseTime) {
                                        activePurchaseRecord = purchaseHistoryRecord
                                    }
                                }
        
                            }
                        }
        
                    }
                    
        
                        Toast.makeText(
                            this,
                            "Subscription Purchases found, Checking validity...",
                            Toast.LENGTH_SHORT
                        ).show()
        
        
        // Make a network call with sku and purchaseToken to get subscription info
        
        //Subscription Data Fetch is a class that handling the networking
                        activePurchaseRecord?.let { SubscriptionDataFetch(
                            this,
                            billingManager.billingClient
                        )
                            .executeNetWorkCall(
                                getString(R.string.ubscription_check_endpoint),
                                it.sku,
                                it.purchaseToken
                            )
                        }
                    
                }
                else {
                    Log.d(billingLogs, "Purchase History Record not found size 0") }
        
            }
            else {
                Toast.makeText(
                    this,
                    "Purchase not found",
                    Toast.LENGTH_SHORT
                ).show()
        
                Log.d(billingLogs, "Purchase History Record not found null")
            }
}
Eren Tüfekçi
  • 2,463
  • 3
  • 16
  • 35
  • 13
    Using "queryPurchases()" has an inherent drawback that it is NOT realtime. Purchases made on other devices can take days to appear before a proper sync takes place, or even longer. I'm flooded by user complains because of this. – bosphere Mar 18 '20 at 08:56
  • 1
    that answer is very wrong, queryPurchase is recommended YES if you have internet connection active, i make this: ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); – DjongaNelson Lontrowski Nov 21 '20 at 11:16
  • `queryPurchaseHistoryAsync` updates the local cache on device, then `queryPurchases afterwards` to get the correct info. Use both. – Ethan Allen Jul 16 '21 at 22:06
  • @EthanAllen are you sure? – Тони Dec 14 '21 at 17:38
  • @EthanAllen @TonyLead No, it has been reported that `PurchaseState` (cancelled and expired subscriptions) take longer to update in the local cache. So one cannot rely on the local cache being current for changes to `PurchaseState`, even after a call to `queryPurchaseHistoryAsync`. Follow Google's recommendation to verify purchases. – click_whir Apr 26 '22 at 20:19