🌐 EN

💳 구독형 앱 만들기

StoreKit 2의 현대적인 async/await API로 구독 시스템을 구현합니다.

⭐ 난이도: ⭐⭐⭐ ⏱️ 예상 시간: 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 페이월 가이드라인: 기능 가치 먼저 보여주기, 명확한 가격, 쉬운 복원 경로

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 체크리스트
✅ 가치 먼저, 가격은 나중에
✅ 명확한 구독 기간과 갱신 주기
✅ 복원 버튼 필수
✅ "구독 관리" 링크 제공
✅ 무료 체험 기간 명시

📦 학습 자료

📚
DocC 튜토리얼
💻
GitHub 프로젝트
🍎
Apple HIG 원문

📎 Apple 공식 자료

📘 공식 문서 💻 샘플 코드 🎬 WWDC 세션