What's new in StoreKit and In-App Purchase
App Store Distribution & Marketing 进阶 20m

StoreKit 和应用内购买的新变化

What's new in StoreKit and In-App Purchase

2024年6月10日

在 Apple 官方观看视频

一句话判断

iOS 18 终于把已完成的消耗品交易纳入交易历史 API,同时新增货币和价格字段、win-back offers 和更强大的 StoreKit Views,这三个改动组合起来让内购管理从”头疼”变成了”基本不用操心”。

这场 Session 讲了什么

StoreKit 在 iOS 18 带来三个方向的重要更新。第一,交易历史 API 的覆盖范围终于补全了——已完成的消耗品交易(finished consumable transactions)现在可以被查询到,不再需要开发者自己维护一套消耗品购买记录。第二,Transaction 和 RenewalInfo 两个数据模型新增了 currencypricerenewalPrice 字段,让你终于可以在 App 内准确展示价格信息。第三,StoreKit Views(去年 WWDC23 引入的内购 UI 组件)增加了 SubscriptionOptionGroup、compact picker 样式等定制能力,让你用更少的代码构建更专业的订阅商店界面。

另外还有 win-back offers——一种新的订阅优惠类型,专门面向流失用户,可以在 App Store 的 Today、Games、Apps 标签页中被 Apple 编辑团队推荐展示。

值得深挖的点

已完成消耗品进入交易历史:消除一个长期的数据盲区

在 iOS 18 之前,消耗品(consumable)一旦调用 finish() 完成交易,就从交易历史中消失了。这意味着如果你需要一个”购买历史”页面,消耗品的部分必须自己用数据库或文件来记录。更麻烦的是,如果用户换设备或重装 App,未消耗的消耗品可以通过 transaction listener 恢复,但已完成的就彻底找不回来了。

iOS 18 通过一个新的 Info.plist key SKIncludeConsumableInAppPurchaseHistory 开启这个能力。它是 opt-in 的——你需要主动设置才会生效,不会影响已有行为。开启后,通过 Transaction.updates 和 transaction history API 都能收到已完成的消耗品交易。

为什么是 opt-in 而不是默认开启?因为消耗品的交易量可能非常大(想象一个游戏里的金币购买),默认全部暴露可能带来性能和存储方面的意外。Apple 选择让开发者自己评估是否需要这个功能,是合理的保守策略。

SubscriptionOptionGroup:让 StoreKit Views 真正能用

去年推出的 SubscriptionStoreView 一行代码就能展示订阅选项,但定制能力很弱——如果你的订阅有多个层级(比如”基础版”和”高级版”),UI 上只能平铺展示。iOS 18 的 SubscriptionOptionGroup 让你可以按条件分组,框架自动生成带标签页的 UI。

更实用的是 SubscriptionOptionGroup.Set——你只需要提供一个从产品到分组键的映射闭包,框架就自动为每个唯一值创建一个分组。配合 compact picker 样式,可以在极小的空间内展示复杂的订阅选项组合。

这个 API 的设计思路是”声明式分组,自动布局”。你只管描述”这些产品属于高级组、那些属于基础组”,UI 的标签页切换、选中状态、营销内容的动态切换,框架全包了。这比去年”要么全用默认 UI,要么自己从头写”的处境好了太多。

代码片段

开启已完成消耗品交易历史

场景:在 Info.plist 中配置,让交易历史 API 包含已完成的消耗品。

<!-- Info.plist -->
<key>SKIncludeConsumableInAppPurchaseHistory</key>
<true/>

坑:这只是让 transaction history 能查到已完成消耗品,不影响 finish() 的调用时机——消耗品被消费后仍然要尽快 finish()

按服务层级分组展示订阅选项

场景:流媒体 App 有”高级”和”基础”两个层级,每个层级有月付和年付选项。

SubscriptionStoreView(groupID: "streaming_pass") {
    // 高级组
    SubscriptionOptionGroup("Premium") { subscription in
        subscription.productLevel == .premium
    } content: {
        PremiumMarketingView()
    }

    // 基础组
    SubscriptionOptionGroup("Basic") { subscription in
        subscription.productLevel == .basic
    } content: {
        BasicMarketingView()
    }
}
.subscriptionStoreControlStyle(.compactPicker)

坑:productLevel 是你在 App Store Connect 里设置的自定义属性,需要在代码里定义对应的 enum 来匹配。

获取交易货币和价格

场景:在购买确认页面展示用户实际支付的价格和货币。

for await transaction in Transaction.updates {
    if case .verified(let t) = transaction {
        // 新增的 currency 和 price 字段
        let price = t.price        // Decimal
        let currency = t.currency  // String (ISO 4217)
        print("支付: \(price) \(currency ?? "")")
    }
}

坑:price 是你在 App Store Connect 配置的价格,不含地区税收调整。实际扣款金额可能不同。这些字段向后兼容到 iOS 15。

最佳实践

新项目:直接上 StoreKit 2 API。把 SKIncludeConsumableInAppPurchaseHistory 默认开启,省去自己维护消耗品购买记录的麻烦。订阅商店 UI 用 SubscriptionStoreView + SubscriptionOptionGroup 搭建,配合 compact picker 样式,用最少代码拿到最专业的效果。

已有项目:如果你还在用 StoreKit 1 的 SKPaymentQueue,强烈建议开始迁移到 StoreKit 2。currencyprice 字段只有在 StoreKit 2 的 Transaction 模型上才有,SKPaymentTransaction 里没有。新增的消耗品交易历史也只在 StoreKit 2 的 transaction API 上暴露。迁移路径虽然需要改代码,但换来的是大量样板代码的删除。

还有什么值得关注

  • Win-back offers 可以在 App Store 的 Today、Games、Apps 标签页被编辑团队推荐展示,是一种新的获客渠道。
  • RenewalInfo 新增了 renewalPricecurrency 字段,让你可以在订阅管理页面展示”下次续费价格”。
  • StoreKit Views 新增了自定义按钮样式和头部/底部 slot,可以插入”恢复购买”和”使用条款”链接。
WWDC 2024