In-App Purchases & Subscriptions
Implement StoreKit 2 for iOS IAP: subscriptions, consumables, receipt validation, restoration, and subscription upsell flows.
In-app purchases and subscriptions are the primary monetization mechanism for iOS apps. StoreKit 2 (Swift async/await API, introduced iOS 15) dramatically simplifies implementation compared to the original StoreKit. Getting IAP right requires robust validation, graceful restoration, and conversion-optimized UX.
Use StoreKit 2 for new apps. Validate on your server using App Store Server API - client-side only is insufficient for high-value purchases. Handle edge cases: expired subscriptions, billing retry, refunds. Subscription upsell UX is as important as the technical implementation.
Workflow
- Configure products in App Store ConnectIAP products are defined in ASC - the app fetches metadata from there at runtime.
- Implement purchase flow with StoreKit 2StoreKit 2 eliminates callback-based StoreKit 1 complexity with clean async/await API.
- Validate transactions server-sideClient-side receipt validation can be bypassed; server-side validation is required for subscription state.
- Handle subscription states and renewalsSubscriptions have complex lifecycle: active, expired, billing retry, grace period, refunded - each state requires different app behavior.
- Design conversion-optimized paywall UXPaywall design often has 2-5× impact on conversion rate compared to price point.
Output Format
StoreKit 2 purchase flow, server-side validation via App Store Server API, subscription state machine, `Transaction.updates` listener, optimized paywall UI.
Examples
StoreKit 2 purchase: `let result = try await product.purchase(options: [.simulatesAskToBuyInSandbox(false)]); if case .success(let verification) = result, case .verified(let tx) = verification { await grantPremiumAccess(); await tx.finish() }`Entitlements check: `for await result in Transaction.currentEntitlements { if case .verified(let tx) = result { if tx.productType == .autoRenewable { updateSubscriptionStatus(.active(expiresAt: tx.expirationDate!)) } } }`Server validation: `GET https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/(originalTransactionId)` → `{ "status": 1 }` means activeEdge Cases & Gotchas
- Sandbox testing: use different Apple ID for sandbox; sandbox billing retries are immediate (not 60 days); don't test billing retry flows in production
- Family sharing: if you enable Family Sharing, verify that entitlements check handles `ownershipType: .familyShared` - grant access but don't show upsell
- StoreKit testing in Xcode: use StoreKit configuration file for local testing without ASC; create `.storekit` file with your products for unit tests
When to Break These Rules
- For B2B apps where subscriptions are managed through your own billing system, consider StoreKit only for additional IAP (not the primary subscription) - Apple's 15-30% cut can be avoided for B2B through web checkout.
Reference Files
StoreKit 2 docs: https://developer.apple.com/documentation/storekitApp Store Server API: https://developer.apple.com/documentation/appstoreserverapiWWDC StoreKit 2: https://developer.apple.com/videos/play/wwdc2021/10114/RevenueCat (managed IAP): https://www.revenuecat.com/docs/