본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 15. 07:33

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대체 대상
SkuDetailsProductDetails
SkuDetailsParamsQueryProductDetailsParams
querySkuDetailsAsync()queryProductDetailsAsync()
SkuDetailsResponseListenerProductDetailsResponseListener
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가 비어 있지 않은 경우 productIdstatusCode반드시 로그(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_OWNEDSERVICE_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 발생재연결하여 처리 계속 (자체 재시도와 자동 재연결이 이중으로 발생하지 않을 것)
8acknowledge 누락부여 후 3일간 방치 (또는 로그로 확인)소비형은 consumeacknowledge를 겸함. 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가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0