π³ ꡬλ ν μ± λ§λ€κΈ°
Implement a subscription system with StoreKit 2's modern async/await API.
β Difficulty: βββ
β±οΈ Est. Time: 3-4h
π App Services
π¦ Product λ‘λ©
StoreManager.swift
func loadProducts() async { products = try await Product.products(for: productIDs) }
π° ꡬ맀 μ²λ¦¬
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 }
π ꡬλ μν νμΈ
SubscriptionStatus.swift
func checkSubscriptionStatus() async { for await result in Transaction.currentEntitlements { guard case .verified(let transaction) = result else { continue } if transaction.productType == .autoRenewable { // ꡬλ νμ±ν isPremium = true } } }
β»οΈ ꡬ맀 볡μ
Restore.swift
func restorePurchases() async { try? await AppStore.sync() await checkSubscriptionStatus() }
π¨ νμ΄μ 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) { // κΈ°λ₯ κ°μΉ λ¨Όμ 보μ¬μ£ΌκΈ° Text("ν리미μ κΈ°λ₯") .font(.largeTitle.bold()) FeatureRow(icon: "star", text: "무μ ν μ¬μ©") FeatureRow(icon: "bolt", text: "κ΄κ³ μ κ±°") FeatureRow(icon: "heart", text: "λ μ μ½ν μΈ ") Spacer() // ꡬλ νλ 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) } } // 볡μ λ²νΌ (HIG νμ) Button("ꡬ맀 볡μ") { 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() // ... ꡬ맀 μ²λ¦¬ isPurchasing = false } } }
π― Transaction 리μ€λ
TransactionObserver.swift
func observeTransactions() async { for await result in Transaction.updates { guard case .verified(let transaction) = result else { continue } // ꡬ맀 μλ£ μ²λ¦¬ if transaction.revocationDate == nil { // νμ± κ΅¬λ unlockPremiumFeatures() } else { // νλΆλ¨ lockPremiumFeatures() } await transaction.finish() } }
π‘ HIG Checklist
β
κ°μΉ λ¨Όμ , κ°κ²©μ λμ€μ
β
λͺ
νν ꡬλ
κΈ°κ°κ³Ό κ°±μ μ£ΌκΈ°
β
볡μ λ²νΌ νμ
β
"ꡬλ
κ΄λ¦¬" λ§ν¬ μ 곡
β
λ¬΄λ£ μ²΄ν κΈ°κ° λͺ
μ