| name | storekit |
| description | Use when implementing in-app purchases, StoreKit 2 subscriptions, consumables, non-consumables, or transaction handling. Covers testing-first workflow with .storekit configuration, StoreManager architecture, and transaction verification. |
StoreKit
StoreKit 2 patterns for implementing in-app purchases with async/await APIs, automatic verification, and SwiftUI integration.
Quick Reference
| Reference | Load When |
|---|---|
| Getting Started | Setting up .storekit configuration file, testing-first workflow |
| Products | Loading products, product types, purchasing with Product.purchase() |
| Subscriptions | Auto-renewable subscriptions, subscription groups, offers, renewal tracking |
| Transactions | Transaction listener, verification, finishing transactions, restore purchases |
| StoreKit Views | ProductView, SubscriptionStoreView, SubscriptionOfferView in SwiftUI |
Core Workflow
- Create
.storekitconfiguration file first (before any code) - Test purchases locally in Xcode simulator
- Implement centralized
StoreManagerwith@MainActor - Set up
Transaction.updateslistener at app launch - Display products with
ProductViewor custom UI - Always call
transaction.finish()after granting entitlements
Essential Architecture
@MainActor
final class StoreManager: ObservableObject {
@Published private(set) var products: [Product] = []
@Published private(set) var purchasedProductIDs: Set<String> = []
private var transactionListener: Task<Void, Never>?
init() {
transactionListener = listenForTransactions()
Task { await loadProducts() }
}
}
Common Mistakes
Missing
.finish()calls on transactions — Forgetting to calltransaction.finish()after granting entitlements causes transactions to never complete. The user won't see their purchase reflected. Always callfinish().Unsafe StoreManager state — Shared
StoreManagerwithout@MainActorcan have race conditions. Multiple async tasks can update@Publishedproperties concurrently, corrupting state. Use@MainActorfor thread safety.No transaction listener at app launch — Not setting up
Transaction.updateslistener means app crashes or misses refunded/canceled purchases. Listen for transactions immediately in@main, not when user taps purchase button.Hardcoded product IDs — Hardcoded IDs make testing and localization hard. Use configuration files or environment variables for product IDs. Same applies to prices (fetch from App Store, don't hardcode).
Ignoring verification failures — App Store verification fails silently sometimes. Not checking verification status means accepting unverified transactions (security risk). Always verify before granting entitlements.