💳 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