무엇이 바뀌고 왜 지금인가
Google은 모든 Android 앱이 청구 코드를 최신 상태로 유지하도록 요구합니다. 2026년 8월 31일까지 모든 새 앱과 기존 앱의 모든 업데이트는 Google Play Billing Library v8 이상을 사용해야 합니다 (2026년 5월에 출시된 v9도 조건을 충족합니다). Play Console에서 2026년 11월 1일까지 일회성 연장을 요청할 수 있습니다. 이는 곧 게시 관문입니다. 이미 설치된 앱은 계속 동작하지만, v8 이상이 되기 전까지는 어떤 업데이트도 배포할 수 없습니다.
문제는 이 일이 매년 되풀이된다는 점입니다. v8은 2026년의 최소 기준이고, v9은 2027년, 그 뒤로도 이어집니다. 버전이 올라갈 때마다 API가 삭제되고 코드 수정을 강제합니다. Google 자체 Play Billing 코드랩도 아직 v5에 머물러 있고 "deprecated" 배너가 붙어 있는데, 이는 이 흐름이 얼마나 빠른지를 보여 줍니다.
이 코드랩은 오래가는 해법을 보여 줍니다. RevenueCat으로 이전하면 RevenueCat이 Billing Library를 대신 관리하므로 다시는 버전을 쫓을 필요가 없습니다. 이 과정에서 다음을 합니다.
- v8과 v9이 정확히 무엇을 삭제했는지 확인합니다 (그래서 BillingClient를 직접 패치하더라도 이 페이지가 도움이 됩니다).
- RevenueCat을 추가하고 기존
launchBillingFlow구매 코드를 교체합니다. - 기존 구독자를 옮겨서 결제 중인 사용자가 계속 접근 권한을 유지하게 합니다.
BillingClient)를 직접
연동하고 있는 Android 앱입니다. 예제는 모두 Kotlin입니다.
두 가지 이전 경로
마감을 넘기는 방법은 두 가지입니다. 둘 다 유효하지만, 이 코드랩은 두 번째를 권장합니다.
| 경로 | 해야 할 일 | 내년 |
|---|---|---|
| BillingClient를 직접 v8으로 패치 | 삭제된 API(3단계)를 자체 청구 코드에서 다시 작성 | v9, v10에서 같은 일을 반복... |
| RevenueCat으로 이전 | 청구 코드를 한 번만 교체하면 RevenueCat이 Billing Library를 책임집니다 | RevenueCat SDK만 업데이트하고 BillingClient 재작성은 없음 |
단일 상품만 판매했고 앞으로도 페이월, 실험, 크로스 플랫폼 지원을 추가할 계획이 전혀 없다면 직접 패치해도 괜찮습니다. 하지만 대부분의 앱에서는 첫 번째 경로에 매년 드는 비용이 두 번째를 택할 이유가 됩니다.
v8과 v9이 깨뜨린 것
참고로, 원시 Billing Library가 v8(2025년 6월 30일 출시)에서 삭제한 내용은 다음과 같습니다. 직접 패치한다면 이것이 작업 목록입니다. RevenueCat으로 이전한다면 이 코드는 삭제하면 됩니다.
| v8에서 삭제됨 | 대체 |
|---|---|
querySkuDetailsAsync(), SkuDetails |
queryProductDetailsAsync(), ProductDetails, QueryProductDetailsParams |
queryPurchaseHistoryAsync(), PurchaseHistoryRecord |
queryPurchasesAsync() (활성 구독과 소비되지 않은 일회성 구매만 반환하며, 무효화된 이력은 서버 Voided Purchases API가 필요함) |
enablePendingPurchases() (인자 없음) |
enablePendingPurchases(PendingPurchasesParams) |
ProrationMode |
setSubscriptionReplacementMode()를 통한 ReplacementMode |
setSkuDetails() |
setProductDetailsParamsList() |
v9(2026년 5월 19일)은 변경 폭이 더 작지만 여전히 코드에 영향을 줍니다. 차단된 Play Store 오류를
ERROR에서 BILLING_UNAVAILABLE로 바꿨고, 외부 결제 필드를 nullable로 만들었으며,
타깃 SDK를 35로 올렸습니다. (onProductDetailsResponse 리스너 시그니처 변경과 새 청구 하위 응답
코드는 v8에서 이미 도입되었습니다.) 최소 SDK는 v8.1부터 API 23(Android 6.0)으로 올라갔습니다.
RevenueCat이 쳇바퀴를 끝내는 방법
RevenueCat Android SDK는 Google Play Billing Library를 대신 번들하고 관리합니다. 현재 SDK는
Billing Library 8.3.0을 전이 의존성으로 함께 제공하므로, com.android.billingclient:billing을
직접 추가하지 않아도 되고, BillingClient를 전혀 호출하지 않습니다.
매년 돌아오는 마감이라는 관점에서 이것이 뜻하는 바는 다음과 같습니다.
- 지금: RevenueCat SDK를 한 번만 도입합니다 (이 코드랩의 나머지 내용).
- 내년, v9이나 v10이 필수가 될 때: RevenueCat SDK 버전만 올리면 됩니다. BillingClient 재작성도, 삭제된 API 찾기도 없습니다.
직접 만들었어야 할 것들도 함께 얻습니다. 단일 진실 공급원인 customerInfo.entitlements, 서버 측
영수증 검증, 웹훅, 그리고 이후 Paywalls, Experiments, Customer Center에 대한 접근까지 포함됩니다.
RevenueCat 추가와 설정
앱 모듈의 Gradle 파일에 SDK를 추가하세요 (최신 버전 사용):
// app/build.gradle.kts
dependencies {
implementation("com.revenuecat.purchases:purchases:10.10.0")
// Optional: paywalls + Customer Center (requires minSdk 24)
// implementation("com.revenuecat.purchases:purchases-ui:10.10.0")
}
// Remove your old: implementation("com.android.billingclient:billing:...")
SDK는 minSdk 23(Android 6.0)을 요구합니다. Application 클래스에서 한 번만 설정하세요.
// MainApplication.kt
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
Purchases.logLevel = LogLevel.DEBUG
Purchases.configure(
PurchasesConfiguration.Builder(this, "goog_YOUR_PUBLIC_SDK_KEY")
// .purchasesAreCompletedBy(PurchasesAreCompletedBy.REVENUECAT) // default: full migration
.build()
)
}
}
goog_로 시작하며,
RevenueCat 대시보드의 Project Settings > API keys에서 찾을 수 있습니다. secret 키를 절대 앱에 포함하지
마세요. 기본값인 PurchasesAreCompletedBy.REVENUECAT은 RevenueCat이 구매를 완료(확인)한다는
뜻입니다. MY_APP으로 바꾸는 것은 9단계의 단계적 경로에서만 하세요.
청구 개념 매핑하기
RevenueCat은 하드코딩된 SKU와 자체 "프리미엄 여부" 로직을 작고 오래가는 모델로 대체합니다. 다음 항목을 대시보드에서 설정하세요.
| 원시 Billing Library | RevenueCat |
|---|---|
SKU / ProductDetails (예: premium_monthly) |
Product (RevenueCat에 가져온 스토어 상품) |
| (SKU 목록을 하드코딩함) | Offering(사용자에게 보여 주는 것) 안의 Package(월간/연간 같은 역할) |
자체 isPremium 불리언 |
Entitlement (예: pro), customerInfo로 확인 |
대시보드에서 기존 Google Play 상품을 가져오고, entitlement(예: pro)를 만든 뒤, 각 상품을 거기에 Attach하세요.
pro 접근 권한이 자동으로 복원됩니다.
구매 흐름 교체하기
기존 queryProductDetailsAsync / launchBillingFlow / 구매 확인 코드를 삭제하고
두 번의 호출로 대체하세요. 먼저 판매할 대상을 가져옵니다.
// Coroutines: fetch the current offering and its packages
val offerings = Purchases.sharedInstance.awaitOfferings()
val packages = offerings.current?.availablePackages.orEmpty()
// Show `packages` in your UI (each pkg.product carries the localized price)
그런 다음 사용자가 선택한 package로 구매를 진행합니다.
try {
val result = Purchases.sharedInstance.awaitPurchase(
PurchaseParams.Builder(activity, selectedPackage).build()
)
val customerInfo = result.customerInfo
if (customerInfo.entitlements["pro"]?.isActive == true) {
// Unlock pro. RevenueCat already acknowledged the purchase with Google.
}
} catch (e: PurchasesTransactionException) {
if (!e.userCancelled) {
// Show a real error; userCancelled just means the user backed out.
}
}
코루틴을 사용하지 않는다면 콜백 방식의 등가 API(getOfferingsWith, purchaseWith)도 있습니다.
acknowledgePurchase / consumeAsync
호출을 삭제하세요.
이쪽 SDK가 처음이라면 Android 코드랩과 상품과 가격 가져오기 가이드에서 더 깊이 다룹니다.
기존 구독자 이전하기
이 부분이 가장 중요합니다. 현재 결제 중인 사용자가 접근 권한을 잃어서는 안 됩니다. 두 가지 요소가 있고, 두 가지 모두 필요합니다.
1. 새 구매: 자동으로 추적하기
Google Play 계정이 이미 연결된 상태에서( Google Play 연동 코드랩 참고), Google Play 서버 알림을 설정하고 Track new purchases from server-to-server notifications를 켜세요. 그러면 RevenueCat이 모든 새 구매를 기록합니다. 아직 SDK가 포함되지 않은 앱 버전에서 발생한 구매도 기록됩니다.
2. 기존 구매: 한 번만 동기화하기
기존 BillingClient 코드로 이미 구독한 사용자에 대해서는, RevenueCat이 적용된 빌드를 처음 실행할 때
syncPurchases()를 한 번 호출하세요. OS 로그인 프롬프트 없이 사용자의 기존
Google Play 구매를 RevenueCat에 전송합니다. 6단계의 상품-entitlement attach와 결합하면 사용자의
pro 접근 권한이 자동으로 복원됩니다.
// Run ONCE per subscriber, the first time they open the RevenueCat build.
if (!prefs.getBoolean("rc_migrated", false)) {
Purchases.sharedInstance.syncPurchasesWith(
onError = { /* retry later */ },
onSuccess = { _ -> prefs.edit().putBoolean("rc_migrated", true).apply() }
)
}
syncPurchases를 호출하면 지연이 늘고, 의도치 않게 고객들이 서로 별칭으로 묶일 수 있습니다.
일회성 플래그 뒤에 두거나(또는 기존 시스템은 "구독 중"이라고 하는데 RevenueCat은 그렇지 않을 때만 호출),
OS 로그인을 유발할 수 있는 restorePurchases()는 사용자가 직접 누르는 "복원" 버튼 뒤에서만
사용하세요.
appUserID), 동기화 전에 Purchases.sharedInstance.logIn(userId)를
호출하세요. 그러면 동기화된 구매가 익명 id가 아니라 올바른 고객에게 연결됩니다.
syncPurchases는 활성
구독과 소비되지 않은 일회성 구매만 동기화합니다. 만료된 구매와 과거 구매까지 채워 넣으려면(정확한 차트와
평생 이력을 위해) 대시보드에서 Google Historical Import를 실행하세요. 이 기능은 2023년
7월까지의 이력을 가져오지만, 90일보다 오래전에 만료된 구매 토큰에는 알려진 공백이 있습니다.
라이선스 테스터로 테스트하기
출시하기 전에, 실제 기기에서 요금이 청구되지 않는 상태로 이전을 검증하세요.
- Play Console에서 Setup > License testing 아래에 테스터를 추가하고, 빌드를 internal 또는 closed testing 트랙에 게시합니다.
- 기존 구독이 있는 테스터로 새 RevenueCat 빌드를 설치하고, 일회성
syncPurchases가 실행되어proentitlement가 활성화되는지 확인합니다. - 새 테스터로 새 흐름을 통해 구매를 완료하고, entitlement가 잠금 해제되며 트랜잭션이 RevenueCat에 나타나는지 확인합니다.
- RevenueCat 대시보드에서 해당 고객을 열어 구매와 entitlement가 기록되었는지 살펴봅니다.
단계적 롤아웃을 원하나요? observer mode를 사용하세요
한 번의 릴리스로 청구 코드를 제거할 준비가 되지 않았다면, RevenueCat을
PurchasesAreCompletedBy.MY_APP으로 설정하세요. 그러면 기존 코드가 여전히 구매를 완료(확인)하는
동안 RevenueCat은 구매와 entitlement를 기록합니다. 기존 구매 코드를 제거한 뒤에는 기본값인
PurchasesAreCompletedBy.REVENUECAT으로 전환하세요.
정리와 얻는 것
Android 앱을 원시 Billing Library에서 RevenueCat으로 이전했습니다.
BillingClient(그리고 v8에서 삭제된 API로 인한 모든 소동)를getOfferings+purchase로 교체했습니다.- 직접 짜맞춘 SKU 로직 대신 단일
proentitlement로 접근 권한을 모델링했습니다. - 일회성
syncPurchases와 상품-entitlement attach로 기존 구독자를 옮겼고, Historical Import로 이력을 채웠습니다.
얻는 것: 2026년 8월 31일 마감을 넘겼고, v9, v10, 그 이후가 필수가 되더라도 RevenueCat SDK만 올리면 됩니다. 더 이상 매년 Billing Library를 이전할 일이 없습니다.
계속하기
- Android 인앱 구매 & Paywalls: RevenueCat Android 연동 전체.
- 앱 수익 높이기와 상품과 가격 가져오기: 청구가 처리된 지금 무엇을 할지.
- RevenueCat: 기존 구독 이전하기와 Google: Billing Library 8로 이전하기.