Highlight
Apple 在 WWDC 2020 为内购补上退款通知、DID_RENEW 续订通知、家庭共享交易、watchOS StoreKit、SKOverlay 和 SKAdNetwork 2.0,让开发者能用服务器通知与 StoreKit API 管理购买、订阅和推广链路。
核心内容
内购最难处理的情况,往往发生在购买完成之后。用户买了 100 个宝石,又向 Apple 申请退款;用户订阅升级、降级、续费失败、重新恢复;家庭成员打开 App,希望立刻拿到共享权益。开发者如果只在客户端看一次购买结果,很快就会丢掉真实状态。
这场 session 先从服务端补洞。Apple 新增退款通知,让 consumable(消耗型项目)、non-consumable(非消耗型项目)和 non-renewing subscription(非续期订阅)的退款进入 App Store Server Notifications。订阅侧也新增 DID_RENEW,让每一次自动续期成功都有服务器事件。
然后 StoreKit 把这些状态带回设备端。Family Sharing 会为购买者和每个家庭成员生成交易;watchOS 6.2 可以直接发起内购;订阅涨价同意弹窗可以由 App 控制展示时机;SKOverlay 让 App Clip 或完整 App 用系统浮层推广另一个 App。
最后是广告归因。SKAdNetwork 2.0 增加 source app id、redownload 和 conversion value。广告 App 可以在首次启动后更新一个 0 到 63 的转化值,广告网络收到的 postback 仍由 Apple 的公钥验证,并在隐私约束允许时带上更多上下文。
详细内容
退款通知把内容撤销变成服务端事件
(01:04)开场的例子是游戏里的 100 Gems。用户购买后向 Apple 申请退款,如果开发者不知道这笔交易已退款,就无法判断是保留内容、扣回余额,还是给用户展示解释消息。
(03:24)Apple 的做法是新增 Refund notification。它是 App Store Server Notifications 的新事件,会在退款发出后通过 JSON POST 通知开发者;如果端点没有返回 HTTP OK,Apple 会最多重试三次。
refund notification:
bid
password
unified_receipt.latest_receipt_info[].original_transaction_id
unified_receipt.latest_receipt_info[].cancellation_date
unified_receipt.latest_receipt_info[].cancellation_reason
unified_receipt.latest_receipt_info[].product_id
关键点:
original_transaction_id用来定位被退款的原始交易。cancellation_date说明 Apple 什么时候完成退款。cancellation_reason的值为 0 或 1;值为 1 时,Apple 说明用户因为 App 内问题申请退款。bid和product_id用来确认通知属于哪个 App、哪个商品。password是 App 的 shared secret,用来验证 payload 来自 Apple。
(09:11)收到退款事件后,开发者可以做退款监控、App 内消息、限制已退款内容访问。对 consumable,还可以扣减 App 内货币余额。session 明确把取舍交给开发者:动作要和社区健康、经济系统公平性匹配。
订阅状态从轮询转向通知驱动
(11:52)订阅生命周期包含首次订阅、自动续期成功或失败、关闭或开启自动续期、升级、降级、取消等状态。Apple 推荐用 App Store Server Notifications 接收状态更新,而不是频繁轮询 verify receipt。
subscription notification:
auto_renew_product_id
notification_type
password
bid
unified_receipt.latest_receipt
unified_receipt.latest_receipt_info[].original_transaction_id
pending_renewal_info[]
关键点:
notification_type标记事件类型,例如 INITIAL_BUY、INTERACTIVE_RENEWAL、DID_FAIL_TO_RENEW、DID_RECOVER。auto_renew_product_id指向计划自动续期的商品。latest_receipt_info包含最近 100 条交易,可以用original_transaction_id找到活跃订阅。pending_renewal_info存放下一次续期信息。
(16:05)2020 年新增 DID_RENEW。它会在每一次自动续期成功后发送,payload 里有 unified receipt、原始交易 ID、新订阅周期的 transaction ID、过期时间和 auto-renew product id。
(17:14)Apple 还把 promotional offer id 加入 pending renewal info。用户决定在下一次续期使用订阅优惠时,开发者可以提前知道这件事。
(18:00)verify receipt 仍然保留三个位置:服务器故障后的恢复模式、用户上线时立即判断 entitlement(权益)、以及自动续期后的双重确认。推荐做法是先订阅 DID_RENEW,再在订阅过期时间后安排一次 verify receipt 调用;如果 DID_RENEW 正常到达,就取消这次任务。
家庭共享会为家庭成员生成交易
(19:36)Family Sharing 支持 auto-renewable subscriptions(自动续期订阅)和 non-consumable in-app purchases(非消耗型内购),最多让五个额外家庭成员共享一次购买。开发者需要在 App Store Connect 为具体产品开启,开启后不能关闭。
(21:27)共享不是简单复用购买者的 receipt。Apple 会为购买者和每个家庭成员创建交易。家庭成员打开 App 时,transaction queue 里会出现对应交易,receipt 的 latest receipt info 里也会包含共享交易。
Family Sharing APIs and signals:
SKProduct.isFamilySharable
transaction queue entries for family members
unique receipt for each family member
SKPaymentTransactionObserver.paymentQueue(didRevokeEntitlementsForProductIdentifiers:)
关键点:
SKProduct.isFamilySharable让 App 在请求商品信息后判断产品是否支持家庭共享。- 新购买会为家庭成员放入类似 restored purchase 的交易,已有恢复逻辑可以复用。
- 旧的 non-consumable 购买需要 restore purchases,家庭成员才能拿到内容。
paymentQueue(didRevokeEntitlementsForProductIdentifiers:)会在关闭家庭共享、离开家庭组、退款后被调用。- 收到撤销回调后要重新验证 receipt,不能只看 product identifiers 数组就直接撤销所有重叠权益。
StoreKit 扩展到 watchOS、涨价弹窗和 SKOverlay
(27:40)watchOS 6.2 开始支持 StoreKit 和内购。watchOS App 可以观察 payment queue、请求 products、把 payment 加入队列。服务端 receipt validation 仍然可用;本地验证时,取设备标识要用 WKInterfaceDevice,而不是 UIDevice。
(30:02)订阅涨价同意流程也变了。App Store 会在用户打开 App 时展示系统同意弹窗;如果当前时机不合适,App 可以在 SKPaymentQueue delegate 方法里返回 false,之后再调用 SKPaymentQueue.showPriceConsentIfNeeded 展示。
(31:33)SKOverlay 是新的系统浮层,用来展示和推广 App。它比 SKStoreProductViewController 更贴近当前界面,也可以帮助 App Clip 把用户引导到完整 App。
SKOverlay flow:
create configuration
initialize overlay
set delegate
present in UIWindowScene
dismiss from UIWindowScene
animate alongside SKOverlay.TransitionContext
关键点:
- AppClipConfiguration 用于从 App Clip 展示对应完整 App。
- AppConfiguration 可以展示任意 App,并提供 app identifier。
- campaign token 和 provider token 可用于 App Analytics。
- position 可以选择 bottom 或 bottomRaised,后者适合带 tab bar 的界面。
- delegate 的展示和消失回调都在 main queue,可配合 transition context 同步移动自己的 UI。
SKAdNetwork 2.0 给广告归因增加可选上下文
(37:43)SKAdNetwork 涉及三方:广告网络、source app 和 advertising app。广告网络把签名后的广告数据放进 source app;用户安装并打开 advertising app 后,advertising app 初始化 postback;设备在计时器结束后把 postback 发给广告网络。
SKAdNetwork 2.0 additions:
version: 2.0
source app id
redownload
conversion value: 0...63
updateConversionValue
关键点:
- 广告展示数据在 2.0 里需要包含 version 和 source app id。
- advertising app 首次启动时初始化 postback;后续调用不会重复启动流程。
updateConversionValue可以写入 0 到 63 的 6-bit 值,用于表达购买、试用注册、完成关卡等事件。- 转化值只能升高,低于当前值的更新会被忽略。
- postback 里的 source app id 和 conversion value 是可选项,Apple 会在隐私计算允许时返回。
核心启发
-
退款事件台账
- 做什么:为每个
original_transaction_id建一条退款处理记录,记录退款时间、原因、处理动作和用户可见消息。 - 为什么值得做:Refund notification 会在 Apple 完成退款后推送,开发者可以及时处理内容和 App 内经济系统。
- 怎么开始:接入 App Store Server Notifications,保存
cancellation_date、cancellation_reason、product_id,再把处理结果同步到 App。
- 做什么:为每个
-
订阅续期校准任务
- 做什么:用 DID_RENEW 更新订阅周期,同时在过期时间后安排 verify receipt 兜底任务。
- 为什么值得做:session 建议用通知减少轮询,但在服务器故障或通知延迟时仍保留恢复路径。
- 怎么开始:收到 DID_RENEW 就取消对应校准任务;任务触发时调用 verify receipt 并按
original_transaction_id更新 entitlement。
-
家庭共享权益页
- 做什么:在购买页标出哪些产品支持家庭共享,并在家庭成员获得或失去共享权益时刷新 UI。
- 为什么值得做:StoreKit 会为家庭成员生成交易,也会在共享关闭、离开家庭组或退款时触发撤销回调。
- 怎么开始:读取
SKProduct.isFamilySharable,复用 restored purchase 逻辑处理家庭成员交易,收到 revoke 回调后重新验证 receipt。
-
App Clip 到完整 App 的升级入口
- 做什么:在 App Clip 关键任务完成后展示 SKOverlay,引导用户安装完整 App。
- 为什么值得做:SKOverlay 专门支持从 App Clip 过渡到完整 App,浮层能和当前界面一起动画。
- 怎么开始:创建 AppClipConfiguration,设置 overlay delegate,在合适的 window scene 中 present,并用 transition context 调整底部 UI。
-
隐私友好的广告转化分层
- 做什么:把安装后的关键行为映射为 0 到 63 的转化值,例如注册、试用、购买、完成首个关卡。
- 为什么值得做:SKAdNetwork 2.0 允许 advertising app 更新 conversion value,广告网络可在保护隐私的前提下评估质量。
- 怎么开始:为事件定义递增等级,只在新等级高于旧等级时调用
updateConversionValue,服务器处理 postback 时接受缺失 source app id 或 conversion value 的情况。
关联 Session
- Introducing StoreKit Testing in Xcode — 本地测试内购、退款、订阅场景,是验证这篇 session 中服务端状态处理的配套工具。
- Architecting for subscriptions — 深入讲 entitlement 设计,接上 DID_RENEW、verify receipt 和订阅生命周期处理。
- Streamline your App Clip — 讲 App Clip 的交易体验和 SKOverlay 使用场景,可补充本文的 SKOverlay 部分。
- Explore App Clips — 先建立 App Clip 的设计和发现机制背景,再理解 SKOverlay 为什么适合完整 App 引导。
评论
GitHub Issues · utterances