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 后缀,并不表示续订失败,也不能说明代码有问题。订阅验证和续订跟踪应由服务端完成。
备注:内容仅供参考。
