App Store 服务端 API 深度解析
Dive into App Store server APIs for In-App Purchase
2025年6月9日
一句话判断
appTransactionId 终于统一了所有交易对象的标识符体系——如果你还在用 originalTransactionId 做跨产品的用户关联,是时候迁移了。
这场 Session 讲了什么
这场 Session 围绕 App Store 服务端 API 的三个核心职责展开:管理内购、签名请求、参与退款决策。主讲人 Riyaz 逐个介绍了今年的实质性更新。
管理内购方面,最大的更新是 appTransactionId——一个全局唯一的、每个 Apple Account per app 的标识符。它出现在 AppTransaction、JWSTransaction 和 JWSRenewalInfo 所有交易对象中,无论用户购买了多少种不同产品,同一个 Apple Account 在同一个 app 里的所有交易共享同一个 appTransactionId。这解决了长期以来用 originalTransactionId 做用户关联时跨订阅产品需要额外逻辑的痛点。
另一个重要更新是 Set App Account Token 端点,允许在服务端为任何交易设置或更新 appAccountToken,包括用户在 app 外完成的交易(如兑换 offer code、App Store 内 promoted purchase)。这个端点适用于所有产品类型,且对自动续期订阅,设置的 token 会延续到未来的续期。
签名请求方面,所有签名格式统一为 JWS(JSON Web Signature)。promotional offer 和 introductory offer 都支持 JWS 签名,新格式比旧版更简洁(更少的输入参数)。Advanced Commerce API 也支持发送签名的 JWS 应用内请求。
退款方面,Send Consumption Information V2 端点大幅简化——从 12 个输入字段减到 5 个(3 必填、2 可选),新增 prorated refund(按比例退款)偏好,支持所有产品类型(之前只支持 consumable 和 auto-renewable subscription)。REFUND 通知新增 refundPercentage 和 revocationType 字段,让服务端能精确执行按比例撤销。
值得深挖的点
appTransactionId vs originalTransactionId vs appAccountToken:三者的定位与选择
这三个标识符容易混淆,Session 做了清晰的对比:
transactionId:App Store 生成,标识一个具体的购买事件(包括内购、恢复、订阅续期)。每次购买都不同。
originalTransactionId:App Store 生成,标识自动续期订阅的原始购买,在整个订阅生命周期的所有续期中保持一致。不适合跨订阅产品关联,因为不同订阅产品有不同的 originalTransactionId。
appAccountToken:开发者生成的 UUID,在 StoreKit 购买时设置或通过新的 Set App Account Token 端点设置。用于关联 App Store 交易与你的用户账户。但 Family Sharing 交易中不可用。
appTransactionId:App Store 生成,每个 Apple Account per app 全局唯一,在所有交易对象中一致。Family Sharing 中每个成员有独立的 appTransactionId。跨重下载、退款、重新购买、storefront 变更都保持不变。
Apple 的建议是优先使用 appTransactionId 作为统一标识符。实际场景中,你可以用 appTransactionId 做一次关联(app 下载时关联 appTransactionId 与你的用户账户),之后所有交易对象都能通过 appTransactionId 关联回同一个用户。这比以前需要分别处理 transactionId 和 originalTransactionId 的逻辑简洁得多。
Trade-off:appTransactionId 是新字段,旧的交易数据中没有。如果你的系统已经基于 originalTransactionId 建立了大量关联逻辑,迁移到 appTransactionId 需要处理两套逻辑的共存期。建议新数据用 appTransactionId,旧数据保持不变,逐步迁移。
Prorated Refund 的实现与注意事项
V2 端点新增的 prorated refund 是个重要的能力升级。以前只有”全额退款”或”拒绝退款”两个选项,现在可以支持按消费比例退款。
对于 consumable 产品,你需要提供 consumptionPercentage(单位是 milli-percent,即千分之一百分比,25000 代表 25% 消费量)。App Store 会根据这个比例决定退款金额。
对于 auto-renewable subscription,consumptionPercentage 由 App Store 自动计算(基于订阅剩余时间),你不需要提供。
收到 REFUND 通知后,你需要根据 refundPercentage 执行相应的撤销:全额退款立即撤销全部内容,按比例退款则撤销对应比例的内容(如扣减虚拟货币余额)。
Trade-off:prorated refund 增加了服务端实现的复杂度——你需要维护用户的消费状态并准确报告 consumptionPercentage。但对于用户满意度来说,按比例退款比”全有或全无”更公平,能减少用户投诉。
代码片段
1. 使用 appTransactionId 关联用户
场景:在 app 下载时关联 appTransactionId,后续交易复用。
// App 端获取 AppTransaction
let appTransaction = try await AppTransaction.shared
if case .verified(let transaction) = appTransaction {
// 发送到服务端关联用户账户
await server.associateUser(
appTransactionId: String(transaction.appTransactionId)
)
}
// 服务端:收到 JWSTransaction 时
// 通过 appTransactionId 关联到已有用户
let userId = db.findUser(by: transaction.appTransactionId)
坑:appTransactionId 对于 Family Sharing 中的每个成员是不同的,如果你的 app 支持 Family Sharing,不能用 appTransactionId 作为家庭组的唯一标识。
2. Set App Account Token
场景:用户通过 offer code 在 app 外完成购买后,在服务端关联账户。
// 服务端代码(使用 App Store Server Library)
let client = AppStoreServerAPIClient(...)
try await client.setAppAccountToken(
originalTransactionId: "123456789",
requestBody: AppAccountTokenRequest(
appAccountToken: UUID().uuidString
)
)
坑:Set App Account Token 会覆盖之前为该交易设置的任何 appAccountToken。对于自动续期订阅,新 token 会延续到所有未来的续期。
3. Send Consumption Information V2
场景:响应 CONSUMPTION_REQUEST 通知,报告用户的消费状态和退款偏好。
try await client.sendConsumptionInformation(
transactionId: "987654321",
requestBody: ConsumptionRequestV2(
customerConsented: true,
sampleContentProvided: true,
deliveryStatus: .delivered,
refundPreference: .grantProrated,
consumptionPercentage: 50000 // 50% consumed
)
)
坑:如果用户没有同意共享消费数据(customerConsented 为 false),不要调用这个端点——请求会被拒绝。
最佳实践
- 新项目统一使用 appTransactionId 作为交易关联的主键,避免混用多种标识符导致的逻辑复杂化。
- 使用 App Store Server Library 的 JWS 签名生成器,不要自己实现签名逻辑。
- 对所有产品类型的退款通知都实现处理逻辑,包括 non-consumable 和 non-renewing subscription。
- 在服务端维护准确的消费状态,以便在 CONSUMPTION_REQUEST 时提供精确的 consumptionPercentage。
- V1 的 Send Consumption Information 端点已废弃,新集成直接用 V2。
- 定期检查 App Store Server API 的 GitHub 仓库,Apple 持续更新 server library。
还有什么值得关注
- Get App Transaction Info 端点允许直接在服务端获取 AppTransaction 信息,不需要依赖设备——这对服务端驱动的业务逻辑校验非常有用。
- JWS introductory offer 签名允许按用户、按交易自定义 introductory offer 资格,比以前的全局资格控制更精细。
- appTransactionId 可以作为 Get Transaction History、Get All Subscription Statuses 等流行端点的 transaction ID 参数使用。