PAYATHON 2026

如何从 in-app billing API 中移除已取消的订阅?

支付小周

结论

in-app billing 客户端 API 无法删除订阅记录,订阅也不能通过 consume 处理。

paymentpending 表示付款尚未完成。遇到这类购买时,应用只能等待 Google Play 将其更新为成功或取消状态。此时不要授予订阅权益,也不要尝试确认或消费购买。

如果代码仍能读取到已经过期的订阅,先确认查询结果属于“当前有效购买”还是“历史购买”,同时检查是否受本地缓存或测试账号混用影响。

处理步骤

1. 确认购买状态

使用 Google Play Billing Library 查询当前订阅,并按照 PurchaseState 分别处理:

billingClient.queryPurchasesAsync(
    QueryPurchasesParams.newBuilder()
        .setProductType(BillingClient.ProductType.SUBS)
        .build()
) { billingResult, purchases ->
    if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
        return@queryPurchasesAsync
    }

    for (purchase in purchases) {
        when (purchase.purchaseState) {
            Purchase.PurchaseState.PURCHASED -> {
                // 购买成功后再验证 purchaseToken、授予权益并 acknowledge。
                handlePurchasedSubscription(purchase)
            }

            Purchase.PurchaseState.PENDING -> {
                // 不授予权益,等待 Google Play 更新状态。
                showPaymentPendingMessage()
            }

            else -> {
                // 不视为有效订阅。
            }
        }
    }
}

不要仅凭 purchaseTime 或 isAutoRenewing 判断订阅是否有效:

  • isAutoRenewing == false 只表示订阅不会再次自动续订,并不意味着购买记录会立即消失。
  • purchaseTime 记录的是购买发生时间,不一定是当前订阅周期的准确到期时间。
  • 购买处于 PENDING 状态时,不能授予权益,也不应执行确认购买的逻辑。

2. 区分当前购买和历史记录

通过购买历史接口查询时,结果中仍可能包含已经取消或过期的订阅。历史记录不是可删除的列表,也不能直接用来判断用户当前是否拥有权益。

应用启动或恢复购买时,应使用 queryPurchasesAsync() 查询当前购买。生产环境中,最好将 purchaseToken 发送到后端,再由后端通过 Google Play Developer API 验证订阅的实际状态和到期时间。

3. 成功购买后及时确认

只有购买状态变为 PURCHASED 后,才能确认购买:

private fun handlePurchasedSubscription(purchase: Purchase) {
    if (!purchase.isAcknowledged) {
        val params = AcknowledgePurchaseParams.newBuilder()
            .setPurchaseToken(purchase.purchaseToken)
            .build()

        billingClient.acknowledgePurchase(params) { result ->
            if (result.responseCode == BillingClient.BillingResponseCode.OK) {
                // 建议在服务端验证成功后授予权益。
            }
        }
    }
}

不要对订阅调用 consumeAsync()。消费操作适用于可以重复购买的应用内商品,不适用于订阅。

4. 测试账号看不到订阅时

依次检查以下内容:

  1. Google Play 商店当前登录的账号,是否就是发起测试购买的许可测试账号。
  2. 应用是否从 Google Play 测试轨道安装,而不是使用不同签名或不同账号安装的本地包。
  3. 当前读取的是否为应用本地或服务端持久化的旧购买数据,而不是本次 queryPurchasesAsync() 返回的结果。
  4. 清除应用自身缓存,重新连接 BillingClient 后再次查询。不要把“清除数据”当成取消 Google Play 订阅的方法。

付款待处理状态通常由 Google Play 管理。应用端没有接口可以强制将 PENDING 改为 CANCELED。只有付款最终失败或超时后,Google Play 才会更新购买状态。

服务端取消或撤销

如果持有有效的 purchaseToken,并且项目已正确配置 Google Play Developer API,可以通过服务端取消或撤销订阅。具体使用哪个接口,取决于当前采用的订阅 API 版本:

  • 取消通常只会阻止后续续订,用户可能仍可使用权益直至当前周期结束。
  • 撤销通常会立即终止权益。
  • 对于已经取消、过期或仍处于付款待处理状态的测试购买,不一定还能再次执行这些操作。

这些操作用于在服务端管理购买状态,不能从 Google Play 的购买历史中“删除记录”。调用前应先查询订阅状态,并正确处理“购买不存在”“已经过期”或“当前状态不允许操作”等响应。

注意事项

  • 不要根据客户端字段自行推算或覆盖 Google Play 的订阅状态。
  • 不要向 PENDING 购买授予内容或会员权限。
  • 不要尝试消费订阅。
  • 不要要求用户通过重复购买覆盖旧的待处理订单。
  • 订阅取消或过期后,历史购买记录仍可能保留。应用只需停止将其识别为有效权益,无须删除记录。
  • 如果 queryPurchasesAsync() 长时间返回明显已经过期的待处理测试购买,应记录账号、商品 ID、订单 ID、purchaseToken 和查询响应,再通过 Google Play Console 支持渠道排查测试订单状态。

备注:内容仅供参考。