
Claude Fable 5를 이용한 Google Play Billing Library v7 → v8.3.0 이행 및 테스트 내용 작성
요약
Google Play Billing Library v7에서 v8.3.0으로의 마이그레이션 가이드와 주요 변경 사항을 다룹니다. SkuDetails API의 삭제와 반환값 변경 등 파괴적 변경 사항에 대한 대응 방안을 설명합니다.
핵심 포인트
- SkuDetails 관련 API가 삭제되어 ProductDetails로 대체 필수
- JNI 사용 시 실행 시점 NoSuchMethodError 발생 주의
- queryProductDetailsAsync의 반환값이 QueryProductDetailsResult로 변경
- 구매 이력 취득 API 삭제에 따른 서버 측 장부 활용 필요
Google Play Billing Library v7 → v8.3.0 이행·테스트 문서
0. 전제·스케줄
- PBL v7의 지원 기한은 2026년 8월 31일(Play Console에서 연장 신청을 하면 11월 1일까지 유예 가능)입니다. 기한 이후에는 v7 상태로 두면 신규 릴리스가 경고/차단 대상이 됩니다.
- 2026년 5월 19일에 PBL 9.0.0이 이미 출시되었습니다. 이번에 8.3.0으로 올려두면 v8 계열의 지원은 2027년 8월 31일까지입니다.
1. v7 → v8의 파괴적 변경 사항 (필수 대응)
1-1. SkuDetails 계열 API의 완전 삭제
v5 이후 deprecated(사용 권장되지 않음) 되었던 것들이 v8에서 클래스 단위로 삭제되었습니다. 컴파일이 되지 않으므로 누락 여부를 기계적으로 검출할 수 있습니다.
| 삭제된 API | 대체 대상 |
|---|---|
SkuDetails | ProductDetails |
SkuDetailsParams | QueryProductDetailsParams |
querySkuDetailsAsync() | queryProductDetailsAsync() |
SkuDetailsResponseListener | ProductDetailsResponseListener |
BillingFlowParams.Builder.setSkuDetails() | setProductDetailsParamsList() |
Purchase.getSkus() | Purchase.getProducts() |
JNI 측 주의사항:
GetMethodID에서 문자열로 지정하고 있는 시그니처는 컴파일 에러가 발생하지 않고 실행 시점에NoSuchMethodError로 종료됩니다.querySkuDetailsAsync,getSkus,getSku등을 grep으로 전수 조사하십시오.- 가격 취득은
SkuDetails.getPrice()라는 하나의 메서드가 아니라,ProductDetails.getOneTimePurchaseOfferDetails().getFormattedPrice()(소모형) /getSubscriptionOfferDetails()(구독)로 분기됩니다. 본 프로젝트는 소모형 중심이므로oneTimePurchaseOfferDetails가 null인 케이스(상품 설정 실수)도 방어(guard)해야 합니다.
1-2. queryProductDetailsAsync의 반환값 변경
콜백이 List<ProductDetails>가 아니라 QueryProductDetailsResult를 반환하게 되었습니다.
getProductDetailsList(): 취득된 상품getUnfetchedProductList(): 취득에 실패한 상품(무효한 오퍼 등)이 이유와 함께 반환됨
→ "스토어에 상품이 나오지 않는다" 계열의 문의 조사가 용이해지므로, unfetchedProductList가 비어 있지 않은 경우 productId와 statusCode를 반드시 로그(Crashlytics의 non-fatal도 가능)에 남기는 구현을 추가하십시오.
1-3. queryPurchaseHistoryAsync / PurchaseHistoryRecord의 삭제
- 과거의 소모 완료·만료된 구매를 SDK에서 취득하는 수단이 완전히 없어졌습니다.
- 현재 유효한 구매는 기존 방식대로
queryPurchasesAsync()로 취득합니다. - 구매 이력이 필요한 용도는 서버 측 장부(영수증 검증 시 저장하고 있는 레코드)로 대체합니다. 환불·차지백(Chargeback)은 Voided Purchases API로 보완할 수 있지만 완전한 대체는 아닙니다.
- 기존의 복구 처리·미소모 리커버리 처리가
queryPurchasesAsync만으로 완결되는지 확인이 필요합니다.queryPurchaseHistoryAsync를 리커버리 계열 디버그 메뉴에서 사용하고 있었다면 해당 기능 자체를 재검토하십시오.
1-4. 자동 재연결 (임의 사항이지만 권장)
BillingClient.Builder.enableAutoServiceReconnection()
가 추가되었습니다. SERVICE_DISCONNECTED 발생 시의 자체 재시도(Retry) 처리를 SDK에 맡길 수 있습니다.
- 채택할 경우: 자체 재연결 루프와 중복으로 실행되지 않는지 확인(무한 재시도 및 콜백 다중 발생 리스크).
- 채택하지 않을 경우: 기존 재연결 구현을 그대로 유지해도 OK. 이번 이행 범위를 작게 가져가려면 채택을 보류하는 것이 안전합니다.
1-5. 8.1~8.3에서 추가된 API (대응 불필요 · 파악만 수행)
8.1~8.3의 차이점은 외부 오퍼(External Offer) / 외부 콘텐츠 링크 / **외부 결제(External Payments)**를 위한 신규 API(enableBillingProgram, isBillingProgramAvailableAsync, DeveloperProvidedBillingDetails 등)입니다. 일본은 외부 링크 결제 대상 지역이므로 향후 사업 판단에 따라 관계가 생길 수 있지만, 호출하지 않는다면 동작에 영향 없음입니다. 8.0이 아닌 8.3.0을 선택하는 이유는 단순히 최신 버그 수정(8.2.1에서 billing program 계열 수정 있음)을 포함하기 위함입니다.
2. 빌드 · 환경 관련 체크
이 부분이 이번 이행에서 가장 사고가 나기 쉽습니다. 코드 수정보다 먼저 빌드가 통과되는지를 최우선으로 검증합니다.
- AAR 교체 후, Gradle / AGP 버전에서 dex화가 통과되는지 (PBL 8은 새로운 compileSdk / Java 버전을 전제로 하며, AAR 메타데이터 체크에서 거부될 가능성이 있음)
- AndroidManifest 머지(Merge) 결과 확인 (
com.android.vending.BILLING권한 등) - Jenkins의 CI 빌드에서 Gradle 캐시를 클리어하고 한 번 풀 빌드 (오래된 AAR이 캐시에서 혼입되는 사고 방지)
3. 테스트 계획
3-1. 테스트 환경 준비
- 라이선스 테스터로 등록된 계정 (구매 시 즉시 테스트 결제로 처리됨)
- 내부 테스트 트랙에 이행 빌드를 업로드 (로컬 apk 직접 설치로는 결제 테스트 불가. Play를 통한 배포 필수)
- 테스트 카드 설정: 「항상 승인」, 「항상 거부」, 「잠시 후 승인(보류 구매)」의 3종류
- 서버 측은 샌드박스(Sandbox)용 영수증 검증 환경 (purchaseToken의 검증 대상이 운영 API이더라도, 테스트 구매는 orderId가
GPA.xxxx-xxxx-xxxx-xxxxx형식 및 테스트 플래그가 포함되어 반환됨) - BillingResult 응답 코드 덮어쓰기 기능(billing overrides)을 사용할 수 있도록 준비 → 단말기 상에서 임의의 에러 코드를 재현 가능.
ITEM_ALREADY_OWNED나SERVICE_UNAVAILABLE재현에 사용
3-2. 기능 테스트 (정상계)
| # | 항목 | 확인 내용 |
|---|---|---|
| 1 | 상품 정보 취득 | 모든 productId의 명칭 · 가격 · 통화가 Play Console 설정과 일치. unfetchedProductList가 비어 있음 |
| ... |
3-3. 이상계 · 중단계 (버그가 발생하는 지점)
| # | 항목 | 절차 | 기대 동작 |
|---|---|---|---|
| 1 | 구매 취소 | 결제 시트에서 뒤로 가기 | USER_CANCELED를 올바르게 핸들링하여 UI가 잠금 상태로 남지 않음 |
| 2 | 결제 거부 | 「항상 거부」 테스트 카드 | 에러 표시 → 재구매 가능 |
| 3 | 보류 구매 | 「잠시 후 승인」 카드 | PENDING 상태를 부여하지 않고 유지 → 승인 후 onPurchasesUpdated 또는 복귀 시 queryPurchasesAsync에서 부여 |
| 4 | 구매 직후 프로세스 kill | 결제 완료 → consume 전 앱 강제 종료 | 재시작 시 미소비 구매를 감지하여 부여와 consume이 실행됨 (이중 부여되지 않음) |
| 5 | 통신 단절(구매 후) | 결제 완료 → 서버 검증 전 비행기 모드 | 재시도(Retry) 또는 다음 실행 시 복구(Recovery)를 통해 부여. purchaseToken이 유실되지 않음 |
| 6 | 통신 단절(상품 취득) | 비행기 모드에서 구매 화면 진입 | 에러 표시, 복귀 후 재취득 가능 |
| 7 | 서비스 단절 | billing overrides로 SERVICE_DISCONNECTED 발생 | 재연결하여 처리 계속 (자체 재시도와 자동 재연결이 이중으로 발생하지 않을 것) |
| 8 | acknowledge 누락 | 부여 후 3일간 방치 (또는 로그로 확인) | 소비형은 consume이 acknowledge를 겸함. consume되지 않는 경로가 없는지 로그로 확인 (누락 시 3일 후 자동 환불) |
| 9 | 다른 기기 복원 | 기기 A에서 구매 → 미소비 상태로 기기 B에서 로그인 | 계정 설계상의 기대 동작대로 작동 (obfuscatedAccountId 대조) |
| 10 | 다중 실행·연타 | 구매 버튼 연타 | launchBillingFlow가 중복으로 실행되지 않음 |
| 11 | 환불 | Play Console에서 테스트 구매를 환불 | 서버 측(Voided Purchases / RTDN)에서 감지하여 잔액 처리가 사양대로 작동 |
3-4. 회귀 관점 (v8 고유)
- JNI를 통한 모든 과금 엔트리 포인트(Entry Point)를 각각 1회씩 호출 (메서드 시그니처 변경에 따른
NoSuchMethodError감지) QueryProductDetailsResult대응 후, 상품 리스트가 부분적으로 취득 실패했을 경우에 스토어 UI가 종료되지 않고 표시될 것 (billing overrides를 사용하거나, Console 측에서 상품 1개를 비활성화하여 재현)- 구버전(v7 빌드)에서 구매 → 미소비 상태로 신버전(v8 빌드)으로 업데이트 → 복구(Recovery)를 통해 부여될 것 (업데이트를 거친 미소비 구매. 이것이 가장 현실적인 사고 패턴)
- Crashlytics에 과금 관련 신규 크래시(Crash) 또는 non-fatal이 발생하지 않을 것 (이전 빌드에서 전환된 빌드의 내부 테스트 기간 중 대시보드 모니터링)
3-5. 출시 후 모니터링
- 단계적 공개(5% → 20% → 50% → 100%)를 실시하고, 각 단계에서 과금 성공률 및 에러 코드 분포를 이전 버전과 비교
- 서버 측의 영수증 검증 실패율, RTDN 수신 수에 이상이 없는지 확인
- 스토어 환불률 (acknowledge 누락이 있으면 3일 후부터 환불이 급증함. 공개 3~4일 후에 반드시 확인)
4. 권장 작업 순서
- 브랜치를 생성하여 AAR을 8.3.0으로 교체 → 먼저 빌드를 통과시킴 (섹션 2)
- 컴파일 에러 부분을
ProductDetails계열로 교체 (섹션 1-1) - JNI 시그니처 전체
grep확인 QueryProductDetailsResult대응 +unfetched로그 추가 (1-2)queryPurchaseHistoryAsync의존 부분 정리 및 삭제 (1-3)- 내부 테스트 트랙에서 3-2 → 3-3 → 3-4 단계를 수행 (체크리스트를 테스트 관리표에 전사)
- 업데이트를 거친 테스트 (v7 빌드 → v8 빌드)
- 단계적 공개 + 모니터링 (3-5)
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기