WWDC Quick Look 💓 By SwiftGGTeam
Architecting for subscriptions

Architecting for subscriptions

观看原视频

Highlight

Apple 建议把订阅权限判断收敛到服务端 entitlement engine,用 receipt、server-to-server notifications 和业务上下文计算用户状态,再把简化的 entitlement payload 安全返回客户端。

核心内容

很多订阅系统最早只问一个问题:这个用户现在有没有权限。实现上通常看 product_idexpires_date。到期日在未来就解锁,到期日在过去就锁定。

这个模型很快会失效。用户可能取消自动续订,但本期服务还没结束;也可能扣款失败,Apple 仍在 Billing Retry 中继续尝试收费;如果 app 开启 Grace Period,用户在宽限期内仍应获得服务。只看 active 或 inactive,会错过挽留、付款修复和升级提示的时机。

这场 session 给出的答案是服务端 entitlement engine。它把 receipt、server-to-server notifications、订阅商品、业务上下文整理成一份简单结果:用户能不能访问服务,处在什么状态,客户端应该展示什么消息或行动入口。

这样做的好处是职责清楚。客户端负责发起购买、展示权益和接收服务端返回的 entitlement payload;服务端负责验证 receipt、合并通知、维护状态,并在状态变化时重新计算权限。订阅业务以后增加新商品、新优惠或新计费状态,也能先改服务端逻辑,不必等待客户端发版。

详细内容

从订阅旅程建模状态

01:21)演讲先拆掉 active/inactive 的二分法。一个月订阅在 5 月 1 日购买后,后续可能正常续订,也可能取消自动续订、扣款失败后恢复、进入 Billing Retry,或在 Grace Period 内恢复。每条路径都会改变服务端该保存的状态和客户端该给出的体验。

关键点:

  • 初次购买会触发 initial buy 的 server-to-server notification。
  • 用户关闭自动续订后,本期仍可使用服务,但后续会通过 expires_dateexpiration_intent 表现为主动流失。
  • Billing Retry 表示 Apple 仍在尝试收款,客户端适合引导用户更新 App Store 付款信息。
  • Grace Period 内恢复可以保持原有计费日期连续性,app 应继续提供服务并提示剩余时间。

用 receipt 和通知喂给 entitlement engine

11:43)Apple 把这套服务端逻辑称作 entitlement engine。输入包括 receipt、server notifications 和 app 自己掌握的用户上下文。输出是一份简化的 JSON payload,用来更新用户数据库,也可以安全返回给设备。

关键点:

  • app receipt 是检查交易数据的事实来源,第一步要验证每份 receipt 的真实性。
  • 如果起始 receipt 已经过期,服务端应向 App Store 的 verifyReceipt endpoint 请求最新 receipt info。
  • 订阅数据分散在 latest_receipt_infopending_renewal_info 等字段中,engine 需要把它们合并成可行动的信息。
  • 如果订阅跨多个 app 或平台,服务端还要合并 receipt 之外的状态,避免用户被重复售卖或被错误锁定。

把状态压缩成 cohort 和 entitlement code

15:35)engine 的中间层先定义 subscriber cohorts,再把一堆 receipt 字段收敛成业务状态。演讲中的示例把状态赋成数值,正值代表可以解锁服务,负值代表不应为该商品提供服务;substate 用小数部分区分免费试用、订阅优惠等商品上下文。

关键点:

  • cohort 让服务端把复杂字段组合变成稳定的业务信号。
  • entitlement code 可以存到服务端,也可以作为简单信号发给客户端。
  • 同一个正值不只表示“解锁”,还可以代表更细的状态,例如免费试用期内已关闭自动续订。
  • 这个编码方式只是示例,实际产品可以按自己的订阅分层、优惠策略和计费状态扩展。

为不同状态附加行动

17:48)当 response object 已经包含 entitlement 信息后,服务端可以按状态附加行动。演讲列了几个典型场景:用户关闭自动续订时给保留 offer;Grace Period 中继续解锁服务并提醒更新付款;Billing Retry 中提供有限访问并引导修复账单;已过期用户收到 win-back offer;连续续订的月付用户看到年付升级建议。

关键点:

  • entitlement response 不只是布尔值,还可以携带消息、offer 或下一步行动。
  • Grace Period 和 Billing Retry 的目标相近,都是修复付款,但服务权限不同。
  • win-back push 仍需要 deep link 到 app 内合适的支付页面。
  • 升级建议可以结合续订次数和 subscription group ID,面向忠实月付用户推荐年付商品。

架构从无状态请求迭代到持久化系统

19:55)Apple 推荐把 entitlement engine 做成高可用服务,让状态变化发生时能立即计算最新权限。最小实现可以先让设备每次请求时把 receipt 发给服务端处理。这个版本能支持不少订阅功能,也能作为存储异常或通知漏处理时的回退路径。

关键点:

  • engine 放在服务端后,权限逻辑的错误可以用 receipt 或 mock receipt data 测试,然后直接部署修复。
  • 只做无状态请求时,设备必须每次发送 receipt;没有持久化 receipt data,也难以支持 web 或其他平台访问。
  • 加入 persistent storage 后,服务端可以保存 receipt data 和 entitlement info,再支持 web 与 off-platform 访问。
  • 增加 App Store Server Notifications endpoint 后,服务端能在订阅状态变化时被通知,减少对 verifyReceipt endpoint 的轮询。
  • 如果客户端请求 entitlement data,演讲建议考虑用 JSON Web Signature 证明数据来自自己的服务端。

核心启发

  • 订阅状态控制台:做什么:为客服、运营和工程团队展示每个用户的 subscription state、substate、最近 receipt 字段和 server notification 时间线。为什么值得做:session 反复强调状态来自多个 receipt 字段和用户行为,控制台能减少“为什么用户被锁住”的排查成本。怎么开始:先按 latest_receipt_infopending_renewal_info 和通知事件生成只读时间线,再把 entitlement code 映射成人能读懂的状态标签。
  • 付款修复入口:做什么:在 Billing Retry 和 Grace Period 中展示不同的付款修复提示。为什么值得做:Billing Retry 适合引导更新付款信息,Grace Period 还要显示服务剩余时间。怎么开始:让 entitlement response 携带 billing action、服务是否继续开放、宽限期剩余信息,再由客户端 deep link 到 App Store 账号付款设置。
  • 取消续订后的挽留 offer:做什么:识别仍在有效期内但已关闭自动续订的用户,展示保留 offer 或后续订阅优惠。为什么值得做:演讲给出的 retention example 正是利用 auto-renewal status 和当前订阅商品做挽留。怎么开始:在 entitlement engine 中为 auto-renew off 的 active cohort 加上 retention message,并让 paywall 只在该状态下显示。
  • 忠实用户年付升级推荐:做什么:把连续续订多期的月付用户引导到年付订阅。为什么值得做:session 建议用续订次数和 subscription group ID 识别忠实用户,再提供折扣升级。怎么开始:在服务端为同一 subscription group 聚合 renewal count,达到阈值后在 entitlement payload 中加入 annual upgrade action。
  • 跨平台权限兜底:做什么:让 iOS、web 和其他平台都请求同一个服务端 entitlement endpoint。为什么值得做:演讲指出持久化 receipt data 后才能支持 web 和 off-platform 访问,并能在通知漏处理时用存储数据保持准确。怎么开始:先把设备发送 receipt 的无状态接口落地,再逐步增加 receipt storage、notification endpoint 和数据库同步流程。

关联 Session

评论

GitHub Issues · utterances