Play Billing 服务端验单与 RTDN:acknowledge 窗口最短只有 1.5 天
本文整理自 Google Play Billing 官方文档,未做真机与沙盒实测,字段名、时间窗口与状态流转以官方文档为准。
官方参考:
- 订阅生命周期:https://developer.android.com/google/play/billing/lifecycle/subscriptions
- RTDN 参考:https://developer.android.com/google/play/billing/rtdn-reference
- 购买生命周期和 RTDN:https://developer.android.com/google/play/billing/lifecycle
purchases.subscriptionsv2.get:https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.subscriptionsv2/getpurchases.subscriptions.acknowledge:https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.subscriptions/acknowledge- Codelab 最佳实践:https://codelabs.developers.google.com/maximize-your-play-billing-integration
RTDN 只是通知,不含完整交易信息
实时开发者通知(RTDN)里能拿到的字段只有这些:
version
packageName
eventTimeMillis // 毫秒时间戳
purchaseToken
notificationType
sku
收到通知后必须再调 Google Play Developer API 拉完整状态,落到自己后端。把 RTDN 当成"状态变更信号"而不是"交易数据",是这一整套集成的前提。eventTimeMillis 是毫秒,别当成秒直接塞进 fromtimestamp。
推送和拉取都走 Cloud Pub/Sub:message.data 是 base64 编码的 DeveloperNotification,message.messageId 是唯一标识,应当用它查重。重复处理不只是逻辑问题,还会白烧 Developer API 配额。投递方式两种:push 到 HTTPS 端点,或者 pull 用客户端库。
DeveloperNotification 里的业务字段是互斥的,同一时刻只会出现其中一个:
subscriptionNotificationoneTimeProductNotificationvoidedPurchaseNotificationpendingRefundReviewNotificationtestNotification
acknowledge 是硬性要求,窗口按方案时长折半
新订阅购买后 3 天内未确认,用户会被自动退款,Google Play 撤销该笔购买。这条不做,钱直接退回去。
预付费方案的窗口更紧:
- 一周或更长的方案:3 天内确认
- 短于一周的方案:必须在方案时长的一半内确认。3 天的方案只有 1.5 天窗口
Play 结算库提供了 acknowledgePurchase() 和 isAcknowledged(),但建议放在后端处理,客户端断开、重装、进程被杀都会让窗口白白流走。
一次性商品的确认走另外两个端点:消耗型用 purchases.products.consume,非消耗型用 purchases.products.acknowledge,同样提交到后端。
验单接口:subscriptionsv2.get,别再碰 subscriptions.get
订阅的验单事实来源是 purchases.subscriptionsv2.get。purchases.subscriptions.get 已经废弃,只为向后兼容保留,新集成不要用。purchases.subscriptions 端点的其他方法仍然可用。
订阅资源里几个关键字段:
subscriptionStateacknowledgementState:PENDING/ACKNOWLEDGEDlineItems[].productId、lineItems[].expiryTimeautoRenewingPlan.autoRenewEnabledofferPhaselatestOrderIdExternalAccountIdentifiers:obfuscatedAccountId、obfuscatedProfileId
购买令牌有效期与续订
购买令牌从订阅注册起,到订阅过期后 60 天内有效,超过这个范围就不能再调 Developer API 了。
续订行为:非分期的自动续订会发 SUBSCRIPTION_RENEWED,续订不需要再次确认。
notificationType 常用取值
订阅类:
1 SUBSCRIPTION_RECOVERED
2 SUBSCRIPTION_RENEWED
3 SUBSCRIPTION_CANCELED
4 SUBSCRIPTION_PURCHASED
5 SUBSCRIPTION_ON_HOLD
6 SUBSCRIPTION_IN_GRACE_PERIOD
7 SUBSCRIPTION_RESTARTED
9 SUBSCRIPTION_DEFERRED
10 SUBSCRIPTION_PAUSED
12 SUBSCRIPTION_REVOKED
13 SUBSCRIPTION_EXPIRED
17 SUBSCRIPTION_ITEMS_CHANGED
20 SUBSCRIPTION_PENDING_PURCHASE_CANCELED
22 SUBSCRIPTION_PRICE_STEP_UP_CONSENT_UPDATED
一次性商品只有两个:
1 ONE_TIME_PRODUCT_PURCHASED
2 ONE_TIME_PRODUCT_CANCELED
状态机:宽限期、静默宽限期、账号保留
订阅状态包括 SUBSCRIPTION_STATE_ACTIVE、IN_GRACE_PERIOD、ON_HOLD、CANCELED、EXPIRED。
宽限期(grace period)默认对自动续订基础方案启用,宽限期内用户仍应有权限。
静默宽限期可以设为 0 天,但 Play 至少会等 1 天重试付款。这 24 小时内订阅保持 ACTIVE,期间不发宽限期 RTDN;之后可能收到 ON_HOLD、CANCELED、EXPIRED 或 RENEWED。
账号保留(on hold)默认对基础方案和分期方案启用,时长 = 60 天减宽限期。on hold 期间 queryPurchasesAsync() 不返回订阅,应阻止访问;恢复后原购买令牌不变,会收到 SUBSCRIPTION_RECOVERED;从账号保留恢复后,结算日期变成恢复当天。
状态流转:宽限期结束后未修正付款进入 on hold;on hold 结束仍未修正,发 SUBSCRIPTION_CANCELED,随后立即发 SUBSCRIPTION_EXPIRED。这两条通知挨着到,后端要么幂等处理,要么把 CANCELED 和 EXPIRED 合并成一次终态更新。
29/30/31 日的订阅会顺延到 28 号
订阅日在 29、30、31 日的,在下一个非闰年二月会先变成 2 月 28 日,之后每月 28 号续订。做权益到期计算时不要把"每月同一天"当成不变量。
重新订阅:linkedPurchaseToken 与 outOfAppPurchaseContext
应用外升级等操作会签发新的购买令牌,可能带 linkedPurchaseToken。
如果原订阅已完全过期,则没有 linkedPurchaseToken,此时响应里会有 outOfAppPurchaseContext(仅出现在未确认的重新订阅里),提供 expiredExternalAccountIdentifiers 和 expiredPurchaseToken,用来把新购买关联到正确用户。之后仍需调 purchases.subscriptions.acknowledge 确认,否则同样会被自动退款。
退款与作废
VoidedPurchaseNotification 包含:
purchaseTokenorderIdproductType:1 订阅 / 2 一次性refundType:1 全额 / 2 基于数量的部分退款
也可以用 Voided Purchases API 主动拉取。
待审核退款走 PendingRefundReviewNotification,字段是 pendingRefundToken、orderId、refundReason(仅 CHARGEBACK=7)、obfuscatedAccountId / obfuscatedProfileId。这条必须在 24 小时内调用 ReviewRefund 提供建议与使用证据,超时等于放弃申诉机会。
一条完整的处理链
购买令牌全局唯一,可以直接拿来做数据库主键。
正式流程:
- 解 base64 拿到
DeveloperNotification - 校验
purchaseToken未被处理过(配合message.messageId查重) - 调
purchases.products.get/purchases.subscriptionsv2.get向 Google 核实 - 授予权益
consume或acknowledge
第 2 步和第 3 步之间是幂等和配额的平衡点:查重放在前面能省 API 调用,但状态更新最好以 API 返回为准,别只信通知里那个 notificationType。