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

Android Billing:ConsumeParams消费失败时如何取消购买?

问题解答:消费失败时的购买状态处理

首先明确:已完成的购买(PURCHASED状态且已确认)无法直接取消,但你遇到的「多次购买商品变成无法再次购买」的问题,本质是消费操作未成功导致Google Play仍判定用户拥有该商品,而非真的变成了一次性购买。可以通过以下方案解决:

核心修复思路

1. 消费失败时添加重试机制

网络不稳定是消费失败的主要原因,不要单次失败就放弃,通过指数退避重试提升成功率。比如第一次失败后等2秒,第二次等4秒,最多重试3-5次。

2. 持久化未消费的购买记录

如果用户在消费过程中关闭应用,下次启动时必须主动查询未消费的购买记录,重新发起消费。可以将未消费的purchaseToken存在SharedPreferences或本地数据库中。

3. 完善消费回调的失败处理逻辑

在消费失败时,不要静默忽略,要记录状态并提示用户,同时触发重试流程。

代码修改示例

修改CHECK_PURCHASE_TYPE方法,添加重试逻辑

private void CHECK_PURCHASE_TYPE(Purchase purchase) {
    String sku = purchase.getProducts().get(0);
    if (sku.equals("sku0")) {
        consumePurchaseWithRetry(purchase, 0);
    } else if (sku.equals("sku1") || sku.equals("sku2")) {
        SAVE_PREF(purchase);
    }
}

// 带重试的消费方法
private void consumePurchaseWithRetry(Purchase purchase, int retryCount) {
    int maxRetries = 3;
    if (retryCount >= maxRetries) {
        // 重试次数耗尽,记录未消费的token,等待下次启动处理
        saveUnconsumedPurchaseToken(purchase.getPurchaseToken());
        // 提示用户购买已完成但未生效,稍后会自动处理
        new Handler(Looper.getMainLooper()).post(() -> 
            binding.PurchaseTxtMessage.setText(R.string.DPurchase_Consume_Failed_Retry)
        );
        return;
    }

    ConsumeParams consumeParams = ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build();
    billingClient.consumeAsync(consumeParams, (billingResult, s) -> {
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
            SAVE_PREF(purchase);
            // 消费成功,移除未消费记录(如果有的话)
            removeUnconsumedPurchaseToken(purchase.getPurchaseToken());
        } else {
            // 计算重试延迟,指数退避
            long delayMillis = (long) Math.pow(2, retryCount) * 1000;
            new Handler(Looper.getMainLooper()).postDelayed(() -> 
                consumePurchaseWithRetry(purchase, retryCount + 1), delayMillis
            );
        }
    });
}

// 持久化未消费的token
private void saveUnconsumedPurchaseToken(String token) {
    SharedPreferences prefs = getSharedPreferences("BillingPrefs", MODE_PRIVATE);
    Set<String> unconsumedTokens = prefs.getStringSet("unconsumed_tokens", new HashSet<>());
    unconsumedTokens.add(token);
    prefs.edit().putStringSet("unconsumed_tokens", unconsumedTokens).apply();
}

// 移除已消费的token
private void removeUnconsumedPurchaseToken(String token) {
    SharedPreferences prefs = getSharedPreferences("BillingPrefs", MODE_PRIVATE);
    Set<String> unconsumedTokens = prefs.getStringSet("unconsumed_tokens", new HashSet<>());
    unconsumedTokens.remove(token);
    prefs.edit().putStringSet("unconsumed_tokens", unconsumedTokens).apply();
}

应用启动时检查未消费记录

在你的BillingClient初始化完成后,添加查询未消费购买的逻辑:

// 查询所有已购买未消费的商品
private void queryUnconsumedPurchases() {
    QueryPurchasesParams params = QueryPurchasesParams.newBuilder()
            .setProductType(BillingClient.ProductType.INAPP)
            .build();
    billingClient.queryPurchasesAsync(params, (billingResult, purchases) -> {
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
            for (Purchase purchase : purchases) {
                // 检查是否是多次购买的sku0,且未被消费
                if (purchase.getProducts().get(0).equals("sku0") && !purchase.isConsumed()) {
                    consumePurchaseWithRetry(purchase, 0);
                }
            }
        }
    });
}

额外注意事项

  • 确保verifyValidSignature逻辑正确,不要误判合法的购买记录。
  • 对于PENDING状态的购买,不要直接标记为取消,应该后续通过查询接口确认最终状态,避免漏处理。
  • 一次性购买的商品(sku1、sku2)不需要消费,只需要确认即可,你的现有逻辑是正确的。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 16:15:14