android billing:4.0.0 - queryPurchases(INAPP) and purchase.getSku()
Asked Answered
L

4

50

I refresh to android billing version 4 and 2 things are not working anymore.

First I have this:

else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            Purchase.PurchasesResult queryAlreadyPurchasesResult = billingClient.queryPurchases(INAPP); // deprecated
            List<Purchase> alreadyPurchases = queryAlreadyPurchasesResult.getPurchasesList();
            if(alreadyPurchases!=null){
                handlePurchases(alreadyPurchases);
            }
        }

queryPurchases is deprecated.

Second I have this:

void handlePurchases(List<Purchase>  purchases) {
    for(Purchase purchase:purchases) {
        //if item is purchased
        if (PRODUCT_ID.equals(purchase.getSku()) && purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED)
        {
            if (!verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature())) {
                // Invalid purchase
                // show error to user
                Toast.makeText(getApplicationContext(), R.string.plus_error, Toast.LENGTH_SHORT).show();
                return;
            }

getSku() was working, but now it is mark as Cannot resolve method getSku() in Purchase

Any ideas how to solve this issues?


From docs:

Summary of changes
Added BillingClient.queryPurchasesAsync() to replace BillingClient.queryPurchases() which will be removed in a future release.

Added Purchase#getSkus() and PurchaseHistoryRecord#getSkus(). These replace Purchase#getSku and PurchaseHistoryRecord#getSku which have been removed.

But I don't know how to apply this new commands in my code above.

If I change getSku to getSkus my if if (PRODUCT_ID.equals(purchase.getSkus()) && purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) will say that it is always false. And I have no idea how to use queryPurchasesAsync(), need 2 params now.

Thanks.

Luffa answered 19/5, 2021 at 16:57 Comment(5)
I'm trying to figure out these changes as well. But isn't your problem perhaps that you compare a String value PRODUCT_ID with a List<String> purchase.getSkus()? Perhaps try if (PRODUCT_ID.equals(purchase.getSkus().get(0) &&.....) instead?Lory
getSkus() returns an ArrayList<String>. Perhaps loop through each of them and do your PRODUCT_ID.equals() ? It is irritating to have to change blocks of code because of the changes. Would have been ok if it's just a method/param name change. They had to change billingClient.queryPurchases() as well.Oration
@Oration Thanks, I will try. Yes, new version, more work to do.Luffa
They killed billing 2.0, those b@stards )) If only they updated docs! It's July 2021, still undocumentedAllembracing
Dear @RGS, can you share some code about verifyValidSignature()Candlemaker
L
45

As I mentioned earlier in a comment you are comparing a String to a List object, but as chitgoks said it is ArrayList<String> and not List<String> as i assumed. I'm not sure if you would ever get more than one sku-string (since you probably don't order multiple things at the same time?) but either look trough them all to be sure or take a chance and compare PRODUCT_ID with only purchase.getSkus().get(0).

The new async call for purchases seems to require only small changes.

Example of old way to do it:

Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
doSomethingWithPurchaseList(result.getPurchasesList());

And this would be the new way to do the same:

billingClient.queryPurchasesAsync(BillingClient.SkuType.SUBS, new PurchasesResponseListener() {
        @Override
        public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List<Purchase> list) {
            doSomethingWithPurchaseList(list);
        }
    });
Lory answered 22/5, 2021 at 21:30 Comment(11)
Thanks for your answer! I'll try. I am still using version 3 in the meantime.Luffa
As developers attempt upgrading billing to the new version 4.0.0 I expect this post will get a lot more notice. The new documentation is incomplete as usual, so we are on our own for updating missing details.Cotidal
@Cotidal for sure! I think all developers are in the same situation.Luffa
As Android Studio does not seem to convert the above Java snippet into Kotlin code, in Kotlin the new way would look like billingClient.queryPurchasesAsync(BillingClient.SkuType.SUBS, PurchasesResponseListener { billingResult, list -> doSomethingWithPurchaseList(list) })Julietjulieta
has anyone noticed that onQueryPurchasesResponse returns expired subscriptions as valid in the emulator? This does not seem to happen on device, but very strange and concerning. It just keeps returning expired subs long after they are no longer listed in the subscription history of the Play Store..Tenstrike
not always btw, but for an unusually long time after they should be gone...Tenstrike
What is the best option if you queryPurchasesAsync for both INAPP purchases and SUBS?Peppercorn
@Panther: I don't know if there is any other way than to make two calls like above, one for SUBS and one for INAPP. I am no expert though, I only figured this out for myself and for my app and then shared my solution here so there could be a solution to get all purchases in one call. There should be no problem calling twice though unless you want to compare subs and inapp purchases or somethingLory
still billingclient.querypurchasesasync returns error billing service is disconnected. But billing service is connected and it passes the if(billing.isready) any clues??Oviform
queryPurchasesAsync is deprecated with com.android.billingclient:billing:5.0.0Karakorum
queryPurchasesAsync is still available, but you need to pass the QueryPurchasesParams as a parrameter insteadSuperannuation
B
33

