17

I am using Google Play Billing Library 2.0.3 (implementation 'com.android.billingclient:billing:2.0.3') and it works fine for the most part, but for some users for the reasons unknown, querySkuDetailsAsync() method occasionally fails with BillingResponseCode.ERROR (6) error code...

Does anyone know anything about it? The reason of this error is not explained anywhere, other than it is a "Fatal error during the API action". Can anyone maybe tell why it might be happening?

edit: I am using the method as it is in the documentation (https://developer.android.com/google/play/billing/billing_library_overview):

public interface QuerySkuDetailsListener {
        void onSuccess(List<SkuDetails> skuDetailsList);

        void onErrorProductsHaveDifferentTypes();

        void onBillingClientError(int error_code);
    }

    public void querySkuDetails(final QuerySkuDetailsListener querySkuDetailsListener, final Product... products) {
        if (products.length < 1) {
            //ERROR_NO_PRODUCTS_TO_QUERY
            throw new NullPointerException();
        }

        String querySkuType = products[0].getSkuType();

        SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
        List<String> skuList = new ArrayList<>();
        for (Product product : products) {
            skuList.add(product.getSku());
            if (!product.getSkuType().equals(querySkuType)) {
                //ERROR_SKU_TYPE_CANT_BE_DIFFERENT
                querySkuDetailsListener.onErrorProductsHaveDifferentTypes();
                return;
            }
        }
        params.setSkusList(skuList).setType(querySkuType);

        billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
            @Override
            public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
                    querySkuDetailsListener.onSuccess(skuDetailsList);
                }
                else {
                    //ERROR QUERYING SKU DETAILS
                    querySkuDetailsListener.onBillingClientError(billingResult.getResponseCode());
                }
            }
        });
    }

Then I use it like this:

        private void initiatePurchaseFlow(final Activity activity, @Nullable final Product oldProduct, final Product product, final JsonObject metadata, final InitiatePurchaseListener initiatePurchaseListener) {

                //Check if billingClient is ready
                if (billingClient.isReady()) {
                    querySkuDetails(new QuerySkuDetailsListener() {
                            @Override
                            public void onSuccess(List<SkuDetails> skuDetailsList) {
                                // Process the result.
                                BillingFlowParams.Builder flowParamsBuilder = BillingFlowParams.newBuilder();
                                flowParamsBuilder.setSkuDetails(skuDetailsList.get(0));
                                if (null != oldProduct) {
                                    flowParamsBuilder.setOldSku(oldProduct.getSku());
                                    flowParamsBuilder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION);
                                }
                                BillingFlowParams flowParams = flowParamsBuilder.build();
                                int responseCode = billingClient.launchBillingFlow(activity, flowParams).getResponseCode();
                                if (responseCode == BillingClient.BillingResponseCode.OK) {
                                    //SUCCESS, OK
                                    ...Rest of the irrelevant code...
                                }
                                else {
                                    //ERROR_IN_LAUNCHING_BILLING_FLOW
                                    dispenseInitiatePurchaseFlowError(activity, initiatePurchaseListener, InitiatePurchaseErrorCodes.ERROR_IN_LAUNCHING_BILLING_FLOW, getBillingClientErrorDescription(responseCode));
                                }
                            }

                            @Override
                            public void onErrorProductsHaveDifferentTypes() {
                                //ERROR_IN_QUERYING_SKU_DETAILS_PRODUCTS_HAVE_DIFFERENT_TYPES
                                dispenseInitiatePurchaseFlowError(activity, initiatePurchaseListener, InitiatePurchaseErrorCodes.ERROR_IN_QUERYING_SKU_DETAILS_PRODUCTS_HAVE_DIFFERENT_TYPES, getBillingClientErrorDescription(0));
                            }

                            @Override
                            public void onBillingClientError(int error_code) {
                                //ERROR_IN_QUERYING_SKU_DETAILS
                                dispenseInitiatePurchaseFlowError(activity, initiatePurchaseListener, InitiatePurchaseErrorCodes.ERROR_IN_QUERYING_SKU_DETAILS, getBillingClientErrorDescription(error_code));
                            }
                        }, product);
                }
                else {
                    //ERROR_BILLING_CLIENT_IS_NOT_READY
                    //Trying to reconnect once          
                    billingClient.startConnection(new BillingClientStateListener() {
                        @Override
                        public void onBillingSetupFinished(BillingResult billingResult) {
                            if (billingResult.getResponseCode() == BillingResponse.OK) {
                                //launching again
                                initiatePurchaseFlow(activity, oldProduct, product, metadata, initiatePurchaseListener);
                            }
                            else {
                            // showing an error dialog
                            dispenseInitiatePurchaseFlowError(activity, initiatePurchaseListener, InitiatePurchaseErrorCodes.ERROR_BILLING_CLIENT_IS_NOT_READY, getBillingClientErrorDescription(billingResult.getResponseCode()));
                            }
                        }
                        @Override
                        public void onBillingServiceDisconnected() {
                            // showing an error dialog
                        }
                    });
                }
            }

