🌐 KO

πŸ’³ κ΅¬λ…ν˜• μ•± λ§Œλ“€κΈ°

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
βœ… κ°€μΉ˜ λ¨Όμ €, 가격은 λ‚˜μ€‘μ—
βœ… λͺ…ν™•ν•œ ꡬ독 κΈ°κ°„κ³Ό κ°±μ‹  μ£ΌκΈ°
βœ… 볡원 λ²„νŠΌ ν•„μˆ˜
βœ… "ꡬ독 관리" 링크 제곡
βœ… 무료 μ²΄ν—˜ κΈ°κ°„ λͺ…μ‹œ

πŸ“¦ Learning Resources

πŸ“š
DocC Tutorial
πŸ’»
GitHub Project
🍎
Apple HIG Docs

πŸ“Ž Apple Official Resources

πŸ“˜ Documentation πŸ’» Sample Code 🎬 WWDC Sessions