iOS 인앱 결제 및 페이월 개요

0:02:00

RevenueCat의 iOS SDK 코드랩에 오신 것을 환영합니다!

이 코드랩에서 다음 내용을 학습합니다:

RevenueCat SDK를 Xcode 프로젝트에 연동합니다. SwiftUI 애플리케이션에서 인앱 결제를 구현합니다. 유료 사용자와 무료 사용자를 구분하는 방법을 배웁니다. RevenueCat의 서버 사이드 구성을 활용하여 SwiftUI로 페이월 화면을 구축합니다.

이 코드랩을 완료하면 RevenueCat의 iOS SDK와 SwiftUI를 사용하여 iOS 앱에서 인앱 결제를 성공적으로 구현하고 동적 페이월을 표시할 수 있습니다.

overview

RevenueCat SDK 가져오기

0:05:00

먼저, 인앱 결제를 구현하기 전에 RevenueCat SDK를 프로젝트에 가져와야 합니다. 가장 쉬운 방법은 Swift Package Manager를 사용하는 것입니다.

Xcode에서 Podfile에 다음을 추가하세요:

text
pod 'RevenueCat'
pod 'RevenueCatUI'

Xcode가 패키지를 가져옵니다. RevenueCat을 앱의 메인 타겟에 추가했는지 확인하세요.

사전 구축된 Paywall을 구현할 계획이라면 RevenueCatUI 라이브러리도 동시에 추가할 수 있습니다.

Getting Your API Key

Before you can initialize the SDK, you'll need your RevenueCat API key. Here's where to find it:

  1. Go to the RevenueCat dashboard
  2. Navigate to your project
  3. Click on API keys in the left sidebar
  4. Copy your Apple App Store API key (it should start with appl_)
Finding your API key in the RevenueCat dashboard
Important: Keep your API keys secure and never commit them to public repositories. Consider using environment variables or a secure configuration management system for production apps.

Initialize the SDK

다음으로, 아래 코드를 사용하여 앱의 init() 메서드 또는 AppDelegate에서 Purchases SDK를 초기화합니다:

swift
import SwiftUI
import RevenueCat