edit: Could the users simply cheat? e.g. use a cracked google play store/other software that is messing with the purchases? Because I am not getting this error on any of the devices that I have, no matter what I do.

edit: So far I found out that onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) method of the billing client returns this error code (BillingResponseCode.ERROR) if you use lucky patcher to crack the application, but I wasn't getting any errors in the querySkuDetailsAsync()... Could this be related?

edit: Also I've created an issue in the google issuetracker, come support me there if you'd like. https://issuetracker.google.com/issues/139631105

Redbu11
  • 173
  • 1
  • 7
  • Where is your code? – Mayur Patel Aug 19 '19 at 06:30
  • 2
    Also experiencing this issue. Seems to me that might be a newly introduced bug. For the time being, my "very dirty" workaround is that if received a 6 code, then perform a quick small retrial loop with querySkyDetailsAsync. So far seems to work, I still can see in the logs that sometimes the code 6 is received, but after a couple of retries it gets the right code. Lets hope the Android team solves it soon. – PerracoLabs Aug 19 '19 at 11:05
  • @PerracoLabs Thanks for the solution, that's an idea! xD I'll try that :P Also I've created an issue in the google issuetracker, come support me there if you'd like. https://issuetracker.google.com/issues/139631105 – Redbu11 Aug 19 '19 at 11:09
  • By any chance, do the users have a different language / country that your application has sales on? To us, if we restrict the countries that we sell products on, the users from there cannot query the products. Occasionally it returns with an empty list while the response code is success, but sometimes it returns API error too. Maybe this is your issue, but since it's not a definitive solution I'm posting as a comment. – Furkan Yurdakul Aug 19 '19 at 11:25
  • @Furkan Yurdakul yes, we do have users from different countries, but we don't have restrictions on the countries that the error is coming from... – Redbu11 Aug 19 '19 at 11:35
  • 1
    I see. Then, the best scenario would be retrying the connection at "onBillingServiceDisconnected" method 2 or 3 times. It will mostly reconnect if the issue is random. – Furkan Yurdakul Aug 19 '19 at 11:38
  • @Furkan Yurdakul it's not related to the onBillingServiceDisconnected(the client is probably not disconnected)... At least not as far as it seems to be... It simply fails in querySkuDetailsAsync when the service is connected. I would certainly try the PerracoLabs' "dirty" solution, as it seems to be the most reasonable one. – Redbu11 Aug 19 '19 at 11:58
  • Google acknolege the bug and fixed in version 2.0.3. Check https://developer.android.com/google/play/billing/billing_library_releases_notes#release-2_0_3 – MiguelSlv Oct 13 '19 at 10:02
  • @MiguelSlv you are talking about a different bug – Redbu11 Oct 14 '19 at 03:56
  • yap. Same behavior but different error. Sorry – MiguelSlv Oct 14 '19 at 12:01
  • Have you checked the debug message? https://developer.android.com/reference/com/android/billingclient/api/BillingResult#getDebugMessage – DrPower Nov 08 '19 at 06:43
  • For me with version 2.0.3 callback is not fired – GMG Nov 29 '19 at 12:45

1 Answers1

4

For future reference and some other people suffering from this (like me at IAB 3.0.1), someone at the issue tracker bug that OP opened posted a comment about how we should react if we receive this code

Error 6 (https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#error) usually represents a transient error from our backend servers. This error code should trigger a retry which in most cases should succeed.

LINK: https://issuetracker.google.com/issues/139631105#comment11

example code:

@Override
public void onSkuDetailsResponse(@NonNull BillingResult billingResult, @Nullable List<SkuDetails> list) {
    Utils.log("BILLING: onSkuDetailsResponse(). Result (" + billingResult.getResponseCode() + ") List size: " + (list == null ? "null" : list.size()));
    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) {
        if (mSkuQueryErrorCounter > 0) mSkuQueryErrorCounter--;
        // process the SkuDetail list...
        }
    } else {
        // https://issuetracker.google.com/issues/139631105#comment11
        mSkuQueryErrorCounter++;
        if (mSkuQueryErrorCounter <= 2) scheduleUpdate(NOW);
        else if (mSkuQueryErrorCounter < 5) scheduleUpdate(TEN_SECONDS);
        else if (mSkuQueryErrorCounter < 10) scheduleUpdate(ONE_HOUR);
        else scheduleUpdate(ONE_DAY);
    }
}
BamsBamx
  • 4,139
  • 4
  • 38
  • 63