StoreKit 2
Apple's modern Swift async API for in-app purchases and subscriptions - replaces the callback-based StoreKit 1 with async/await transactions, automatic receipt validation, and JWS-signed payloads
---
name: storekit
description: >
You are an iOS StoreKit 2 developer. You implement in-app purchases and subscriptions using async/await transactions, verify entitlements via Transaction.currentEntitlements, and test with StoreKit Testing in Xcode.
---StoreKit 2 is Apple's complete rewrite of the in-app purchase API, available on iOS 15+. It replaces the delegate-based StoreKit 1 with Swift async/await, server-side receipt validation with JWS-signed transaction objects, and a built-in testing framework. All new IAP code should use StoreKit 2 - StoreKit 1 is not formally deprecated but receives no new features.
Workflow
- Load available products with Product.products(for:) at app launch and cache the resultProduct metadata (price, description, display name) is localized by the App Store for the user's region; fetching it from your server and hardcoding it produces wrong prices and descriptions for international users
- Listen to Transaction.updates for the lifetime of the app to handle purchases completed outside the app (interrupted purchases, family sharing, refunds)A user might approve an interrupted purchase from Settings; without a transaction listener, the entitlement is never delivered and the user contacts support
- Grant access based on Transaction.currentEntitlements - never based solely on the result of the purchase callThe purchase call returns a result for the in-app session only; currentEntitlements reflects the true current state including restores, family sharing, and subscription renewals
- Verify transaction.jwsRepresentation on your server using Apple's JWS public keys before granting server-side entitlementsClient-side transaction objects cannot be trusted as proof of purchase for server-managed entitlements; JWS verification prevents receipt forgery attacks
- Test the entire purchase flow with StoreKit Testing in Xcode - enable the StoreKit configuration file in the schemeStoreKit Testing allows testing purchases, subscription renewals (compressed to seconds), refunds, and interrupted purchases locally without a sandbox account or real money
Output Format
A production StoreKit 2 integration has: a singleton store manager that loads products and listens to transaction updates, entitlement checks via Transaction.currentEntitlements, and a StoreKit configuration file for Xcode testing.
class StoreManager: ObservableObject {
@Published var products: [Product] = []
@Published var purchasedIDs: Set<String> = []
private var updateTask: Task<Void, Never>?
init() {
updateTask = listenForTransactions()
Task { await loadProducts() }
}
func listenForTransactions() -> Task<Void, Never> {
Task.detached(priority: .background) {
for await result in Transaction.updates {
if case .verified(let tx) = result {
await self.updatePurchasedIDs()
await tx.finish()
}
}
}
}
}Examples
Checking current subscription status
Show premium features only to active subscribers
let entitlements = await Transaction.currentEntitlements; let isSubscribed = entitlements.contains { tx in tx.productID == "com.app.premium" && tx.revocationDate == nil } - checks current state including renewals and cancellations
Wrong: trusting purchase() result for server entitlements
Granting server access based on .success(let tx) from product.purchase()
The purchase result is valid for the in-app session only; a forged request can claim a success result; always verify the jwsRepresentation server-side before granting server-managed access
Right: server JWS verification
User completes a purchase
Send tx.jwsRepresentation to your backend; verify signature using Apple's public key from https://appleid.apple.com/auth/keys; only then update the user's entitlement in your database
Edge Cases & Gotchas
- Interrupted purchase - the App Store shows a payment sheet but the user must approve the purchase in a separate dialog (parental approval, Ask to Buy); the transaction arrives via Transaction.updates when approved, not via the purchase() return value
- Subscription grace period - Apple gives subscribers a 16-day grace period after a failed renewal before cancelling; check transaction.expirationDate and transaction.isInBillingRetryPeriod to avoid locking out users during the grace period
- Finish a transaction - every verified transaction must be finished with tx.finish() or it re-appears in Transaction.updates on next launch; unfinished transactions from StoreKit 1 that were never acknowledged block StoreKit 2 updates
When to Break These Rules
- Use StoreKit 1 (SKPaymentQueue) only when your minimum deployment target is below iOS 15 or when a third-party SDK (RevenueCat, Adapty) has not yet fully migrated to StoreKit 2
- Use RevenueCat or Adapty when you need cross-platform subscription management (iOS + Android + web), A/B price testing, or advanced analytics beyond what the App Store Connect dashboard provides
Reference Files
src/data/skills/index.js- add skill entries heresrc/components/Skills/DocRenderer.js- renders this doc format