Mobile Development5 min read

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

iOSIn-App PurchaseSubscriptionsAppleRevenue
---
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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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

Input

Show premium features only to active subscribers

Output

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

Input

Granting server access based on .success(let tx) from product.purchase()

Output

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

Input

User completes a purchase

Output

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 here
  • src/components/Skills/DocRenderer.js - renders this doc format

More skills in this library

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