Android In App Billing 定期订阅如何检测续订
明确结论
Google Play 的自动续订订阅在正式环境中会按周期自动续费,应用无需主动发起续订。这个过程会一直持续,直至用户取消订阅、付款失败且无法恢复,或者开发者撤销订阅等情况发生。
建议按以下方式检测续订:
- 用户首次购买时,将
purchaseToken上传到服务器。 - 服务器通过 Google Play Developer API 查询订阅状态。
- 配置 Real-time Developer Notifications(RTDN),接收续订、取消、暂停和恢复等事件。
- 收到通知后,再调用 Google Play Developer API 查询最终状态,不要直接根据通知内容授予权益。
正常的周期续订通常沿用原来的 purchaseToken,不会在每个账期生成新 token。升级、降级或重新订阅时可能产生新的 purchaseToken,服务器需要结合 linkedPurchaseToken 处理订阅关系。
应用如何知道订阅已经续订
续订成功后,Google Play 会更新服务端的订阅状态。客户端也可以通过 BillingClient.queryPurchasesAsync() 查询当前用户拥有的订阅,但不应将客户端查询作为唯一依据,原因包括:
- 应用可能长时间没有启动;
- 网络或缓存可能导致状态没有及时更新;
- 退款、撤销和付款宽限期等变化可能发生在应用关闭期间;
- 客户端数据更容易被篡改。
实际项目通常以服务器查询结果为准,客户端查询主要用于用户登录后快速刷新界面。
val params = QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.SUBS)
.build()
billingClient.queryPurchasesAsync(params) { billingResult, purchases ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
purchases.forEach { purchase ->
// 将 purchase.purchaseToken 和对应商品信息发送到自己的服务器验证
uploadPurchaseTokenToServer(purchase.purchaseToken)
}
}
}
客户端返回“已购买”,并不表示服务器已经确认用户当前仍拥有完整权益。最终判断应在服务器完成。
使用 purchaseToken 查询订阅状态
服务器保存客户端上传的 purchaseToken 后,可以调用 Android Publisher API 的 purchases.subscriptionsv2.get:
GET https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/subscriptionsv2/tokens/{token}
Authorization: Bearer {accessToken}
参数含义如下:
packageName是应用包名;token是 Billing Library 返回的purchaseToken;accessToken应通过有权访问 Google Play Developer API 的服务器凭据获取。
简化的服务端请求示例:
async function getSubscription(packageName, purchaseToken, accessToken) {
const url =
`https://androidpublisher.googleapis.com/androidpublisher/v3/applications/` +
`${encodeURIComponent(packageName)}/purchases/subscriptionsv2/tokens/` +
`${encodeURIComponent(purchaseToken)}`;
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error(`Google Play API returned ${response.status}`);
}
return response.json();
}
查询结果中需要重点检查:
subscriptionState;lineItems[].expiryTime;lineItems[].autoRenewingPlan.autoRenewEnabled;acknowledgementState;linkedPurchaseToken;- 暂停、取消以及付款相关的上下文。
判断用户当前是否拥有权益时,不能只看自动续订是否开启。用户取消自动续订后,通常仍可使用服务,直到当前已付款周期结束。更可靠的方式是结合订阅状态和 expiryTime 判断。
purchaseToken 是否会在续订时变化
普通的自动续订通常会沿用同一个 purchaseToken。服务器可以继续使用该 token 查询最新的到期时间和订阅状态。
以下操作可能产生新 token:
- 用户升级或降级订阅;
- 用户在订阅结束后重新购买;
- 订阅被替换;
- 其他会建立新购买关系的操作。
新订阅信息可能带有 linkedPurchaseToken,用于指向上一笔相关购买。服务器应保存 token 之间的关联,并避免旧订阅继续重复授予权益。
建议至少保存以下字段:
user_id
package_name
product_id
purchase_token
linked_purchase_token
subscription_state
expiry_time
last_verified_at
一个用户可能对应多个 token,因此不能假设每个用户始终只有一个 token,也不能把 purchaseToken 当作用户身份。它表示的是购买关系,需要与应用自己的用户账号绑定。
使用 RTDN 及时检测续订
轮询 Google Play Developer API 可以确认订阅状态,但不适合频繁调用。较完整的做法是配置 Real-time Developer Notifications,通过 Google Cloud Pub/Sub 接收订阅事件。
处理流程通常如下:
- 用户首次购买订阅。
- 客户端将
purchaseToken上传到服务器。 - 服务器调用 Google Play Developer API 验证并保存订阅。
- Google Play 中发生续订、取消、恢复、暂停或撤销等变化。
- RTDN 将事件发送到 Pub/Sub。
- 服务器从通知中取得
purchaseToken。 - 服务器再次调用
purchases.subscriptionsv2.get。 - 根据查询结果更新权益和到期时间。
RTDN 只表示订阅状态发生了变化,不包含最终可信的订阅详情。消息可能重复、延迟或乱序,因此处理程序必须保证幂等,并以 API 查询结果为准。
如何测试自动续订
Android 订阅应通过 Google Play Console 提供的测试轨道和许可测试账号进行测试。不要依靠本地安装的未发布 APK 判断完整的订阅生命周期,因为购买流程需要由 Google Play 环境正确处理。
基本步骤如下:
- 在 Play Console 中创建订阅商品和基础方案。
- 将测试账号配置为许可测试人员。
- 把应用发布到内部测试轨道。
- 使用测试账号从 Google Play 安装应用。
- 完成测试购买。
- 检查客户端查询结果、RTDN 消息和 Developer API 返回的状态。
测试订阅通常使用加速的续订周期,并会在续订有限次数后停止。具体周期和次数可能因 Google Play 的测试规则、基础方案和账号配置而变化,因此不应将“一天”或某个固定次数写进业务逻辑。测试时,应以当前 Play Console 的实际表现和官方测试规则为准。
如果测试订阅在一天后结束,可能是测试环境停止了加速续订,并不表示正式环境中的订阅也只持续一天。正式订阅会按照配置的真实结算周期自动续费。
权益判断示例
下面是一个简化示例。实际项目还需要处理宽限期、账号保留、暂停、撤销和延迟付款等状态。
function hasSubscriptionEntitlement(subscription, now = new Date()) {
const activeStates = new Set([
"SUBSCRIPTION_STATE_ACTIVE",
"SUBSCRIPTION_STATE_IN_GRACE_PERIOD"
]);
if (!activeStates.has(subscription.subscriptionState)) {
return false;
}
return (subscription.lineItems ?? []).some((item) => {
if (!item.expiryTime) {
return false;
}
return new Date(item.expiryTime) > now;
});
}
不能只根据 autoRenewEnabled 判断权益:
false可能只表示用户取消了下一周期的续订,当前周期仍然有效;true也不能保证下一次扣款一定成功;- 订阅处于宽限期、暂停或账号保留状态时,需要按照产品规则决定是否继续提供服务。
需要特别注意的事项
- 首次购买必须按照 Google Play Billing 的要求进行确认,否则购买可能被自动退款。
- 不要在客户端保存服务账号密钥,也不要让客户端直接调用需要服务器凭据的 Developer API。
- RTDN 消息可能重复,处理程序需要支持重复消费,并在收到消息后重新查询订阅状态。
- 服务器应定期校验仍处于有效状态的订阅,用于弥补 RTDN 消息丢失或配置异常。
- 到期时间应以 Google Play 返回的值为准,不要直接在本地给上次到期时间增加一个月。
- 发生升级、降级或重新订阅时,需要处理
linkedPurchaseToken,避免新旧购买同时授予权益。 - 业务数据库应将购买关系绑定到应用自己的登录用户,不要依赖设备、Google 账号显示信息或
purchaseToken识别用户。
备注:内容仅供参考。
