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

Google IAP Billing Library 5:待处理转可计费购买消费时触发SERVICE_DISCONNECTED

Google IAP Billing Library 5:PENDING转CHARGEABLE购买消费时SERVICE_DISCONNECTED错误修复

问题详情

使用Google IAP Billing Library 5,代码基本照搬官方文档,但处理从PENDING状态转为CHARGEABLE状态的内购时,调用consumeAsync总会返回SERVICE_DISCONNECTED错误。实际服务并未真正断开(onBillingServiceDisconnected很久才触发),强制重连后反而查不到CHARGEABLE状态的购买,但完全退出再重启APP,就能正常检测到并成功消费。

注:未经历PENDING状态的测试支付,消费流程完全正常。

原代码:

public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
    runOnUiThread(new Runnable()    {   @Override   public void run()
    {
    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED)
        {toastUI("Purchase Canceled"); return;}

    if (billingResult.getResponseCode() !=  BillingClient.BillingResponseCode.OK || purchases == null)
        {toastUI("Error: " + billingResult.getDebugMessage()); return; }

    // only option left is BillingClient.BillingResponseCode.OK

    for (Purchase purchase : purchases)
        {
        if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING)
            header.setText("Pending Purchase; you may return later to complete ");
        else
            {
            ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();

            ConsumeResponseListener listener = (billingResult1, purchaseToken) -> {
            if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK)
                {
                header.setText("Purchase Complete: " + purchase.getProducts());
                Log.e(TAG, "purchase complete");
                //TODO now we just need to apply the purchase to the main game
                }
            else if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_DISCONNECTED)
                {
                Log.e(TAG, "Server disconnected, retrying");
                header.setText("Server disconnected, retrying "+ billingResult1.toString());
                billingClient.startConnection(IAP_Activity.this);
                }
            else
                    header.setText("purchase consumption failed: \n " + billingResult1.getDebugMessage());
            };

            billingClient.consumeAsync(consumeParams, listener);
            }
        }}}); // run on UI thread
    }

问题根源

这个SERVICE_DISCONNECTED是框架的误报,核心原因是:

  • 当购买从PENDING转为CHARGEABLE时,onPurchasesUpdated回调返回的Purchase对象可能是PENDING状态时的旧实例,用这个旧实例的token直接消费,会触发服务端校验失败,被包装成SERVICE_DISCONNECTED错误。
  • 强制重连后直接消费,本地BillingClient缓存还没更新最新的购买状态,所以查不到CHARGEABLE的订单;重启APP后,BillingClient会重新拉取服务器端的最新购买数据,因此能正常处理。

修复方案

1. 持久化保存PENDING状态的购买Token

当收到PENDING状态的购买时,把该订单的purchaseToken保存到本地(比如SharedPreferences或数据库),后续用于查询最新状态:

// 示例:用SharedPreferences保存
SharedPreferences prefs = getSharedPreferences("IAP_PREFS", MODE_PRIVATE);
if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) {
    header.setText("Pending Purchase; you may return later to complete ");
    prefs.edit().putString("PENDING_PURCHASE_TOKEN", purchase.getPurchaseToken()).apply();
}

2. 主动查询最新购买状态再消费

不要直接用onPurchasesUpdated返回的Purchase对象消费,而是调用queryPurchasesAsync拉取服务器端最新的购买数据,拿到CHARGEABLE状态的实例后再执行消费:

// 定义查询未消费购买的方法
private void queryAndConsumePendingPurchases() {
    if (!billingClient.isReady()) {
        Log.e(TAG, "Billing client not ready, skip query");
        return;
    }
    billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder()
            .setProductType(BillingClient.ProductType.INAPP)
            .build(), (billingResult, purchases) -> {
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
            for (Purchase purchase : purchases) {
                if (purchase.getPurchaseState() == Purchase.PurchaseState.CHARGEABLE
                        && !purchase.isAcknowledged()) {
                    // 执行消费
                    consumePurchase(purchase);
                    // 消费成功后清除本地保存的token
                    getSharedPreferences("IAP_PREFS", MODE_PRIVATE)
                            .edit().remove("PENDING_PURCHASE_TOKEN").apply();
                }
            }
        }
    });
}

// 单独封装消费逻辑
private void consumePurchase(Purchase purchase) {
    ConsumeParams consumeParams = ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build();
    billingClient.consumeAsync(consumeParams, (billingResult1, purchaseToken) -> {
        runOnUiThread(() -> {
            if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                header.setText("Purchase Complete: " + purchase.getProducts());
                Log.e(TAG, "purchase complete");
                // 应用购买权益到游戏
            } else {
                header.setText("purchase consumption failed: \n " + billingResult1.getDebugMessage());
            }
        });
    });
}

3. 重连后触发购买查询

当收到SERVICE_DISCONNECTED错误时,重连成功后要主动查询购买状态,而不是直接重试消费:

// 修改原代码中的SERVICE_DISCONNECTED处理逻辑
else if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_DISCONNECTED) {
    Log.e(TAG, "Server disconnected, reconnecting...");
    header.setText("Server disconnected, reconnecting...");
    billingClient.startConnection(new BillingClientStateListener() {
        @Override
        public void onBillingSetupFinished(BillingResult billingResult) {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                // 连接成功后查询最新购买状态
                queryAndConsumePendingPurchases();
            }
        }

        @Override
        public void onBillingServiceDisconnected() {
            // 可以加入重连重试逻辑
        }
    });
}

4. 在APP启动/连接成功时自动查询

在onBillingSetupFinished回调里,自动触发一次购买查询,确保APP启动后能检测到所有待处理的CHARGEABLE订单:

@Override
public void onBillingSetupFinished(BillingResult billingResult) {
    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
        // 连接成功后查询待处理的购买
        queryAndConsumePendingPurchases();
    }
}

修改后的完整onPurchasesUpdated代码

public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
    runOnUiThread(() -> {
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
            toastUI("Purchase Canceled");
            return;
        }

        if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK || purchases == null) {
            toastUI("Error: " + billingResult.getDebugMessage());
            return;
        }

        SharedPreferences prefs = getSharedPreferences("IAP_PREFS", MODE_PRIVATE);
        for (Purchase purchase : purchases) {
            if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) {
                header.setText("Pending Purchase; you may return later to complete ");
                // 保存PENDING订单的token
                prefs.edit().putString("PENDING_PURCHASE_TOKEN", purchase.getPurchaseToken()).apply();
            } else {
                // 不直接消费,而是触发查询最新状态后消费
                queryAndConsumePendingPurchases();
            }
        }
    });
}

内容的提问来源于stack exchange,提问作者Al Ro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 01:12:36