How to handle Purchase States with Purchase History Record in new Google In App Purchase
Asked Answered
R

1

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.

Risinger answered 21/9, 2019 at 8:58 Comment(0)
R
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")
            }
}
Risinger answered 21/9, 2019 at 12:10 Comment(6)
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.Metacenter
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();Thoroughbred
queryPurchaseHistoryAsync updates the local cache on device, then queryPurchases afterwards to get the correct info. Use both.Caryloncaryn
@EthanAllen are you sure?Abbatial
@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.Keddah
I confirm calling queryPurchaseHistoryAsync(), even for both INAPP & SUBS types does nothing, at least in billing library v.6...Is there a functional workaround for billing library 6.x? Calling queryPurchaseHistoryAsync() is recommended all over the internet, but it just does not work...Official docs also does not mention anything. Imho this is an obvious bug, reported more than 4 years ago, but google is ignoring it.Kneepan

© 2022 - 2024 — McMap. All rights reserved.