前语

StoreKit 为咱们供给了经过使用程序获得收入的时机。它答应咱们设置使用内购买和订阅的购买流程。StoreKit 2 引进了一种基于现代 Swift 的 API,用于构建类型安全的使用内购买。下面咱们将开端关于 StoreKit 2 的系列文章。

装备项目

首要,咱们必须在项目的 “Signing & Capabilities” 选项卡中装备使用内购买项目。接下来,应该创建一个 StoreKit 装备文件,以便在没有与 App Store 的网络连接的状况下测验使用内购买功用。前往 “File -> New -> File” 并挑选 “StoreKit Configuration File”。

能够创建一个仅本地的装备文件,并将其填充为测验订阅和使用内购买项目。另一种挑选是启用 “Sync this file with an app in App Store Connect” 复选框,从 App Store Connect 获取订阅和使用内购买项目列表。

最终一步是使用预界说的 StoreKit 装备文件运转你的使用程序。需要编辑项目的 scheme,并在运转部分的选项标签中挑选的 StoreKit 装备文件。现在,现已拥有一个彻底装备的项目,答应咱们在 Xcode 中测验使用内购买。

构建付出功用

让咱们开端构建咱们的付出功用,引进 Store 类型来处理与使用内购买相关的一切逻辑。

import StoreKit
@MainActor final class Store: ObservableObject {
    @Published private(set) var products: [Product] = []
    init() {}
    func fetchProducts() async {
        do {
            products = try await Product.products(
                for: [
                    "123456789", "987654321"
                ]
            )
        } catch {
            products = []
        }
    }
}

正如在上面的示例中所看到的,咱们界说了 Store 类型,用于获取和存储将显现在付出屏幕上的产品列表。StoreKit 2 框架供给了 Product 类型,该类型封装了与使用内购买相关的一切逻辑。Product 类型具有一个名为 products 的静态函数,咱们能够使用它来经过供给标识符集合来获取产品列表。

struct ContentView: View {
    @StateObject private var store = Store()
    var body: some View {
        VStack {
            if store.products.isEmpty {
                ProgressView()
            } else {
                ForEach(store.products, id: \.id) { product in
                    Button {
                        Task {
                            try await store.purchase(product)
                        }
                    } label: {
                        VStack {
                            Text(verbatim: product.displayName)
                                .font(.headline)
                            Text(verbatim: product.displayPrice)
                        }
                    }
                    .buttonStyle(.borderedProminent)
                }
            }
        }
        .task {
            await store.fetchProducts()
        }
    }
}

咱们使用 Store 类型来获取和显现可用的使用内购买列表。Product 类型的实例包含了咱们需要显现的一切信息,如使用内购买的标题、描绘和价格。

Product 类型还具有 purchase 函数,咱们能够使用它来发动特定产品的使用内购买流程。它返回一个 PurchaseResult 枚举的实例,界说了三种状况:成功、挂起和用户取消。

@MainActor final class Store: ObservableObject {
    // ...
    @Published private(set) var activeTransactions: Set<StoreKit.Transaction> = []
    func purchase(_ product: Product) async throws {
        let result = try await product.purchase()
        switch result {
        case .success(let verificationResult):
            if let transaction = try? verificationResult.payloadValue {
                activeTransactions.insert(transaction)
                await transaction.finish()
            }
        case .userCancelled:
            break
        case .pending:
            break
        @unknown default:
            break
        }
    }
}

每当购买成果处于成功状况时,都会供给一个 Transaction 类型的关联值,界说了成功的买卖。StoreKit 将买卖封装在 VerificationResult 类型中,答应咱们验证买卖是否正确签名并来自 App Store。

VerificationResult 类型由 StoreKit 2 用于验证数据是否有用且由 App Store 签名。它供给了 payloadValue 计算特点,咱们能够使用它来解包已签名数据,或许如果数据未正确签名,则引发错误。

一旦获取了买卖,应该解锁用户购买的功用,并在特定买卖上调用 finish 函数。请记住,只要在解锁已购买的功用后才应该完成买卖。

struct ContentView: View {
    @StateObject private var store = Store()
    var body: some View {
        VStack {
            Text("Purchased items: \(store.activeTransactions.count)")
            if store.products.isEmpty {
                ProgressView()
            } else {
                if store.activeTransactions.isEmpty {
                    ForEach(store.products, id: \.id) { product in
                        Button {
                            Task {
                                try await store.purchase(product)
                            }
                        } label: {
                            VStack {
                                Text(verbatim: product.displayName)
                                    .font(.headline)
                                Text(verbatim: product.displayPrice)
                            }
                        }
                        .buttonStyle(.borderedProminent)
                    }
                }
            }
        }
        .task {
            await store.fetchProducts()
        }
    }
}

当启用“询问购买”时,购买将变为挂起状况。在这种状况下,买卖稍后才会抵达,只要在父母同意后才会抵达。应该观察 Transaction.updates 流来处理这种类型的买卖。咱们必须在使用程序发动时开端监督此流,以保证不会错失任何买卖。

@MainActor final class Store: ObservableObject {
    @Published private(set) var activeTransactions: Set<StoreKit.Transaction> = []
    private var updates: Task<Void, Never>?
    // ...
    init() {
        updates = Task {
            for await update in StoreKit.Transaction.updates {
                if let transaction = try? update.payloadValue {
                    activeTransactions.insert(transaction)
                    await transaction.finish()
                }
            }
        }
    }
    deinit {
        updates?.cancel()
    }
}

StoreKit 2 供给了一种轻松获取一切活泼订阅和已购买产品的方法。Transaction 类型上的 currentEntitlements 特点列出了一切活泼订阅和未退款的产品。

@MainActor final class Store: ObservableObject {
    @Published private(set) var activeTransactions: Set<StoreKit.Transaction> = []
    // ...
    func fetchActiveTransactions() async {
        var activeTransactions: Set<StoreKit.Transaction> = []
        for await entitlement in StoreKit.Transaction.currentEntitlements {
            if let transaction = try? entitlement.payloadValue {
                activeTransactions.insert(transaction)
            }
        }
        self.activeTransactions = activeTransactions
    }
}

咱们能够使用 currentEntitlements 特点来获取每次使用程序发动或更频频时的一切活泼购买。经过主动监督 currentEntitlements 特点,咱们消除了还原购买的需求,由于 currentEntitlements 一直包含最新的活泼订阅和非耗费性购买列表,即便它们是在另一台设备上购买的也是如此。

@main
struct MyApp: App {
    @Environment(\.scenePhase) private var scenePhase
    @StateObject private var store = Store()
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(store)
                .task(id: scenePhase) {
                    if scenePhase == .active {
                        await store.fetchActiveTransactions()
                    }
                }
        }
    }
}

总结

这篇文章介绍了怎么在 iOS 使用中使用 StoreKit 2 完成使用内购买和订阅功用。主要内容包含项目装备、构建 Paywall 功用、显现产品列表、购买产品、处理买卖状况、监控买卖更新和获取活泼订阅与购买。经过具体的示例和解释,开发者能够轻松了解怎么利用 StoreKit 2 构建强大的使用内购买功用。