StoreKit — Transaction
This article is extracted from the chat log with AI. Please identify it with caution.

在 StoreKit 框架中,Transaction 是表示应用内购买交易的核心类型,用于管理购买状态、验证收据信息以及处理订阅生命周期。它在 StoreKit 2(iOS 15+)中被重新设计,提供了更简洁的 API 和更高的安全性。以下是 Transaction 的详细介绍:


1. Transaction 的定义与作用 #

  • 表示购买行为
    Transaction 封装了用户的一次购买行为,包含购买的商品、时间、状态、订阅信息等元数据。
  • 验证购买真实性
    通过 Apple 的签名机制(JWS)验证交易是否合法,防止伪造购买。
  • 管理订阅状态
    追踪订阅的激活、续订、过期或退款等状态变化。

2. Transaction 的结构体 #

Transaction 是一个结构体(struct),包含以下关键属性:

核心属性 #

属性类型说明
idUInt64交易唯一标识符
productIDString购买的商品或订阅的 ID
purchaseDateDate购买时间
originalPurchaseDateDate原始购买时间(针对续订)
environmentEnvironment环境(沙盒/生产)
hasPurchasedPromotionalOfferBool是否使用了促销优惠
appBundleIDString应用 Bundle ID
revocationDateDate?退款或撤销时间(若存在)
expirationDateDate?订阅过期时间(若存在)

订阅相关属性 #

属性类型说明
subscriptionSubscription?订阅的详细信息(周期、续订状态等)
isUpgradedBool是否由用户升级订阅导致

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. 注意事项 #

  1. 未完成交易
    需确保所有交易标记为完成(finish()),否则可能导致购买卡顿。

    await transaction.finish()
    
  2. 沙盒测试延迟
    沙盒环境中的订阅过期/续订可能有最大 30 分钟的延迟。

  3. 兼容性
    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,开发者可以更安全、高效地管理应用内购买和订阅逻辑。

本文共 1336 字,创建于 May 5, 2025
相关标签: Xcode, ByAI, StoreKit