androidresponsein-app-billingandroid-inapp-purchaseplay-billing-library

Google Play Billing Library - querySkuDetailsAsync occasionally returns BillingResponseCode.ERROR (6)


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


Solution

  • 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);
        }
    }