You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android应用内订阅:支付成功后queryPurchasesAsync未返回订阅详情

Android内购订阅异常(Paytm/UPI支付场景)解决方案

针对Paytm或UPI支付成功后,billingClient.queryPurchasesAsync无法返回订阅结果,但清除Play Store缓存后恢复正常的问题,这是印度地区UPI类支付场景下常见的同步延迟问题,以下是经过验证的解决思路和代码优化方案:

核心原因分析

  • UPI支付的异步特性:不同于测试卡的即时同步,UPI/Paytm支付需要银行端确认后才会向Play Store发送支付成功通知,这个过程可能存在延迟,导致Play Store本地缓存未及时更新,而queryPurchasesAsync仅读取本地缓存数据,因此无结果返回。
  • Play Store本地缓存一致性问题:部分设备上Play Store的订阅缓存未及时与服务器同步,清除缓存后会强制拉取最新服务器数据,所以能正常识别订阅状态。

具体解决/规避方案

1. 补充查询购买历史(queryPurchaseHistoryAsync)

queryPurchasesAsync仅返回当前有效的订阅,而queryPurchaseHistoryAsync会返回用户最近的购买记录(包括已过期或pending状态的记录),可以在现有查询逻辑中补充该查询,覆盖延迟同步的场景:

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 {
                            // 当前无有效订阅, fallback 到购买历史查询
                            queryPurchaseHistory();
                        }
                    } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ERROR) {
                        showToast(R.string.query_purchase_error);
                        queryPurchaseHistory();
                    } else {
                        showToast(getString(R.string.billing_error, billingResult.getResponseCode()));
                    }
                });
            });
}

private void queryPurchaseHistory() {
    if (billingClient == null || !billingClient.isReady()) return;
    billingClient.queryPurchaseHistoryAsync(QueryPurchaseHistoryParams.newBuilder()
            .setProductType(BillingClient.ProductType.SUBS)
            .build(), (billingResult, purchaseHistoryRecords) -> {
                runOnUiThread(() -> {
                    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchaseHistoryRecords.size() > 0) {
                        // 取最近的购买记录触发后端验证
                        PurchaseHistoryRecord latestRecord = purchaseHistoryRecords.get(0);
                        verifyPurchase(latestRecord.getPurchaseToken(), ebPremiumProductId);
                        // 延迟5秒后再次查询当前订阅,等待缓存同步
                        new Handler(Looper.getMainLooper()).postDelayed(this::queryEbPurchases, 5000);
                    } else {
                        subsBtn.setText(R.string.subscribe);
                        subsBtn.setEnabled(true);
                        verifyPurchase("null", ebPremiumProductId);//移除过期订阅标记
                    }
                });
            });
}

2. 实现Play实时开发者通知(RTDN)

这是最可靠的解决方案:当用户支付成功后,Play会主动向你的后端服务器发送状态变更通知,后端直接通过Google Play Developer API验证token有效性,无需依赖客户端查询。

  • 操作:在Play控制台配置后端通知URL,当支付状态(从pending到purchased)变更时,Play会发送包含购买token的POST请求,后端验证后直接给用户授予权益。
  • 优势:完全不受客户端缓存影响,即使客户端查询失败,后端也能主动感知支付成功。

3. 优化Pending状态的重试逻辑

UPI支付经常会出现较长时间的Pending状态,需要延长重试间隔并增加重试次数,避免频繁查询:

private int retryCount = 0;
private static final int MAX_RETRY = 5;

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) -> {
        if (retryCount < MAX_RETRY) {
            retryCount++;
            // 渐进式延迟:10s、20s、30s...
            long delay = retryCount * 10000;
            new Handler(Looper.getMainLooper()).postDelayed(this::queryEbPurchases, delay);
        } else {
            showToast(R.string.retry_limit_reached);
            showSubscriptionCheckGuide();
        }
    });
    builder.show();
}

private void showSubscriptionCheckGuide() {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle(R.string.subscription_check_guide);
    builder.setMessage("请打开Play Store应用,进入「订阅」页面确认状态,之后返回本应用即可同步权益");
    builder.setPositiveButton(R.string.ok, null);
    builder.show();
}

4. 临时用户引导方案

对于已经出现问题的用户,可以在应用内提供清晰的引导:

  • 告知用户前往Play Store的「我的订阅」页面确认状态
  • 作为临时方案,引导用户清除Play Store缓存(仅用于应急,不推荐作为常规流程)

代码优化建议

  • 移除onResume中频繁调用queryEbPurchases的逻辑,改为仅在支付流程结束、应用从后台切回前台时触发一次查询,避免不必要的网络请求。
  • 在handlePurchase中,对于Pending状态的订阅,除了弹窗提示,还可以启动后台定时任务自动重试查询,无需用户手动触发。

内容的提问来源于stack exchange,提问作者Sujith S Manjavana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 23:55:25