@main
struct MyApp: App {
    init() {
        // SDK의 모든 메시지 로그 출력
        Purchases.logLevel = .debug

        // API 키로 SDK 구성
        Purchases.configure(withAPIKey: <public_apple_api_key>, appUserID: <app_user_id>)
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

사용자 식별: 특정 appUserID를 제공하지 않았으므로 RevenueCat이 자동으로 익명 ID를 생성하고 관리합니다. 복원 기능을 제대로 사용하려면 백엔드에서 제공하는 고유 식별자를 입력하세요. 트랜잭션 완료: 기본적으로 SDK는 App Store와의 트랜잭션 완료를 자동으로 처리하므로 직접 처리할 필요가 없습니다.

이제 구현의 50%를 완료했습니다!

Entitlement 검증하기

0:03:00

이제 사용자 Entitlement 검증으로 넘어가겠습니다.

Entitlement는 사용자가 구매 후 잠금 해제하는 접근 수준이나 기능을 나타냅니다. 이는 광고 배너를 표시할지 또는 프리미엄 접근을 부여할지 결정하는 데 유용합니다.

Swift의 최신 async/await 문법을 사용하여 사용자가 활성 Entitlement를 가지고 있는지 쉽게 확인할 수 있습니다:

swift
import SwiftUI
import RevenueCat

struct ContentView: View {
    @State private var isSubscribed: Bool = false

    let ENTITLEMENT_ID = "premium" // RevenueCat 대시보드의 Entitlement 식별자

    var body: some View {
        VStack {
            if isSubscribed {
                PremiumContent()
            } else {
                Text("무료 콘텐츠입니다.")
                // 여기에 배너나 페이월을 표시할 수 있습니다
            }
        }
        .task {
            // 뷰가 나타날 때 Entitlement 확인
            await checkEntitlement()
        }
    }

    private func checkEntitlement() async {
        do {
            let customerInfo = try await Purchases.shared.customerInfo()
            // Entitlement가 활성 상태인지 확인
            if customerInfo.entitlements[ENTITLEMENT_ID]?.isActive == true {
                self.isSubscribed = true
            } else {
                self.isSubscribed = false
            }
        } catch {
            print("고객 정보 가져오기 오류: \(error)")
        }
    }
}

사용자가 특정 Entitlement를 가지고 있는지 확인한 후 어떻게 진행할지 결정할 수 있습니다. 이 SwiftUI 예제에서는 @State 변수(isSubscribed)를 업데이트하여 뷰가 자동으로 다시 렌더링되고 프리미엄 콘텐츠 또는 무료 버전을 표시합니다.

인앱 결제 구현하기

0:04:00

이제 프리미엄 경험을 제공하기 위해 인앱 결제를 구현해 보겠습니다. 시작하려면 먼저 RevenueCat에서 구성한 상품을 가져와야 합니다. 이 데이터는 사용자에게 구매 옵션을 표시하는 데 사용됩니다.

Purchases.shared.products(productIdentifiers:)를 호출하여 사용 가능한 상품을 가져올 수 있습니다:

swift
import RevenueCat

class PurchaseViewModel: ObservableObject {
    func purchase(product: StoreProduct) async {
        do {
            let result = try await Purchases.shared.purchase(product: product)

            // 사용자가 이제 프리미엄 권한이 있는지 확인
            if result.customerInfo.entitlements["premium"]?.isActive == true {
                // 프리미엄 콘텐츠 잠금 해제
                print("구매 성공!")
            }
        } catch {
            print("구매 오류: \(error)")
        }
    }

    func fetchProducts() async -> [StoreProduct] {
        do {
            // 식별자를 사용하여 상품 가져오기
            let products = try await Purchases.shared.products(["premium_monthly", "premium_yearly"])
            return products
        } catch {
            print("상품 가져오기 오류: \(error)")
            return []
        }
    }
}

StoreProduct 객체를 가져온 후 Purchases.shared.purchase(product:)를 호출하여 인앱 결제 플로우를 시작할 수 있습니다. 이렇게 하면 네이티브 App Store 구매 시트가 자동으로 트리거되어 사용자가 앱 내에서 안전하게 트랜잭션을 완료할 수 있습니다.

이처럼 몇 줄의 코드만으로 완전히 기능하는 인앱 결제 플로우를 연동했습니다. 영수증 처리, StoreKit API 또는 구매 검증을 수동으로 처리하는 복잡함이 필요 없습니다.

페이월(Paywall) 구현하기

0:07:00

이제 SwiftUI를 사용하여 iOS 프로젝트에서 Paywall을 구현할 차례입니다.

비즈니스 로직

시작하려면 RevenueCat 대시보드에서 현재 Offering을 가져와야 합니다. Offering은 페이월에서 사용자에게 표시되는 사용 가능한 패키지(월간, 연간 등)를 정의합니다. Purchases.shared.offerings()를 사용하여 쉽게 수행할 수 있습니다:

swift
import Foundation
import RevenueCat
import SwiftUI

@MainActor // @Published 속성 변경이 메인 스레드에서 발생하도록 보장
class PaywallViewModel: ObservableObject {

    // 페이월에 표시될 현재 Offering을 보유합니다.
    @Published var offering: Offering?

    // UI에 인디케이터를 표시하기 위한 로딩 상태를 추적합니다.
    @Published var isLoading: Bool = false

    init() {
        // ViewModel이 생성되자마자 Offering을 가져올 수 있습니다.
        Task {
            await fetchOffering()
        }
    }

    /**
     * RevenueCat에서 현재 Offering을 가져와 @Published 속성을 업데이트합니다.
     */
    func fetchOffering() async {
        self.isLoading = true

        do {
            let offerings = try await Purchases.shared.offerings()
            // 대시보드에서 이 배치에 표시되도록 구성한
            // 'current' Offering을 가져옵니다.
            self.offering = offerings.current
        } catch {
            print("Offering 가져오기 오류: \(error.localizedDescription)")
            // UI에서도 오류 상태를 처리할 수 있습니다
        }

        self.isLoading = false
    }
}

RevenueCat iOS SDK는 Swift의 async/await를 네이티브로 지원하여 최신 SwiftUI 애플리케이션에 완벽하게 맞습니다. @Publishedoffering을 게시하면 SwiftUI 뷰가 변경 사항을 반응적으로 관찰할 수 있습니다.

SwiftUI로 Paywall UI 구현하기

이 시점에서 모든 준비가 완료되었습니다. RevenueCatUI 패키지를 추가했다면 SwiftUI를 사용하여 쉽게 페이월 UI를 구축할 수 있습니다.

RevenueCat의 UI 라이브러리는 내장 SwiftUI 뷰인 PaywallView를 제공하여 페이월 화면을 빠르게 표시할 수 있습니다. 이 컴포넌트는 표준 SwiftUI 수정자를 사용하여 완전히 커스터마이징할 수 있습니다.

다음은 SwiftUI의 sheet 수정자를 사용하여 페이월을 구현하고 표시하는 것이 얼마나 간단한지 보여주는 예제입니다:

swift
import SwiftUI
import RevenueCat
import RevenueCatUI

struct ContentView: View {
    @State private var showPaywall = false

    var body: some View {
        Button("페이월 표시") {
            showPaywall = true
        }
        .sheet(isPresented: $showPaywall) {
            // 시트에 페이월 표시
            PaywallView()
        }
    }
}

PaywallView는 자동으로 현재 Offering을 가져와 RevenueCat 대시보드에서 구성한 템플릿을 사용하여 표시합니다. 이미 가져온 경우 Offering 객체를 직접 전달할 수도 있습니다:

swift
import SwiftUI
import RevenueCat
import RevenueCatUI

struct ContentView: View {
    // 페이월 시트 표시를 트리거합니다.
    @State private var showPaywall = false

    // 페이월용 ViewModel 인스턴스를 생성합니다.
    // @StateObject는 뷰와 동일한 생명주기를 보장합니다.
    @StateObject private var paywallViewModel = PaywallViewModel()

    var body: some View {
        VStack(spacing: 20) {
            Text("앱에 오신 것을 환영합니다!")

            Button("프리미엄으로 업그레이드") {
                // 버튼을 탭하면 시트를 표시하도록 플래그를 설정합니다.
                showPaywall = true
            }
        }
        .sheet(isPresented: $showPaywall) {
            // 시트의 콘텐츠입니다.
            // PaywallView를 표시하고 ViewModel에서 Offering을 전달합니다.

            // Offering을 가져오는 동안 로딩 인디케이터를 표시할 수 있습니다.
            if paywallViewModel.isLoading {
                ProgressView()
            } else if let offering = paywallViewModel.offering {
                // Offering을 사용할 수 있으면 PaywallView에 전달합니다.
                PaywallView(offering: offering)
            } else {
                // 선택 사항: Offering을 로드할 수 없는 경우 오류 또는 메시지를 표시합니다.
                Text("죄송합니다. 현재 구독 옵션을 로드할 수 없습니다.")
            }
        }
    }
}

루트 뷰와 같은 앱의 최상위 뷰에 일부 수정자를 첨부할 수 있습니다. 표시 로직을 자동으로 처리해 줍니다.

iPhone에서 RevenueCat Paywall은 시트 또는 전체 화면으로 표시할 수 있으며, SwiftUI 또는 UIKit에서 연동하기 위한 여러 옵션이 있습니다:

  • presentPaywallIfNeeded를 사용한 Entitlement 기반 자동 표시
  • presentPaywallIfNeeded를 사용한 커스텀 표시 로직
  • PaywallView 또는 PaywallViewController를 사용한 수동 연동

예를 들어:

swift
import SwiftUI
import RevenueCat
import RevenueCatUI

@main
struct MyApp: App {
    // ... (init 코드) ...

    var body: some Scene {
        WindowGroup {
            ContentView()
                // 이 수정자는 "premium" Entitlement를 확인합니다.
                // 사용자에게 없으면 페이월 시트가 표시됩니다.
                .presentPaywallIfNeeded(
                    requiredEntitlementIdentifier: "premium",
                    purchaseCompleted: { customerInfo in
                        // 구매를 축하하기 좋은 곳입니다!
                        print("구매 완료: \(customerInfo.entitlements)")
                    },
                    restoreCompleted: { customerInfo in
                        // Entitlement가 활성화되면 페이월이 자동으로 닫힙니다.
                        print("복원 완료: \(customerInfo.entitlements)")
                    }
                )
        }
    }
}

구성 완료! 이제 사용자가 필요한 Entitlement를 가지고 있지 않을 때마다 Paywall Editor에서 구성한 것과 동일한 디자인으로 페이월을 표시할 수 있습니다.

이미 보셨듯이 페이월 시스템은 서버 기반 UI로 구축되어 있습니다. 즉, 앱 업데이트를 푸시하거나 App Store 심사를 거치지 않고도 대시보드에서 직접 페이월의 콘텐츠와 디자인을 동적으로 업데이트할 수 있습니다.

마무리

이 코드랩에서 RevenueCat의 iOS SDK를 연동하고, 인앱 결제를 구현하고, SwiftUI에서 페이월을 구축하는 방법을 배웠습니다. 이제 앱을 출시하고 더 많은 수익을 창출할 시간입니다! 💰

아래 리소스를 통해 SwiftUI를 사용한 iOS 프로젝트에서 RevenueCat SDK 사용에 대해 더 자세히 알아볼 수 있습니다: