๐Ÿ’ณ ๊ตฌ๋…ํ˜• ์•ฑ ๋งŒ๋“ค๊ธฐ

StoreKit 2์˜ ํ˜„๋Œ€์ ์ธ async/await API๋กœ ๊ตฌ๋… ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ฆ 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 ์›๋ฌธ