Highlight
StoreKit 2 用 Swift async/await 重写了整个内购流程:
Product.products(for:)一行代码获取商品、product.purchase()一行代码发起购买、每笔交易自带 JWS 签名且自动验证,订阅状态通过currentEntitlements直接查询。
核心内容
你在做一个内购系统。StoreKit 1 的代码你写过:创建 SKProductsRequest、实现代理方法、处理 SKPaymentTransaction 的状态机、在沙盒和正式环境之间来回切换测试、收据解析那一长串代码让你头疼。
StoreKit 2 说:这些都可以更简单。
新产品请求一行代码。新购买流程一行代码。交易验证自动完成。订阅状态直接查询。所有 API 都是 Swift-native 的 async/await,代码读起来像同步逻辑。
而且 StoreKit 2 和 StoreKit 1 完全兼容。旧交易自动同步到新 API,你可以渐进式迁移。
详细内容
获取产品信息
(03:13)
StoreKit 2 的 Product 类型替代了 SKProduct,增加了产品类型、订阅信息等字段:
let products = try await Product.products(for: ["fuel.small", "car.sports", "nav.pro"])
// 按类型分组
let consumables = products.filter { $0.type == .consumable }
let nonConsumables = products.filter { $0.type == .nonConsumable }
let subscriptions = products.filter { $0.type == .autoRenewable }
关键点:
Product.products(for:)是静态方法,传入产品 ID 集合- 返回的
Product包含type属性,自动识别 consumable / non-consumable / subscription - 支持
BackingValue包装类型,未来新增字段也能在旧 SDK 上访问
发起购买
(03:31)
购买变成 Product 的实例方法,结果用 PurchaseResult 枚举:
let result = try await product.purchase()
switch result {
case .success(let verification):
// 购买成功,verification 包含签名交易
let transaction = try checkVerified(verification)
await deliverContent(for: transaction)
await transaction.finish()
case .userCancelled:
// 用户取消
break
case .pending:
// 等待验证(如家长批准、银行验证)
break
case .unverified(_, let error):
// 验证失败
print("Purchase failed: \(error)")
}
关键点:
purchase()返回PurchaseResult,覆盖成功、取消、等待、失败四种状态- 成功时返回的
verification包含自动验证结果 - 最后必须调用
transaction.finish(),否则交易会一直留在队列中
自动交易验证
(11:19)
StoreKit 2 的每笔交易都带有 JWS(JSON Web Signature)签名。系统自动验证签名是否来自 App Store:
func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
switch result {
case .verified(let safe):
return safe
case .unverified(_, let error):
throw error
}
}
关键点:
VerificationResult有.verified和.unverified两个 case- 系统已验证签名来自 Apple、匹配你的 bundle ID、生成于当前设备
- 你也可以选择自行验证 JWS,使用 CryptoKit 和证书链
App Account Token:关联你的用户系统
(04:20)
如果你的 App 有自己的账户系统,可以用 appAccountToken 把 StoreKit 交易和你的用户关联:
let accountToken = UUID(uuidString: user.id)!
let result = try await product.purchase(options: [.appAccountToken(accountToken)])
关键点:
appAccountToken是 UUID 格式,由你生成- 购买完成后,token 会出现在交易的
appAccountToken字段中 - 跨设备同步,即使换设备也能追踪到同一账户的购买记录
监听交易更新
(15:22)
用 Transaction.updates 监听所有交易更新(包括其他设备上的购买、家长批准后的完成、退款等):
Task {
for await result in Transaction.updates {
guard let transaction = try? checkVerified(result) else { continue }
if transaction.revocationDate == nil {
await deliverContent(for: transaction)
}
await transaction.finish()
}
}
关键点:
Transaction.updates是无限异步序列,App 启动后就要开始监听- 处理退款(
revocationDate非 nil)和升级(isUpgraded为 true)的情况 - 必须在 App 启动时就开始监听,否则可能错过更新
查询交易历史和当前权益
(19:36)
StoreKit 2 提供三种查询方式:
// 1. 获取所有历史交易
for await result in Transaction.all {
let transaction = try checkVerified(result)
// 处理每条历史交易
}
// 2. 获取某个产品的最新交易
if let result = await Transaction.latest(for: "nav.pro") {
let transaction = try checkVerified(result)
// 检查是否仍有效
}
// 3. 获取当前有效权益(最常用)
for await result in Transaction.currentEntitlements {
let transaction = try checkVerified(result)
// 解锁对应内容
}
关键点:
currentEntitlements只返回当前有效的非消耗品和活跃订阅- 已退款的交易不会出现在
currentEntitlements中 - 消耗品不包含在内,因为它们不是持久权益
- 用户换设备后首次打开 App 就能自动获取权益,无需手动恢复
订阅状态查询
(22:47)
订阅状态包含三部分:最新交易、续订状态、续订信息:
let statuses = await product.subscription?.status
for status in statuses {
// 1. 最新交易
let latestTransaction = try checkVerified(status.transaction)
// 2. 续订状态:subscribed / expired / inGracePeriod / inBillingRetryPeriod / revoked
switch status.state {
case .subscribed:
// 正常订阅中
case .expired:
// 已过期
case .inGracePeriod:
// 宽限期内
default:
break
}
// 3. 续订信息(也带 JWS 签名)
let renewalInfo = try checkVerified(status.renewalInfo)
if renewalInfo.autoRenewStatus == .off {
// 用户关闭了自动续订,可以展示挽留优惠
}
if renewalInfo.autoRenewProductID != product.id {
// 用户降级了订阅等级
}
}
关键点:
status返回数组,因为用户可能同时有自有订阅和家庭共享订阅- 应该取数组中最高等级的服务
renewalInfo包含autoRenewStatus、autoRenewProductID、expirationReason等关键字段- 续订信息也带 JWS 签名,系统自动验证
JWS 签名结构
(34:03)
StoreKit 2 使用 JSON Web Signature 标准,包含三部分:
- Header:包含算法(ECDSA)和证书链(x5c)
- Payload:交易 ID、产品 ID、购买日期等核心数据
- Signature:用 header 和 payload 生成的签名
自行验证时的额外检查:
- 确认 payload 中的 bundle ID 匹配你的 App
- 用
AppStore.deviceVerificationID做设备验证:将 nonce 和 device ID 拼接后做 SHA384 哈希,与 payload 中的设备验证字段比对
关键点:
- 证书链包含在 JWS 数据中,验证无需联网
- StoreKit 2 已自动完成验证,大部分场景不需要自行实现
- 服务端验证参考 App Store Server API
核心启发
-
新项目直接用 StoreKit 2 做内购。async/await 让购买流程的代码从几十行代理方法压缩到几行顺序代码。自动验证省去了收据解析的麻烦。入口 API:
Product.products(for:)+product.purchase()。 -
用
currentEntitlements替代手动恢复购买。用户换设备后首次打开 App 就能自动获取所有有效权益,不需要点击”恢复购买”按钮。消耗品除外。入口 API:Transaction.currentEntitlements。 -
在订阅过期前做挽留。通过
renewalInfo.autoRenewStatus检测用户是否关闭了自动续订,如果是,在过期前展示 win-back 优惠。入口 API:Product.subscription?.status+.renewalInfo。 -
用
appAccountToken关联自有账户系统。如果你的 App 有独立登录系统,生成 UUID 作为appAccountToken随购买发送,后续在交易记录中就能追踪到具体用户。入口 API:.appAccountToken(UUID)purchase option。 -
App 启动后立即启动交易监听。
Transaction.updates是无限异步序列,必须在 App 启动时就开始监听,否则可能错过其他设备上的购买完成、家长批准、退款等事件。入口 API:Task { for await result in Transaction.updates { ... } }。
关联 Session
- Support customers and handle refunds — StoreKit 2 客户支持、退款处理和订阅管理
- In-app purchases on the server — App Store Server API 和服务器端验证
- What’s new in Wallet and Apple Pay — Apple Pay 新特性和支付 sheet 改进
- Meet StoreKit for SwiftUI — SwiftUI 中的 StoreKit 集成
评论
GitHub Issues · utterances