Mobile Development7 min read

In-App Purchases & Subscriptions

Implement StoreKit 2 for iOS IAP: subscriptions, consumables, receipt validation, restoration, and subscription upsell flows.

in-app-purchasesiossubscriptionsrevenue

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

  1. Configure products in App Store ConnectIAP products are defined in ASC - the app fetches metadata from there at runtime.
  2. Implement purchase flow with StoreKit 2StoreKit 2 eliminates callback-based StoreKit 1 complexity with clean async/await API.
  3. Validate transactions server-sideClient-side receipt validation can be bypassed; server-side validation is required for subscription state.
  4. Handle subscription states and renewalsSubscriptions have complex lifecycle: active, expired, billing retry, grace period, refunded - each state requires different app behavior.
  5. 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 active

Edge 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/storekit
  • App Store Server API: https://developer.apple.com/documentation/appstoreserverapi
  • WWDC StoreKit 2: https://developer.apple.com/videos/play/wwdc2021/10114/
  • RevenueCat (managed IAP): https://www.revenuecat.com/docs/

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.