在 StoreKit 框架中,Transaction
是表示应用内购买交易的核心类型,用于管理购买状态、验证收据信息以及处理订阅生命周期。它在 StoreKit 2(iOS 15+)中被重新设计,提供了更简洁的 API 和更高的安全性。以下是 Transaction
的详细介绍:
1. Transaction 的定义与作用 #
- 表示购买行为
Transaction
封装了用户的一次购买行为,包含购买的商品、时间、状态、订阅信息等元数据。 - 验证购买真实性
通过 Apple 的签名机制(JWS)验证交易是否合法,防止伪造购买。 - 管理订阅状态
追踪订阅的激活、续订、过期或退款等状态变化。
2. Transaction 的结构体 #
Transaction
是一个结构体(struct
),包含以下关键属性:
核心属性 #
属性 | 类型 | 说明 |
---|---|---|
id | UInt64 | 交易唯一标识符 |
productID | String | 购买的商品或订阅的 ID |
purchaseDate | Date | 购买时间 |
originalPurchaseDate | Date | 原始购买时间(针对续订) |
environment | Environment | 环境(沙盒/生产) |
hasPurchasedPromotionalOffer | Bool | 是否使用了促销优惠 |
appBundleID | String | 应用 Bundle ID |
revocationDate | Date? | 退款或撤销时间(若存在) |
expirationDate | Date? | 订阅过期时间(若存在) |
订阅相关属性 #
属性 | 类型 | 说明 |
---|---|---|
subscription | Subscription? | 订阅的详细信息(周期、续订状态等) |
isUpgraded | Bool | 是否由用户升级订阅导致 |
3. Transaction 的类型 #
StoreKit 2 引入了两种 Transaction
类型,均基于 JWS(JSON Web Signature) 格式,确保数据安全:
(1) JWS 类型 #
JWS
是 Apple 签名后的字符串,包含交易数据的完整签名信息。- 用途:直接传递给服务器进行验证,无需本地解析。
- 示例:
let jwsString = transaction.jwsRepresentation
(2) 解码后的 Transaction #
- 通过
Transaction(verifiedJWS:)
解码 JWS 字符串得到可读的Transaction
对象。 - 用途:在客户端直接读取交易信息。
- 代码示例:
if let transaction = try? Transaction(verifiedJWS: jwsString) { print("Product ID: \(transaction.productID)") }
4. 如何获取 Transaction #
(1) 当前有效的订阅或权益 #
使用 Transaction.currentEntitlements
获取用户当前有效的购买:
let entitlements = await Transaction.currentEntitlements
for case let transaction in entitlements {
if transaction.productID == "premium_subscription" {
// 处理有效订阅
}
}
(2) 监听交易更新 #
通过 Transaction.updates
监听交易状态的实时变化(如续订、退款):
Task {
for await update in Transaction.updates {
switch update {
case .verified(let transaction):
// 处理已验证的交易
case .unverified(_, let error):
// 处理未验证的交易(可能伪造)
}
}
}
5. 验证 Transaction #
(1) 客户端验证 #
在客户端使用 Transaction(verifiedJWS:)
自动验证签名:
do {
let transaction = try Transaction(verifiedJWS: jwsString)
// 验证成功
} catch {
// 验证失败(签名无效或数据篡改)
}
(2) 服务端验证 #
将 JWS 发送到服务器,使用 Apple 的公有密钥验证签名:
# 服务器端调用 Apple API 验证
POST https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/{transactionID}
6. 处理常见场景 #
(1) 检查订阅是否有效 #
if let expirationDate = transaction.expirationDate,
expirationDate > Date.now {
// 订阅有效
} else {
// 订阅已过期
}
(2) 处理退款或撤销 #
if let revocationDate = transaction.revocationDate {
// 用户已退款,需撤销权益
}
(3) 恢复购买后更新状态 #
await AppStore.sync()
let entitlements = await Transaction.currentEntitlements
// 更新本地状态
7. 与传统 StoreKit 1 的区别 #
特性 | StoreKit 1 (SKPaymentTransaction) | StoreKit 2 (Transaction) |
---|---|---|
数据格式 | 原始收据(需手动解析) | JWS(签名完整,易验证) |
订阅管理 | 依赖服务器通知 | 本地直接读取 expirationDate |
验证方式 | 需调用服务器接口 | 客户端自动验证 |
API 复杂度 | 复杂(需处理队列、监听) | 简洁(基于 async/await) |
8. 注意事项 #
未完成交易
需确保所有交易标记为完成(finish()
),否则可能导致购买卡顿。await transaction.finish()
沙盒测试延迟
沙盒环境中的订阅过期/续订可能有最大 30 分钟的延迟。兼容性
StoreKit 2 仅支持 iOS 15+、macOS 12+ 等新系统,需做好旧版本回退逻辑。
9. 完整代码示例 #
import StoreKit
// 获取当前有效订阅
func checkActiveSubscriptions() async {
let entitlements = await Transaction.currentEntitlements
for case let transaction in entitlements {
if let subscription = transaction.subscription {
if subscription.expirationDate > Date.now {
print("用户订阅有效")
}
}
}
}
// 监听交易更新
func observeTransactionUpdates() {
Task {
for await update in Transaction.updates {
switch update {
case .verified(let transaction):
print("交易更新: \(transaction.productID)")
await transaction.finish()
case .unverified(let transaction, let error):
print("交易未验证: \(error)")
}
}
}
}
10. 参考文档 #
通过合理使用 Transaction
,开发者可以更安全、高效地管理应用内购买和订阅逻辑。