On-Device Google Billing V3 与 Developer API 数据不一致
结论
这是测试订阅中可能出现的正常情况。处理跨平台订阅权限时,应以 Google Play Developer API 返回的服务端状态和 expiryTimeMillis 为准,不能把设备端 Billing V3 的 autoRenewing 当作最终授权依据。
两端虽然都有 autoRenewing 字段,但字段含义和数据刷新机制并不相同:
- 设备端结果可能来自 Google Play 客户端缓存。
autoRenewing: true只能说明设备最后获取状态时,该订阅仍处于自动续订状态。 - Developer API 返回 Google Play 后端记录。
autoRenewing: false说明后端当前已经不再安排续订。 - License tester 的订阅采用加速周期,测试续订次数也有限。测试订阅可能被系统停止续订,所以即使用户没有手动取消,服务端也可能变为
autoRenewing: false。 - Google Play 的订阅页面仍然显示该项目,不代表它还会续订。尚未到期、已取消但仍在有效期内,以及历史订阅都可能继续显示。
如何判断订阅是否有效
autoRenewing 表示订阅是否计划再次续订,并不等同于“当前是否有权限”。
对于题目中的旧版 SubscriptionPurchase 响应,主要判断条件是:
当前时间 < expiryTimeMillis
例如:
function evaluateSubscription(subscriptionPurchase) {
const now = Date.now();
const expiryTime = Number(subscriptionPurchase.expiryTimeMillis);
return {
entitled: Number.isFinite(expiryTime) && now < expiryTime,
willRenew: subscriptionPurchase.autoRenewing === true,
expiresAt: new Date(expiryTime).toISOString(),
cancelReason: subscriptionPurchase.cancelReason
};
}
可能出现以下状态组合:
| 是否有效 | 是否自动续订 | 含义 |
|---|---|---|
true | true | 当前有效,并计划续订 |
true | false | 当前仍有效,但到期后不再续订 |
false | false | 已到期且不会续订 |
false | true | 通常是缓存、同步延迟或异常状态,需要重新向服务端确认 |
因此,这笔订阅在 expiryTimeMillis 之前仍可提供权限,但到期后不应继续提供权限,也不应预期它会再次续订。
cancelReason: 0 的含义
在旧版 purchases.subscriptions.get 返回模型中,cancelReason: 0 通常表示用户取消,不能据此解释为“并非用户主动取消”。其他值可能表示系统、替换订阅或开发者操作。具体枚举应以项目所用时期的 Android Publisher API 文档为准。
不过,对于测试购买,即使测试人员没有在界面中明确点击取消,订阅也可能因达到测试续订限制、测试环境清理或相关测试流程而停止续订。只看 cancelReason,通常无法完整还原测试订阅停止续订的操作路径。
cancelReason 存在也不代表订阅立即失效。自动续订取消后,用户通常仍可使用已经支付或由测试生成的剩余有效期。
服务端处理建议
服务端收到 purchaseToken 后,应按以下方式处理:
- 校验
packageName、订阅商品 ID 与purchaseToken的对应关系。 - 调用 Google Play Developer API 查询最新状态。
- 根据服务端返回的到期时间判断当前权限。
- 只把
autoRenewing作为判断到期后是否可能继续续订的辅助状态。 - 在临近到期时再次刷新状态,避免永久沿用某一次查询结果。
- 防止同一个
purchaseToken绑定到多个应用账户。 - 生产环境应使用实时开发者通知,并在收到通知后重新查询 Developer API。
旧版接口的查询形式类似:
GET https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/subscriptions/{subscriptionId}/tokens/{token}
Node.js 中可以进行类似校验:
const response = await androidPublisher.purchases.subscriptions.get({
packageName: "com.redacted.package",
subscriptionId: "quarterly_plan",
token: purchaseToken
});
const purchase = response.data;
const expiryTimeMillis = Number(purchase.expiryTimeMillis);
const entitled =
Number.isFinite(expiryTimeMillis) &&
Date.now() < expiryTimeMillis;
const status = {
entitled,
autoRenewing: purchase.autoRenewing === true,
expiryTimeMillis,
cancelReason: purchase.cancelReason ?? null
};
如果项目使用较新的 Google Play Billing 和 Android Publisher API,应优先采用当前推荐的订阅查询模型,例如 purchases.subscriptionsv2.get。判断状态时,需要结合 subscriptionState、lineItems、到期时间和关联 token,不能继续围绕旧版单一的 autoRenewing 字段设计权限系统。
为什么设备端可能长期显示旧值
Billing V3 的设备端查询主要用于恢复购买和展示客户端界面,不是跨平台权限系统的权威数据源。以下情况都可能导致两端状态出现短暂或持续的不一致:
- Google Play 客户端尚未刷新本地购买缓存;
- 测试订阅刚被后端停止续订;
- 设备端查询发生在服务端状态变化之前;
- 测试环境使用加速时间,且续订次数有限;
- 订阅已取消但尚未到期;
- Google Play 的订阅管理页面仍保留该订阅记录。
排查时可以尝试重新启动 Google Play,等待状态同步后再次查询,但这些操作不能代替服务端验证。
这两个字段应分别处理:expiryTimeMillis 决定当前是否有权限,autoRenewing 决定当前有效期结束后是否计划续订。在这组数据中,服务端状态表示订阅可能仍在有效期内,但已经不会再次续订。设备端缓存中的 autoRenewing: true 不能作为可靠的授权依据。
备注:内容仅供参考。
