PAYATHON 2026

如何取消 Google In App Purchases 自动续订

支付老李

结论

用户订阅成功后,服务端可以立即关闭自动续订。只要执行的是“停止后续续费”,而不是退款或撤销购买,订阅权益通常会保留到当前计费周期结束,不需要另外设置定时任务。

以下几种操作需要分清:

  • cancel:停止下一周期续费,当前周期一般仍然有效。
  • revoke:立即终止订阅权益。
  • refund:退款不一定会立即撤销权益,结果取决于具体调用方式。
  • 因付款失败被系统取消:可能涉及宽限期、账号保留期等状态,不能按照用户主动取消来处理。

通过服务端 API 取消订阅,也不代表 cancelReason = 1。cancelReason = 1 表示 Google Play 因计费等问题执行的系统取消。开发者主动取消在较新的状态模型中有单独的表示方式。旧版接口与新版 subscriptionsv2 的字段并不完全一致,因此不能只根据 cancelReason 判断权益是否有效。

推荐实现方式

订阅完成后,服务端可以按以下顺序处理:

  1. 接收客户端提交的 purchaseToken。
  2. 调用 Google Play Developer API 验证购买结果。
  3. 确认订单、商品与用户相匹配。
  4. 对订阅执行 acknowledge。
  5. 为用户开通权益。
  6. 调用取消接口,关闭后续自动续订。
  7. 保存 Google Play 返回的到期时间。
  8. 到期前继续允许用户使用,到期后再关闭权益。

acknowledge 不能省略。如果没有在 Google Play 规定的期限内确认购买,订单可能被自动退款并撤销。

使用新版 SubscriptionsV2 API

如果当前接入版本支持 purchases.subscriptionsv2.cancel,应明确指定停止续费类型:

POST https://androidpublisher.googleapis.com/androidpublisher/v3/applications/com.example.app/purchases/subscriptionsv2/tokens/PURCHASE_TOKEN:cancel
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "cancellationContext": {
    "cancellationType": "USER_REQUESTED_STOP_RENEWALS"
  }
}

USER_REQUESTED_STOP_RENEWALS 表示停止后续续费。用户通常仍可使用已经付费的当前周期。

不要误用:

DEVELOPER_REQUESTED_STOP_PAYMENTS

该类型表示开发者要求停止付款,可能立即终止订阅,不适合“购买一个周期后自然到期”的场景。具体行为应以项目当前使用的 Google Play Developer API 版本说明为准。

旧版取消接口示例

使用旧版 purchases.subscriptions.cancel 时,请求形式类似:

curl -X POST \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/com.example.app/purchases/subscriptions/monthly_plan/tokens/PURCHASE_TOKEN:cancel"

该操作用于停止自动续订,无法通过客户端 Billing Library 代替。访问凭据和取消操作都必须放在可信的服务端。

旧版查询结果可能包含:

{
  "autoRenewing": false,
  "expiryTimeMillis": "1735689600000",
  "cancelReason": 3
}

不同接口版本的取消原因枚举可能不同。例如,较新的旧版订阅资源可能使用 3 表示开发者取消,而最早的文档只列出 0、1 和 2。解析时应以实际调用接口的 schema 为准,不能因为取消由服务端发起,就直接推断为系统因计费问题取消。

权益判断方式

取消成功不等于权益立即失效。服务端需要结合订阅状态和到期时间进行判断:

boolean entitlementActive =
        expiryTimeMillis > System.currentTimeMillis()
        && !isRevoked;

实际项目还需要处理宽限期、账号保留期、暂停、退款和撤销等状态。不能仅凭 autoRenewing == false 关闭权限。该字段只表示订阅不会再次续费,不代表当前周期已经到期。

建议保存以下信息:

purchaseToken
productId / basePlanId
latestOrderId
subscriptionState
autoRenewing
expiryTime
cancellationType
acknowledgementState

同时订阅 Real-time Developer Notifications。收到通知后,应重新查询 Google Play,不要完全依赖本地定时器或客户端上报。

更合适的商品模型

如果业务模式本来就是“购买固定时长,到期后由用户手动再次购买”,可以先检查 Google Play Console 是否支持为该商品配置 prepaid plan。预付费方案不会自动续订,比购买后立即取消更符合商品本身的含义,也能减少状态同步工作和客服解释成本。

如果目标地区、商品配置或当前接入能力不支持 prepaid plan,再采用确认购买后立即停止续费的方案。界面上需要明确告知用户,本次购买只包含一个周期,不会自动续订。

注意事项

  • 取消操作必须由服务端完成,不能将服务账号凭据放入 APK。
  • 应先验证购买并执行 acknowledge,再取消自动续订。
  • 不要通过 revoke 实现自然到期,否则权益可能立即终止。
  • 取消请求应支持幂等,避免网络重试引发错误处理。
  • purchaseToken 可能因订阅升级、降级或替换而变化,需要处理关联购买。
  • 权益的最终状态应以 Google Play API 查询结果和实时通知为准。
  • 上线前应使用许可测试账号,验证取消、到期、退款和重新订阅流程。测试订阅的周期长度及状态变化可能与正式环境不同。

备注:内容仅供参考。