如何获取 subscription 的最后续订时间和结束时间?
结论
Purchase.getPurchaseTime() 不能提供订阅最近一次续订的时间。对于订阅,这个值表示订阅关系最初建立的时间,不能用于计算当前订阅周期何时结束。
正确的处理流程如下:
- Android 客户端通过
queryPurchasesAsync()获取purchaseToken。 - 将
purchaseToken发送到自己的服务端。 - 服务端调用 Google Play Developer API 的
purchases.subscriptionsv2.get。 - 从响应中的
lineItems[].expiryTime读取当前订阅周期的结束时间。 - 如果业务必须记录续订发生时间,应接入 Real-time Developer Notifications(RTDN)。收到
SUBSCRIPTION_RENEWED后,立即重新查询订阅状态并保存事件记录。
判断用户是否仍有订阅权益时,通常只需要可信的 expiryTime 和订阅状态,无需自行推算“最后续订时间”。
为什么不能在客户端直接获取
queryPurchasesAsync() 返回的是用户当前拥有的购买项目,并非完整的订阅交易流水。
同一个订阅可能自动续订多次,但 purchaseToken 和最初购买时间不会像普通一次性商品那样,在每次续订后重新生成。因此:
purchase.purchaseTime
不能表示最近一次扣款或续订的时间。
也不要使用下面的公式计算结束时间:
结束时间 = purchaseTime + 订阅周期
这种算法无法正确处理多次续订、升级或降级、延期、暂停、宽限期、账号保留期和预付费订阅等情况。
获取订阅结束时间
客户端只需获取并上传 purchaseToken:
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.SUBS)
.build()
) { billingResult, purchases ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
purchases.forEach { purchase ->
uploadPurchaseTokenToServer(
productIds = purchase.products,
purchaseToken = purchase.purchaseToken
)
}
}
}
服务端使用自己的 Google Play API 凭据发起请求:
GET https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/subscriptionsv2/tokens/{purchaseToken}
Authorization: Bearer {accessToken}
收到响应后,主要检查订阅状态和 lineItems:
{
"subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
"latestOrderId": "GPA.1234-5678-9012-34567..0",
"lineItems": [
{
"productId": "premium_monthly",
"expiryTime": "2026-09-30T12:34:56Z"
}
]
}
expiryTime 是 RFC 3339 格式的时间戳,表示对应订阅项目当前权益的截止时间。服务端应按 UTC 时间解析并保存,例如:
const expiryTime = new Date(subscription.lineItems[0].expiryTime);
if (Number.isNaN(expiryTime.getTime())) {
throw new Error("Invalid subscription expiryTime");
}
const entitlementActive = expiryTime.getTime() > Date.now();
实际业务中,不能假定 lineItems 永远只有一个。套餐变更或其他订阅状态转换可能产生多个项目,需要结合 productId、订阅状态和各项目的 expiryTime 判断具体权益。
如何记录最近一次续订时间
purchases.subscriptionsv2.get 主要用于返回当前订阅状态和到期时间,并不保证包含可以直接使用的“最后续订时间”字段。
如果业务确实需要记录续订时间,应在服务端按以下方式处理:
- 配置 Google Play RTDN。
- 接收
SUBSCRIPTION_RENEWED通知。 - 使用通知中的
purchaseToken调用purchases.subscriptionsv2.get。 - 保存新的
expiryTime、latestOrderId、订阅状态和通知事件时间。 - 根据
purchaseToken、订单号或事件进行幂等处理,避免重复通知导致重复记账。
RTDN 中的 eventTimeMillis 更适合用作通知事件时间。在要求严格财务精度的场景中,不应直接将它视为扣款完成时间。如果需要精确的交易入账时间,应通过 latestOrderId 对应的订单信息或 Google Play 财务报表进行核对。具体可用字段取决于当前启用的 Google Play API 和报表能力。
需要注意的状态
仅比较 expiryTime 无法覆盖所有业务规则,还需要检查 subscriptionState。例如:
- 用户取消自动续订后,在
expiryTime之前通常仍有权益。 - 宽限期内是否继续提供权益,由产品策略决定。
- 账号保留期可能表示付款失败,且权益已经暂停。
- 暂停、撤销和过期状态不能只根据客户端缓存判断。
- 预付费订阅可能不会自动续订,延期方式也有所不同。
- 套餐升级或降级后,不能按固定周期倒推开始时间。
订阅权益应由服务端最终判定,并以 Google Play Developer API 返回的最新状态和 expiryTime 为准。客户端数据只能用于发起验证,不能作为可信的订阅到期依据。
备注:内容仅供参考。