개요
0:01:00구매 복원은 구독이나 인앱 구매를 판매하는 iOS 앱이라면 반드시 갖춰야 할 기본 기능입니다. Apple의 App Store Review Guidelines는 인앱 구매를 제공하는 모든 앱에 사용자가 구매를 복원할 수 있는 수단을 포함하도록 요구합니다. 이 수단이 없으면 심사 과정에서 앱이 거절될 수 있습니다.
사용자가 구매를 복원해야 하는 대표적인 상황은 다음과 같습니다.
- 앱을 삭제한 뒤 다시 설치할 때
- 새 iPhone이나 iPad를 설정할 때
- 같은 Apple ID로 로그인한 두 번째 기기에 앱을 내려받을 때
- 로컬 구매 상태를 잃어버린 앱 업데이트 이후
사전 준비
- RevenueCat iOS SDK가 설치되어 있습니다(Swift Package Manager 또는 CocoaPods 사용)
- App 진입점에서 API 키로 SDK를 구성해 두었습니다
- RevenueCat 대시보드에 Entitlement가 하나 이상 구성되어 있습니다
restorePurchases() API
0:01:30
RevenueCat의 restorePurchases() 메서드는 다음 작업을 순서대로 수행합니다.
- 기기에서 현재 App Store 영수증을 가져옵니다
- Apple 검증을 위해 영수증을 RevenueCat 서버로 보냅니다
- 활성 구독이나 구매를 현재 고객 레코드에 동기화합니다
- 모든 활성 Entitlement를 담아 갱신된
CustomerInfo객체를 반환합니다
Swift의 최신 async/await 문법으로 기본 사용법을 보면 다음과 같습니다.
// Basic restore using async/await
do {
let customerInfo = try await Purchases.shared.restorePurchases()
if customerInfo.entitlements["premium"]?.isActive == true {
// User has premium access
print("Purchases restored successfully")
} else {
print("No active purchases found to restore")
}
} catch {
print("Error restoring purchases: \(error.localizedDescription)")
}
반환되는 CustomerInfo 객체는 복원 이후 사용자의 현재 구독 상태를 반영합니다.
Entitlement는 상품 ID가 아니라 항상 식별자(예: "premium")로 확인하세요.
이렇게 하면 기능 잠금 로직을 특정 상품에서 분리해 둘 수 있습니다.
SwiftUI 구현
0:02:30
아래는 그 자체로 완결된, 프로덕션에 바로 쓸 수 있는 RestorePurchasesButton SwiftUI 뷰입니다.
로딩 상태를 처리하고, 결과를 알림으로 보여 주며, 중복 탭을 막기 위해 비동기 작업이 진행되는 동안 버튼을 비활성화합니다.
import SwiftUI
import RevenueCat
struct RestorePurchasesButton: View {
@State private var isRestoring = false
@State private var showAlert = false
@State private var alertMessage = ""
var body: some View {
Button(action: restorePurchases) {
if isRestoring {
ProgressView()
.progressViewStyle(.circular)
} else {
Text("Restore Purchases")
}
}
.disabled(isRestoring)
.alert("Restore Purchases", isPresented: $showAlert) {
Button("OK") { }
} message: {
Text(alertMessage)
}
}
private func restorePurchases() {
isRestoring = true
Task {
do {
let customerInfo = try await Purchases.shared.restorePurchases()
if customerInfo.entitlements.active.isEmpty {
alertMessage = "No active purchases found. If you believe this is an error, please contact support."
} else {
alertMessage = "Your purchases have been successfully restored!"
}
} catch {
alertMessage = "Failed to restore purchases: \(error.localizedDescription)"
}
isRestoring = false
showAlert = true
}
}
}
이 버튼을 페이월이나 설정 화면에 넣으세요.
struct PaywallView: View {
var body: some View {
VStack(spacing: 16) {
// ... your pricing options ...
RestorePurchasesButton()
.font(.footnote)
.foregroundColor(.secondary)
}
}
}
UIKit 구현
0:02:00아직 SwiftUI로 전환하지 않은 앱이라면, 클로저 기반 콜백 API를 사용하는 UIKit 구현은 다음과 같습니다.
@IBAction func restorePurchasesTapped(_ sender: UIButton) {
sender.isEnabled = false
Purchases.shared.restorePurchases { customerInfo, error in
sender.isEnabled = true
if let error = error {
self.showAlert(title: "Error", message: error.localizedDescription)
return
}
if customerInfo?.entitlements["premium"]?.isActive == true {
self.showAlert(title: "Success", message: "Purchases restored successfully!")
self.updateUIForPremium()
} else {
self.showAlert(
title: "Not Found",
message: "No active purchases found. Contact support if you believe this is an error."
)
}
}
}
private func showAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
내장 복원 버튼
0:01:30
RevenueCat의 Paywalls(RevenueCatUI의 PaywallView 또는
PaywallViewController)를 사용한다면 "구매 복원" 버튼이 자동으로 포함됩니다.
직접 추가할 필요가 없습니다.
이 내장 버튼은 모든 RevenueCat 페이월 템플릿 하단에 표시되며, 위에서 만든 수동 구현과 똑같이 동작합니다.
페이월 UI를 완전히 직접 만든다면 위에서 소개한
RestorePurchasesButton 컴포넌트를 사용하세요.
import RevenueCatUI
// RevenueCat's PaywallView includes a restore button automatically
struct AppPaywall: View {
var body: some View {
// The restore purchases button is built in — no extra code needed
PaywallView()
}
}
중요한 참고 사항
0:01:30restorePurchases()를 호출하면, RevenueCat이 새 익명 ID를 만들 수 있습니다.
앱에 자체 인증 시스템이 있다면, 복원을 호출하기 전에
Purchases.shared.logIn(appUserID:)로 사용자를 로그인시켜 구매가 올바른 계정에
연결되도록 하세요.
restorePurchases()는 사용자가 복원 버튼을
명시적으로 탭했을 때만 호출하세요. 앱을 실행할 때마다 자동으로 호출하는 것은 불필요하고,
App Store 네트워크 요청을 유발하며, 앱 시작 속도를 느리게 할 수 있습니다.
관련 가이드
- iOS 인앱 구매 전체 튜토리얼: RevenueCat iOS 연동을 처음부터 끝까지
- Android에서 구매 복원하기 (Kotlin): 이 가이드의 Android 버전
- iOS에서 구독 상태 확인하기: CustomerInfo로 기능을 잠그는 방법
- 문제 해결: RevenueCat의 흔한 문제와 해결책