Android In-App Subscriptions: queryPurchasesAsync not Returning Subscription Details after Successful Payment

175 views Asked by At

I'm encountering an issue with in-app subscriptions in my Android app. Some users are reporting that they are not receiving subscription benefits even after a successful payment. Upon investigating, I found that billingClient.queryPurchasesAsync consistently returns nothing, even after a successful payment.

The peculiar thing is that if the user clears the data and cache of the Play Store app and then opens my app, the subscription details are acknowledged without any issue.

Additionally, I've noticed that the issue tends to occur when users make payments using Paytm or other UPI methods. Test card purchases, however, work fine.

Here is a snippet of my code:

private PurchasesUpdatedListener purchasesUpdatedListener = (billingResult, purchases) -> {
        if (isDestroyed() || isFinishing()) return;
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
            for (Purchase purchase : purchases) {
                handlePurchase(purchase);
            }
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
            showToast(R.string.purchase_cancelled);
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            //purchases will be null
            showToast(R.string.item_already_owned);
        } else {
            showToast(R.string.error);
        }

    };


 private BillingClientStateListener billingClientStateListener = new BillingClientStateListener() {
        @Override
        public void onBillingServiceDisconnected() {
            reconnectBilling();
        }

        @Override
        public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
            if (isDestroyed() || isFinishing()) return;
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                queryEbProducts();
                queryEbPurchases();
            } else {
                showToast(R.string.unable_to_load_billing);
            }
        }
    };

 private AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = billingResult ->
            runOnUiThread(() -> {
                if (isDestroyed() || isFinishing()) return;
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    verifyPurchase(lastPurchaseToken, ebPremiumProductId);
                } else {
                    showLongToast(R.string.subscription_acknowledgment_error);
                }
            });


 private void verifyPurchase(String token, String sku) {
//here I send the purchase token to the server and grant the subscription benefits from the server-side.      
 }

private void queryEbProducts() {
        if (billingClient == null || !billingClient.isReady()) return;
        QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder()
                .setProductList(ImmutableList.of(QueryProductDetailsParams.Product.newBuilder()
                        .setProductId(ebPremiumProductId)
                        .setProductType(BillingClient.ProductType.SUBS).build())).build();

        billingClient.queryProductDetailsAsync(queryProductDetailsParams, (billingResult, productDetailsList) -> runOnUiThread(() -> {
            if (isDestroyed() || isFinishing()) return;
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                if (productDetailsList.size() < 1) {
                    showToast(R.string.empty_product_list);
                }
                for (ProductDetails productDetails : productDetailsList) {
                    updateEbProductData(productDetails);//update UI
                }
            }
        }));
    }


private void queryEbPurchases() {
        if (billingClient == null || !billingClient.isReady()) return;

        billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS).build(),
                (billingResult, purchases) -> {
                    if (isDestroyed() || isFinishing()) return;
                    runOnUiThread(() -> {
                        if (isDestroyed() || isFinishing()) return;
                        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                            if (purchases.size() > 0) {
                                for (Purchase purchase : purchases) {
                                    handlePurchase(purchase);
                                }
                            } else {
                                subsBtn.setText(R.string.subscribe);
                                subsBtn.setEnabled(true);
                                verifyPurchase("null", ebPremiumProductId);//remove expired subscription
                            }
                        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ERROR) {
                            showToast(R.string.query_purchase_error);
                        } else {
                            showToast(getString(R.string.billing_error, billingResult.getResponseCode()));
                        }

                    });

                });

    }


   private void handlePurchase(Purchase purchase) {
        if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
            enableManageSubsBtn();
            if (!purchase.isAcknowledged()) {
                if (!acknowledgingToken.equals(purchase.getPurchaseToken())) {
                    acknowledgingToken = purchase.getPurchaseToken();
                    showBusyBar(R.string.verifying_purchase);
                    lastPurchaseToken = purchase.getPurchaseToken();
                    AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();
                    billingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
                }
            } else {
                verifyPurchase(purchase.getPurchaseToken(), ebPremiumProductId);
            }
            if (!purchase.isAutoRenewing()) showToast(R.string.canceled_subs_msg);
        } else if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) {
            showPendingPurchase();
        } else {
            showToast(getString(R.string.invalid_purchase_state, purchase.getPurchaseState()));
        }
    }

  private void showPendingPurchase() {
        if (isFinishing() || isDestroyed()) return;
        subsBtn.setText(R.string.purchase_pending);
        subsBtn.setEnabled(true);
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.purchase_pending);
        builder.setMessage(R.string.pending_purchase_msg);
        builder.setPositiveButton(R.string.ok, null);
        builder.setNegativeButton(R.string.retry, (dialog, which) -> queryEbPurchases());
        builder.show();

    }


 @Override
    protected void onResume() {
        super.onResume();
        queryEbPurchases();
    }

I've verified that the SKUs match between my code and the Play Store Console, and I'm using the correct product IDs. I've also ensured that the purchase flow logic is correct.

Has anyone else encountered a similar issue with queryPurchasesAsync not returning the expected subscription details, especially with Paytm and UPI payments? Is there a known workaround or solution for this problem?

Any insights or suggestions would be greatly appreciated. Thank you!

0

There are 0 answers