In-App Purchases (StoreKit)
In-App Purchases (StoreKit)
Purchase in-app products with StoreKit 2 by requesting products, purchasing, and verifying transactions.
Product Types
- Consumable: Can be bought multiple times (e.g., coins).
- Non-Consumable: One-time unlock (e.g., premium upgrade).
- Auto-Renewable Subscription: Recurring access.
StoreKit 2 Basics
Use StoreKit 2 to request products, purchase, and verify transactions with App Store-signed receipts.
Syntax: let products = try await Product.products(for:), try await product.purchase(), verify and transaction.finish().
Example
import StoreKit
@MainActor
func buy(productID: String) async throws {
  let products = try await Product.products(for: [productID])
  guard let product = products.first else { return }
  let result = try await product.purchase()
  switch result {
  case .success(let verification):
    let transaction = try verification.payloadValue
    // unlock content
    await transaction.finish()
  default: break
  }
}This example requests a product, performs a purchase, verifies the transaction, unlocks content, and finishes the transaction.
Fetch Products
Fetch products once (e.g., on app start) and keep them in memory for your paywall/store UI.
Example
import StoreKit
@MainActor
func loadProducts(ids: [String]) async throws -> [Product] {
  let products = try await Product.products(for: ids)
  return products.sorted { $0.displayName < $1.displayName }
}Purchase Flow & Results
Handle all outcomes and always verify before unlocking content.
Example
import StoreKit
@MainActor
func purchase(_ product: Product) async {
  do {
    let result = try await product.purchase()
    switch result {
    case .success(let verification):
      let transaction = try verification.payloadValue
      // unlock content guarded by transaction.productID
      await transaction.finish()
    case .userCancelled:
      // show nothing or restore UI state
      break
    case .pending:
      // family approval or SCA; update UI accordingly
      break
    @unknown default:
      break
    }
  } catch {
    // network or App Store errors
  }
}Listen for Transaction Updates
Receive purchases made outside your app UI (e.g., from the App Store or another device) and finish them.
Example
import StoreKit
func startTransactionListener() {
  Task.detached {
    for await update in Transaction.updates {
      do {
        let transaction = try update.payloadValue
        // update entitlements based on transaction.productID
        await transaction.finish()
      } catch {
        // handle verification failure
      }
    }
  }
}Restore Purchases
Query current entitlements to restore non-consumables and active subscriptions.
Example
import StoreKit
@MainActor
func restore() async {
  for await result in Transaction.currentEntitlements {
    if let transaction = try? result.payloadValue {
      // re-unlock features for transaction.productID
    }
  }
}Tip: Configure IAPs in App Store Connect and test with Sandbox accounts.
Use server-side receipt validation for security-sensitive unlocks.
Testing & Sandbox
Use TestFlight or the StoreKit Configuration file to simulate products; sign in with a Sandbox tester on device; note that sandbox transactions settle faster, and device logs help diagnose failures.
 
