WWDC21: StoreKit 2 简介 Meet StoreKit 2

发布
更新
字数 1055
阅读 6 分钟
阅读量 2000

新的 StoreKit 2 包含 Products / Purchases / Transaction Info / Transaction history / Subscription status,基于 Swift async/await 设计,只需一行代码即可完成支付,还可以指定 appAccountToken 标识交易,打通自建账号系统;此外借由 automatic validation,可以方便的在 iOS 端完成支付验证,当然自定义验证依然支持;通过监听 transaction update,可以更好的处理一些需要额外验证操作的交易。

使用 StoreKit configuration 文件测试

请求、支付、响应

init() {
    // ...
   
    // 监听交易更新
    taskHandle = listenForTransactions()
    
    async {
        // 初始化时即查询
        await requestProducts()
    }
}

@MainActor
func requestProducts() async {
    do {
        let storeProducts = try await Product.request(with: Set(productIdToEmoji.keys))
        
        var newCars: [Product] = []
        var newSubscriptions: [Product] = []
        var newFuel: [Product] = []
        
        for product in storeProducts {
            switch product.type {
            case .consumable:
                newFuel.append(product)
            case .nonConsumable:
                newCars.append(product)
            case .autoRenewable:
                newSubscriptions.append(product)
            default:
                print("Unknown product")
            }
        }
        
        // Sort products by price.
        cars = sortByPrice(newCars)
        subscriptions = sortByPrice(newSubscriptions)
        fuel = sortByPrice(newFuel)
    } catch {
        print("Failed product request: \(error)")
    }
        
}

func purchase(_ product: Product) async throws -> Transaction? {
    // 可以通过指定 `appAccountToken` 实现与自己的账号系统同步
    let result = try await product.purchase(options: [.appAccoutnToken(currentAccountToken)])
    
    switch result {
    // StoreKit 2 会完成 transaction 的验证
    case .success(let verification):
        let transaction = try checkVerified(verification)
        
        // 更新已支付的内容状态以及 UI
        await updatePurchasedIdentifiers(transaction)
        
        // 告知 StoreKit 完成订单
        await transaction.finish()
        
        return transaction
    // 有时用户会做一些额外的验证操作,例如儿童需要父母通过,就会返回 `.pending` 状态,
    // 这些步骤完成后,支付操作完成,因此还需要监听 transaction updates,见 `listenForTransactions()`
    case .userCancelled, .pending:
        return nil
    default:
    }
}

func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
    switch result {
    case .unverified:
        // 如果订单验证失败,抛出一个错误,可以通过 UI 告知用户
        throw StoreError.failedVerification
    case .verified(let safe):
        return safe
    }
}

func isPurchased(_ productIdentifier: String) async throws -> Bool {
    guard let result = await Transaction.latest(for: productIdentifier) else {
        return false
    }
    
    let transaction = try checkVerified(result)
    
    return transaction.revocationDate == nil && !transaction.isUpgraded
}

/// 监听交易更新,在 app 启动时开启监听
func listenForTransactions() -> Task.Handle<Void, Error> {
    return detach {
        for await result in Transaction.listener {
            do {
                // 和支付响应一样,检查支付验证结果
                let transaction = try self.checkVerified(result)
                
                // 更新已支付的内容状态以及 UI
                await updatePurchasedIdentifiers(transaction)
                
                await transaction.finish()
            } catch {
                print("Transaction failed verification")
            }
        }
    }
}
    

为了测试交易监听,需要在 Xcode 测试环境中开启 Ask To Buy 来模拟生成带有 .pending 状态的交易。点击 configuration 文件,在 Editor 菜单开启 Ask To Buy,重新运行 App,再次点击支付,会弹出一个提示界面,点击 Ask ,回到 Xcode,打开 Xcode transaction manager,可以看到待处理交易,选中,点击右上角的 Approve 按钮即可。

交易历史

当 App 安装后,全部交易记录就准备好,以备随时获取。不同设备上的交易历史是实时自动同步的。可以使用上文中的示例,在 App 启动时注册监听,来响应更新。所以,基于新的 StoreKit 2,Restore 按钮不再是必选项了。

因为在 StoreKit 2 中所有的更新等都是自动完成的,但为了以防万一,如果用户觉得自己的交易没有被加载,应用可以使用 Sync API,允许用户主动触发同步。

订阅状态

Latest transaction

近期交易

Renewal state

枚举类型,可以方便的检查该订阅是否有效、过期等

@MainActor
func updateSubscriptionStatus() async {
    do {
        guard let product = store.subscriptions.first, let statuses = try await product.subscription?.status else { return }
        
        // 因为状态包含同一项目等多种支付状态数据,例如个人、家庭共享等
        var highestStatus: Product.SubscrriptionInfo.Status? = nil
        var highestProduct: Product? = nil
        
        for status in statuses {
            switch status.state {
            case .expired, .revoked:
                continue
            default:
                let renewalInfo = try store.checkVerified(status.renewalInfo)

                guard let newSubscription = store.subscriptions.first(where: { $0.id == renewalInfo.currentProductID }) else {
                    continue
                }

                guard let currentProduct = highestProduct else {
                    highestStatus = status
                    highestProduct = newSubscription
                    continue
                }

                let highestTier = store.tier(for: currentProduct.id)
                let newTier = store.tier(for: renewalInfo.currentProductID)

                if newTier > highestTier {
                    highestStatus = status
                    highestProduct = newSubscription
                }
            }
        }
        
        status = highestStatus
        currentSubscription = highestProduct
    } catch {
        print("Could not update subscription status \(error)")
    }
}

Renewal info

详细信息

via WWDC 2021:Meet StoreKit2 代码摘自 Implementing a Store In Your App Using the StoreKit API