PAYATHON 2026

Android renewed subscription orderID 缺少 ..X 后缀

支付老李

结论

实现不一定有问题。inventory.getPurchase() 返回的是 Google Play Billing 客户端缓存的购买记录,不能保证每次自动续订后,其中的 orderId 都会更新为带 ..X 后缀的续订订单号。

..0、..1 等后缀用于区分同一订阅产生的后续续订订单,但客户端查询结果可能仍保留首次购买的 orderId。因此,不能根据客户端的 orderId 或其后缀判断订阅是否已经续订。

为什么没有看到 ..X

自动续订可能产生类似下面的订单号:

GPA.1234-5678-9012-34567
GPA.1234-5678-9012-34567..0
GPA.1234-5678-9012-34567..1

其中:

  • 不带后缀的订单号通常对应首次购买。
  • ..0、..1 等通常对应后续续订交易。
  • 这些后缀用于标识订单和财务交易,不能用于判断客户端订阅状态。

inventory.getPurchase() 常见于旧版 In-app Billing 辅助库。它查询的是设备端当前拥有的 Purchase,不是订阅的完整交易历史。即使订阅已经自动续订,客户端库存中的 Purchase 数据也可能不会更新为最新续订订单的数据。

续订扣款、宽限期、账号保留、暂停和恢复等状态,也无法仅凭订单号可靠识别。

推荐处理方式

1. 客户端把 purchaseToken 发送到服务端

客户端完成购买后,保存并上传以下信息:

Purchase purchase = inventory.getPurchase(sku);

if (purchase != null) {
    String productId = purchase.getSku();
    String purchaseToken = purchase.getToken();
    String clientOrderId = purchase.getOrderId();

    // 将 productId 和 purchaseToken 发送到服务端验证。
    // clientOrderId 可以留作日志,但不要把它作为订阅有效性的唯一依据。
}

如果使用新版 Google Play Billing Library,可以从 Purchase 对象中取得购买令牌:

val productIds = purchase.products
val purchaseToken = purchase.purchaseToken
val clientOrderId = purchase.orderId

2. 在服务端验证订阅状态

服务端使用 packageName、订阅商品信息和 purchaseToken 调用 Google Play Developer API,读取 Google Play 记录的订阅状态。

概念示例:

packageName    = com.example.app
subscriptionId = monthly_subscription
purchaseToken  = token_received_from_android

具体使用旧版 purchases.subscriptions.get,还是较新的订阅查询接口,取决于当前采用的 Google Play Developer API 版本和订阅模型。不要为了获得续订订单号,在客户端自行拼接 ..X。

3. 使用 Real-time Developer Notifications 跟踪变化

如果业务需要及时处理续订、取消、暂停、恢复、宽限期或退款,应配置 Real-time Developer Notifications。收到通知后,使用通知携带的购买令牌向 Google Play Developer API 查询权威状态。

处理流程如下:

客户端购买
    → 上传 purchaseToken
    → 服务端向 Google Play 验证
    → 保存订阅状态和到期时间
    → 收到实时开发者通知
    → 再次向 Google Play 查询并更新状态

如果必须获取每笔续订订单号

需要对账、开票或由客服定位具体扣款时,应以 Google Play Console 中的订单与财务记录、结算报告,或服务端 API 实际返回的数据为准。

不要通过下面的方式构造订单号:

String renewalOrderId = originalOrderId + "..1"; // 错误做法

客户端无法可靠推断续订序号。扣款失败、订阅恢复、替换套餐以及其他订阅生命周期变化,都可能让简单递增的假设失效。

注意事项

  • orderId 用于标识订单,不代表订阅状态。
  • purchaseToken 是服务端验证购买记录的关键凭据。
  • 不能只检查 ..X 后缀来认定续订成功。
  • 不要仅依赖客户端缓存处理会员权限。
  • 不要将购买令牌或服务账号凭据写入日志,也不要把服务账号凭据放在客户端代码中。
  • 沙盒测试、真实扣款以及不同订阅模型的返回细节可能不同,应以服务端 API 的实际响应和 Google Play 后台记录为准。

因此,inventory.getPurchase() 中没有出现 ..X 后缀,并不表示续订失败,也不能说明代码有问题。订阅验证和续订跟踪应由服务端完成。

备注:内容仅供参考。