🌐 KO

💳 Build a Subscription App

Implement a subscription system with StoreKit 2's modern async/await API.

⭐ Difficulty: ⭐⭐⭐ ⏱️ Est. Time: 3-4h 📂 App Services

📦 Loading Products

StoreManager.swift
func loadProducts() async {
    products = try await Product.products(for: productIDs)
}

💰 Purchase Handling

Purchase.swift
let result = try await product.purchase()
switch result {
case .success(let verification):
    guard case .verified(let transaction) = verification else { return }
    await transaction.finish()
case .userCancelled, .pending:
    break
}

🔄 Check Subscription Status

SubscriptionStatus.swift
func checkSubscriptionStatus() async {
    for await result in Transaction.currentEntitlements {
        guard case .verified(let transaction) = result else { continue }

        if transaction.productType == .autoRenewable {
            // Subscription activated
            isPremium = true
        }
    }
}

♻️ Restore Purchases

Restore.swift
func restorePurchases() async {
    try? await AppStore.sync()
    await checkSubscriptionStatus()
}

🎨 Paywall UI (HIG)

Apple HIG Paywall Guidelines: Show feature value first, clear pricing, easy restore path.

PaywallView.swift
struct PaywallView: View {
    @State private var products: [Product] = []
    @State private var isPurchasing = false

    var body: some View {
        VStack(spacing: 20) {
            // Show feature value first
            Text("Premium Features")
                .font(.largeTitle.bold())

            FeatureRow(icon: "star", text: "Unlimited Access")
            FeatureRow(icon: "bolt", text: "Ad Free")
            FeatureRow(icon: "heart", text: "Exclusive Content")

            Spacer()

            // Subscription Plans
            ForEach(products, id: \.id) { product in
                Button {
                    purchase(product)
                } label: {
                    VStack {
                        Text(product.displayName)
                        Text(product.displayPrice)
                            .font(.headline)
                    }
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(12)
                }
            }

            // Restore button (HIG required)
            Button("Restore Purchases") {
                Task { await restorePurchases() }
            }
            .font(.caption)
        }
        .padding()
        .task {
            products = try? await Product.products(for: ["premium_monthly"])
        }
    }

    func purchase(_ product: Product) {
        isPurchasing = true
        Task {
            let result = try await product.purchase()
            // ... purchase handling
            isPurchasing = false
        }
    }
}

🎯 Transaction Listener

TransactionObserver.swift
func observeTransactions() async {
    for await result in Transaction.updates {
        guard case .verified(let transaction) = result else { continue }

        // Handle completed purchase
        if transaction.revocationDate == nil {
            // Active subscription
            unlockPremiumFeatures()
        } else {
            // Refunded
            lockPremiumFeatures()
        }

        await transaction.finish()
    }
}

💡 HIG Checklist
✅ Value first, pricing second
✅ Clear subscription period and renewal cycle
✅ Restore button required
✅ Provide "Manage Subscription" link
✅ Clearly state free trial period

📦 Learning Resources

📚
DocC Tutorial
💻
GitHub Project
🍎
Apple HIG Docs

📎 Apple Official Resources

📘 Documentation 💻 Sample Code 🎬 WWDC Sessions