StoreKit 2 was Apple's complete rewrite of the IAP API, shipping with iOS 15. It's async/await-native, dramatically simpler than the original StoreKit, and the only reasonable choice for new apps in 2026. This is the working pattern we use in the Aether AI apps.
Why StoreKit 2
- Async/await โ no more delegate callbacks, transaction queues, semaphores
- Built-in receipt validation โ JWS-signed transactions you can verify locally OR on a server
- Better testing โ local StoreKit configuration file, no need to deploy to TestFlight to test
- Cleaner API โ half the lines of code for the same functionality
Setup in App Store Connect
- Sign Paid Apps agreement
- Configure banking and tax info
- Create the IAP in App Store Connect โ Product ID like
com.djenterprises.rdr2.unlimited_ai - For testing: create a Sandbox tester account
Fetching products
import StoreKit
let productIDs: Set<String> = ["com.djenterprises.rdr2.unlimited_ai"]
let products = try await Product.products(for: productIDs)
let unlock = products.first { $0.id == productIDs.first }
Making a purchase
do {
let result = try await unlock.purchase()
switch result {
case .success(let verification):
let transaction = try checkVerified(verification)
// Grant entitlement
await transaction.finish()
case .userCancelled:
break
case .pending:
// Awaiting parental approval, etc.
break
@unknown default:
break
}
} catch {
// Handle error
}
The checkVerified helper extracts the transaction and checks the JWS signature. Apple has a recommended pattern in their sample code.
Transaction listener
On app launch, listen for transactions that arrived while the app was closed (refunds, family sharing, etc.):
Task {
for await result in Transaction.updates {
guard case .verified(let txn) = result else { continue }
// Update entitlement state
await txn.finish()
}
}
Restore purchases
// In your restore button handler:
try await AppStore.sync()
Then re-check Transaction.currentEntitlements to see what the user owns.
Testing with StoreKit configuration
Xcode's StoreKit Configuration file lets you simulate IAPs locally โ no App Store Connect round-trip required. File โ New โ StoreKit Configuration, define products, then in your scheme: Run โ Options โ StoreKit Configuration. Purchases work in the simulator.
Test these scenarios before submission:
- Successful purchase
- Failed payment (decline simulation)
- User cancellation
- Restore on a fresh install
- Refund (simulated via Transaction.refund test API)
- Family sharing (if enabled)
Server-side receipt validation
For most consumer apps, on-device verification via checkVerified is sufficient. For premium / subscription apps where revenue matters more, validate on your backend using Apple's App Store Server API.
The pattern: when the user makes a purchase, send the JWS to your backend, your backend verifies with Apple's API and grants the entitlement in your database. This prevents jailbreak/MITM bypass at the cost of complexity.
For the full iOS app development story including how this fits with Claude API integration, see Shipping the RDR2 Companion From Scratch.
- Apple โ StoreKit documentation
- Apple โ WWDC21: Meet StoreKit 2
- Apple โ App Store Server API
- RevenueCat โ Subscription IAP best practices