Products & Purchases
Load products, buy them and verify transactions with modern async StoreKit 2.
Product.products(for:) loads localized prices from the App Store. product.purchase() returns a result you must verify before unlocking content.
Listen to Transaction.updates at launch to catch renewals, refunds and purchases made on other devices.
1import StoreKit
2
3@MainActor
4@Observable
5final class Store {
6 var products: [Product] = []
7 var purchasedIDs: Set<String> = []
8
9 func load() async throws {
10 products = try await Product.products(for: ["pro.monthly", "pro.yearly"])
11 }
12
13 func buy(_ product: Product) async throws {
14 let result = try await product.purchase()
15 guard case .success(let verification) = result,
16 case .verified(let transaction) = verification else { return }
17 purchasedIDs.insert(transaction.productID)
18 await transaction.finish()
19 }
20
21 func listenForTransactions() -> Task<Void, Never> {
22 Task {
23 for await update in Transaction.updates {
24 if case .verified(let transaction) = update {
25 purchasedIDs.insert(transaction.productID)
26 await transaction.finish()
27 }
28 }
29 }
30 }
31}Common pitfalls
- Always call transaction.finish() — unfinished transactions are redelivered on every launch.
- Test with a StoreKit Configuration file in Xcode before touching App Store Connect.
Still unclear? Ask BeeAI about Products & Purchases.
Get a tailored explanation, or paste your own code for a Swift 6 review. Free members get daily questions — Pro is unlimited.
Related topics
Show a native paywall and decide who has Pro access using current entitlements.
Write asynchronous code that reads top-to-bottom, with errors handled by normal try/catch.
Guarantee UI code runs on the main thread — and understand Swift 6.2 default MainActor isolation.
Become a Founding Pro member: unlimited BeeAI, upcoming Swift paths and every new reference entry first.
See Pro Plans