getSkus returns an ArrayList<String>. Please use contains as below.

purchase.getSkus().contains(YOUR_PRODUCT_ID.toLowerCase())
Billye answered 2/6, 2021 at 2:23 Comment(4)
why do we need to use .toLowerCase()??Bush
@Bush Maybe to make the operation case-insensitive?Zoologist
oh my... google as always, everything complicates and complicates...Exist
List<String> productIds = purchase.getProducts(); // Use getProducts() instead of getSku() ~~~ then ~~~ productIds.contains(PRODUCT_ID)Intrude
C
16

Posting this a year in. As with billing 4.0.0, the documentation 'Integrate the Google Play Billing Library into your app' as of billing 5.0.0 is buggy and incomplete, although possibly not as bad as a year ago. Now we are dealing with ProductDetails instead of SkuDetails objects. Also note the following corrections in documentation:

QueryProductDetailsParams queryProductDetailsParams =
QueryProductDetailsParams.newBuilder()
    .setProductList(
        ImmutableList.of(
      

should be:

QueryProductDetailsParams queryProductDetailsParams =
QueryProductDetailsParams.newBuilder()
    .setProductList(
        ImmutableList.from,(//'from' instead of 'of'      

...

BillingFlowParams billingFlowParams =
BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(
        ImmuableList.of(
          

should be:

BillingFlowParams billingFlowParams =
BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(
        ImmutableList.from(//'ImmutableList.from' instead of 'ImmuableList.of'
           

...

billingClient.queryProductDetailsAsync(
queryProductDetailsParams,
new ProductDetailsResponseListener() {
    public void onProductDetailsResponse(BillingResult billingResult,
            List<ProductDetails> productDetailsList) () {
       

should be:

billingClient.queryProductDetailsAsync(
queryProductDetailsParams,
new ProductDetailsResponseListener() {
    public void onProductDetailsResponse(BillingResult billingResult,
            List<ProductDetails> productDetailsList) {//no extra parens
       

...

//Incomplete
billingClient.queryPurchasesAsync(
    QueryPurchasesParams.newBuilder()
        .setProductType(ProductType.SUBS)
        .build(),
    /* purchaseResponseListener= */ this
);

// PurchaseResponseListener implementation.
public void onQueryPurchasesResponse(BillingResult billingResult, List<Purchase> purchases) {
    // check BillingResult
    // process returned purchase list, e.g. display the plans user owns
}

The most disappointing part of documentation IMHO. It just gives clues and the 'this' is missleading, you will get an error with the suggestion to cast purchaseResponseListener to it. An actual implementation would be:

billingClient.queryPurchasesAsync(
                QueryPurchasesParams.newBuilder()
                        .setProductType(BillingClient.ProductType.INAPP)//or SUBS
                        .build(),
                new PurchasesResponseListener() {
                    @Override
                    public void onQueryPurchasesResponse(BillingResult billingResult, List<Purchase> purchases) {
                         if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK&&purchases != null) {
                            for (Purchase purchase : purchases) {
                                handlePurchase(purchase);
                            }
                             }
                    }
                }
        );

In your code, 'purchase.getSku' won't be recognized as the method was 'purchase.getSkus()'. This is depreciated now anyways and you would use the following to pull the product id (sku) off. Likely you will have just one product for a puchase object, although users can now buy multiple products with a single purchase:

purchase.getProducts().get(0)
Cotidal answered 25/5, 2022 at 21:10 Comment(2)
This was exactly what I needed to convert from V3 to V5. Thank you so much for the help.Giselle
Do you know under what situation a single purchase has multiple products?Ergot
S
0

So you all know new billing library has new feature

  1. every thing will be in background thread so do not do change anything on main UI during acknowledgement of purchase and restoring purchase.

  2. if you are giving consumable purchase then user can now buy same sku in more quantity in one purchase so write logic accordingly. use getQuantity() function.

  3. to restore non consumable.

billingClient.queryPurchasesAsync(
  BillingClient.SkuType.INAPP,
  new PurchasesResponseListener() {

    @Override
    public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List < Purchase > myPurchases) {

      if (!myPurchases.isEmpty()) {

        for (Object p: myPurchases) {
          final Purchase purchase = (Purchase) p;
          if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED && purchase.getSkus.contains("sku here") {
              handlePurchase(purchase);
            }

          }
        });
    }
  }
)
Superstructure answered 24/8, 2021 at 15:49 Comment(1)
using this method but billing service always returns billing service disconnected error.Oviform

© 2022 - 2024 — McMap. All rights reserved.