1. 소개
이 Codelab에서는 일회성 제품을 만들고, 앱을 Play 결제 라이브러리 (PBL)와 통합하고, 구매 중단 이유를 분석하는 데 중점을 둡니다.
시청자층
이 Codelab은 Play 결제 라이브러리 (PBL)를 사용 중이거나 PBL을 사용하여 일회성 제품을 수익 창출하려는 Android 앱 개발자를 대상으로 합니다.
학습할 내용…
- Google Play Console에서 일회성 제품을 만드는 방법
- 앱을 PBL과 통합하는 방법
- PBL에서 소비성 및 비소비성 일회성 제품 구매를 처리하는 방법
- 구매 이탈을 분석하는 방법
필요한 항목…
- 개발자 계정으로 Google Play Console에 액세스할 수 있습니다. 개발자 계정이 없으면 계정을 만들어야 합니다.
- 이 Codelab의 샘플 앱(GitHub에서 다운로드 가능)
- Android 스튜디오
2. 샘플 앱 빌드
샘플 앱은 다음 측면을 보여주는 완전한 소스 코드가 있는 완전한 기능을 갖춘 Android 앱으로 설계되었습니다.
- PBL과 앱 통합
- 일회성 제품 가져오기
- 일회성 제품의 구매 흐름 시작
- 다음 결제 응답을 유도하는 구매 시나리오:
BILLING_UNAVAILABLE
USER_CANCELLED
OK
ITEM_ALREADY_OWNED
다음 데모 동영상은 샘플 앱이 배포되고 실행된 후의 모습과 동작을 보여줍니다.
자격 요건
샘플 앱을 빌드하고 배포하기 전에 다음을 실행하세요.
- Google Play Console 개발자 계정을 만듭니다. 이미 개발자 계정이 있는 경우 이 단계를 건너뛰세요.
- Play Console에서 새 앱을 만듭니다. 앱을 만들 때 샘플 앱의 앱 이름을 지정할 수 있습니다.
- Android 스튜디오를 설치합니다.
빌드
이 빌드 단계의 목표는 샘플 앱의 서명된 Android App Bundle 파일을 생성하는 것입니다.
Android App Bundle을 생성하려면 다음 단계를 따르세요.
- GitHub에서 샘플 앱을 다운로드합니다.
- 샘플 앱 빌드. 빌드하기 전에 샘플 앱의 패키지 이름을 변경한 후 빌드합니다. Play Console에 다른 앱의 패키지가 있는 경우 샘플 앱에 제공하는 패키지 이름이 고유해야 합니다.
참고: 샘플 앱을 빌드하면 로컬 테스트에 사용할 수 있는 APK 파일만 생성됩니다. 하지만 앱을 실행해도 제품과 가격이 가져오지 않습니다. 제품이 Play Console에서 구성되지 않았기 때문입니다. 이 Codelab의 뒷부분에서 제품을 구성합니다. - 서명된 Android App Bundle을 생성합니다.
다음 단계는 Android App Bundle을 Google Play Console에 업로드하는 것입니다.
3. Play Console에서 일회성 제품 만들기
Google Play Console에서 일회성 제품을 만들려면 Play Console에 앱이 있어야 합니다. Play Console에서 앱을 만든 다음 이전에 만든 서명된 App Bundle을 업로드합니다.
앱 만들기
앱을 만들려면 다음 단계를 따르세요.
- 개발자 계정을 사용하여 Google Play Console에 로그인합니다.
- 앱 만들기를 클릭합니다. 그러면 앱 만들기 페이지가 열립니다.
- 앱 이름을 입력하고 기본 언어 및 기타 앱 관련 세부정보를 선택합니다.
- 앱 만들기를 클릭합니다. 그러면 Google Play Console에 앱이 만들어집니다.
이제 샘플 앱의 서명된 App Bundle을 업로드할 수 있습니다.
서명된 App Bundle 업로드
- 서명된 App Bundle을 Google Play Console의 내부 테스트 트랙에 업로드합니다. 업로드한 후에만 Play Console에서 수익 창출 관련 기능을 구성할 수 있습니다.
- 테스트 및 출시 > 테스트 > 내부 버전 > 새 버전 만들기를 클릭합니다.
- 버전 이름을 입력하고 서명된 앱 번들 파일을 업로드합니다.
- 다음을 클릭한 후 저장 및 게시를 클릭합니다.
이제 일회성 제품을 만들 수 있습니다.
일회성 제품 만들기
일회성 제품을 만들려면 다음 단계를 따르세요.
- Google Play Console의 왼쪽 탐색 메뉴에서 Play를 통한 수익 창출 > 제품 > 일회성 제품으로 이동합니다.
- 일회성 제품 만들기를 클릭합니다.
- 다음 제품 세부정보를 입력합니다.
- 제품 ID: 고유한 제품 ID를 입력합니다.
one_time_product_01
를 입력합니다. - (선택사항) 태그: 관련 태그를 추가합니다.
- 이름: 제품 이름을 입력합니다. 예를 들면 다음과 같습니다.
Product name
- 설명: 제품 설명을 입력합니다. 예를 들면 다음과 같습니다.
Product description
- (선택사항) 아이콘 이미지 추가: 제품을 나타내는 아이콘을 업로드합니다.
- 제품 ID: 고유한 제품 ID를 입력합니다.
- 다음을 클릭합니다.
- 구매 옵션을 추가하고 지역별 사용 가능 여부를 구성합니다. 일회성 제품에는 사용 권한이 부여되는 방식과 가격, 지역별 사용 가능 여부를 정의하는 구매 옵션이 하나 이상 필요합니다. 이 Codelab에서는 제품에 표준 구매 옵션을 추가합니다. 구매 옵션 섹션에서 다음 세부정보를 입력합니다.
- 구매 옵션 ID: 구매 옵션 ID를 입력합니다. 예를 들면 다음과 같습니다.
buy
- 구매 유형: 구매를 선택합니다.
- (선택사항) 태그: 이 구매 옵션에 해당하는 태그를 추가합니다.
- (선택사항) 고급 옵션을 클릭하여 고급 옵션을 구성합니다. 이 Codelab에서는 고급 옵션 구성을 건너뛰어도 됩니다.
- 구매 옵션 ID: 구매 옵션 ID를 입력합니다. 예를 들면 다음과 같습니다.
- 사용 가능 여부 및 가격 섹션에서 가격 설정 > 가격 일괄 수정을 클릭합니다.
- 국가 / 지역 옵션을 선택합니다. 이렇게 하면 모든 지역이 선택됩니다.
- 계속을 클릭합니다. 그러면 가격을 입력하는 대화상자가 열립니다. 10달러를 입력한 다음 적용을 클릭합니다.
- 저장을 클릭한 다음 활성화를 클릭합니다. 이렇게 하면 구매 옵션이 생성되고 활성화됩니다.
이 Codelab에서는 다음 제품 ID를 사용하여 일회성 제품 3개를 추가로 만듭니다.
- consumable_product_01
- consumable_product_02
- consumable_product_03
샘플 앱은 이러한 제품 ID를 사용하도록 구성되어 있습니다. 다른 제품 ID를 제공할 수 있으며, 이 경우 제공한 제품 ID를 사용하도록 샘플 앱을 수정해야 합니다.
Google Play Console에서 샘플 앱을 열고 Play를 통한 수익 창출 > 제품 > 일회성 제품으로 이동합니다. 그런 다음 일회성 제품 만들기를 클릭하고 3~9단계를 반복합니다.
일회성 제품 만들기 동영상
다음 샘플 동영상은 앞에서 설명한 일회성 제품 생성 단계를 보여줍니다.
4. PBL과 통합
이제 앱을 Play 결제 라이브러리 (PBL)와 통합하는 방법을 알아보겠습니다. 이 섹션에서는 통합의 대략적인 단계를 설명하고 각 단계의 코드 스니펫을 제공합니다. 이러한 스니펫을 가이드로 사용하여 실제 통합을 구현할 수 있습니다.
앱을 PBL과 통합하려면 다음 단계를 따르세요.
- 샘플 앱에 Play 결제 라이브러리 종속 항목을 추가합니다.
dependencies { val billing_version = "8.0.0" implementation("com.android.billingclient:billing-ktx:$billing_version") }
- BillingClient를 초기화합니다. BillingClient는 앱에 상주하며 Play 결제 라이브러리와 통신하는 클라이언트 SDK입니다. 다음 코드 스니펫은 결제 클라이언트를 초기화하는 방법을 보여줍니다.
protected BillingClient createBillingClient() { return BillingClient.newBuilder(activity) .setListener(purchasesUpdatedListener) .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) .enableAutoServiceReconnection() .build(); }
- Google Play에 연결합니다.다음 코드 스니펫은 Google Play에 연결하는 방법을 보여줍니다.
public void startBillingConnection(ImmutableList<Product> productList) { Log.i(TAG, "Product list sent: " + productList); Log.i(TAG, "Starting connection"); billingClient.startConnection( new BillingClientStateListener() { @Override public void onBillingSetupFinished(BillingResult billingResult) { if (billingResult.getResponseCode() == BillingResponseCode.OK) { // Query product details to get the product details list. queryProductDetails(productList); } else { // BillingClient.enableAutoServiceReconnection() will retry the connection on // transient errors automatically. // We don't need to retry on terminal errors (e.g., BILLING_UNAVAILABLE, // DEVELOPER_ERROR). Log.e(TAG, "Billing connection failed: " + billingResult.getDebugMessage()); Log.e(TAG, "Billing response code: " + billingResult.getResponseCode()); } } @Override public void onBillingServiceDisconnected() { Log.e(TAG, "Billing Service connection lost."); } }); }
- 일회성 제품 세부정보 가져오기.앱을 PBL과 통합한 후 앱에서 일회성 제품 세부정보를 가져와야 합니다. 다음 코드 스니펫은 앱에서 일회성 제품 세부정보를 가져오는 방법을 보여줍니다.
private void queryProductDetails(ImmutableList<Product> productList) { Log.i(TAG, "Querying products for: " + productList); QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder().setProductList(productList).build(); billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { @Override public void onProductDetailsResponse( BillingResult billingResult, QueryProductDetailsResult productDetailsResponse) { // check billingResult Log.i(TAG, "Billing result after querying: " + billingResult.getResponseCode()); // process returned productDetailsList Log.i( TAG, "Print unfetched products: " + productDetailsResponse.getUnfetchedProductList()); setupProductDetailsMap(productDetailsResponse.getProductDetailsList()); billingServiceClientListener.onProductDetailsFetched(productDetailsMap); } }); }
ProductDetails
을 가져오면 다음과 비슷한 응답이 표시됩니다.{ "productId": "consumable_product_01", "type": "inapp", "title": "Shadow Coat (Yolo's Realm | Play Samples)", "name": "Shadow Coat", "description": "A sleek, obsidian coat for stealth and ambushes", "skuDetailsToken": "<---skuDetailsToken--->", "oneTimePurchaseOfferDetails": {}, "oneTimePurchaseOfferDetailsList": [ { "priceAmountMicros": 1990000, "priceCurrencyCode": "USD", "formattedPrice": "$1.99", "offerIdToken": "<--offerIdToken-->", "purchaseOptionId": "buy", "offerTags": [] } ] }, { "productId": "consumable_product_02", "type": "inapp", "title": "Emperor Den (Yolo's Realm | Play Samples)", "name": "Emperor Den", "description": "A fair lair glowing with molten rock and embers", "skuDetailsToken": "<---skuDetailsToken--->", "oneTimePurchaseOfferDetails": {}, "oneTimePurchaseOfferDetailsList": [ { "priceAmountMicros": 2990000, "priceCurrencyCode": "USD", "formattedPrice": "$2.99", "offerIdToken": "<--offerIdToken-->", "purchaseOptionId": "buy", "offerTags": [] } ] }
- 결제 흐름을 시작합니다.
public void launchBillingFlow(String productId) { ProductDetails productDetails = productDetailsMap.get(productId); if (productDetails == null) { Log.e( TAG, "Cannot launch billing flow: ProductDetails not found for productId: " + productId); billingServiceClientListener.onBillingResponse( BillingResponseCode.ITEM_UNAVAILABLE, BillingResult.newBuilder().setResponseCode(BillingResponseCode.ITEM_UNAVAILABLE).build()); return; } ImmutableList<ProductDetailsParams> productDetailsParamsList = ImmutableList.of( ProductDetailsParams.newBuilder().setProductDetails(productDetails).build()); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build(); billingClient.launchBillingFlow(activity, billingFlowParams); }
- 구매를 감지하고 처리합니다. 이 단계에서는 다음 작업을 실행해야 합니다.
- 구매 확인
- 사용자에게 권한 부여
- 사용자에게 알림
- 구매 절차를 Google에 알림
private void handlePurchase(Purchase purchase) { // Step 1: Send the purchase to your secure backend to verify the purchase following // https://developer.android.com/google/play/billing/security#verify // Step 2: Update your entitlement storage with the purchase. If purchase is // in PENDING state then ensure the entitlement is marked as pending and the // user does not receive benefits yet. It is recommended that this step is // done on your secure backend and can combine in the API call to your // backend in step 1. // Step 3: Notify the user using appropriate messaging. if (purchase.getPurchaseState() == PurchaseState.PURCHASED) { for (String product : purchase.getProducts()) { Log.d(TAG, product + " purchased successfully! "); } } // Step 4: Notify Google the purchase was processed. // For one-time products, acknowledge the purchase. // This sample app (client-only) uses billingClient.acknowledgePurchase(). // For consumable one-time products, consume the purchase // This sample app (client-only) uses billingClient.consumeAsync() // If you have a secure backend, you must acknowledge purchases on your server using the // server-side API. // See https://developer.android.com/google/play/billing/security#acknowledge if (purchase.getPurchaseState() == PurchaseState.PURCHASED && !purchase.isAcknowledged()) { if (shouldConsume(purchase)) { ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(); billingClient.consumeAsync(consumeParams, consumeResponseListener); } else { AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.getPurchaseToken()) .build(); billingClient.acknowledgePurchase( acknowledgePurchaseParams, acknowledgePurchaseResponseListener); } } }
5. 구매 이탈 분석
지금까지 코드랩에서는 USER_CANCELLED, BILLING_UNAVAILABLE, OK, ITEM_ALREADY_OWNED 응답과 같은 제한된 시나리오에 중점을 두었습니다. 하지만 Play 결제에서는 다양한 실제 요인에 의해 트리거될 수 있는 13가지 응답 코드를 반환할 수 있습니다.
이 섹션에서는 USER_CANCELLED
및 BILLING_UNAVAILABLE
오류 응답의 원인을 자세히 설명하고 구현할 수 있는 가능한 수정 조치를 제안합니다.
USER_CANCELED 응답 오류 코드
이 응답 코드는 사용자가 구매를 완료하기 전에 구매 흐름 UI를 포기했음을 나타냅니다.
가능한 원인 | 어떤 조치를 취할 수 있나요? |
|
|
BILLING_UNAVAILABLE 응답 오류 코드
이 응답 코드는 사용자의 결제 서비스 제공업체 또는 선택한 결제 수단에 문제가 있어 구매를 완료할 수 없음을 의미합니다. 예를 들어 사용자의 신용카드가 만료되었거나 사용자가 지원되지 않는 국가에 있는 경우입니다. 이 코드는 Play 결제 시스템 자체의 오류를 나타내지 않습니다.
가능한 원인 | 어떤 조치를 취할 수 있나요? |
|
|
응답 오류 코드의 재시도 전략
Play 결제 라이브러리 (PBL)의 복구 가능한 오류에 대한 효과적인 재시도 전략은 사용자 세션 상호작용 (예: 구매 중)과 백그라운드 작업 (예: 앱 재개 시 구매 쿼리)과 같은 컨텍스트에 따라 다릅니다. 이러한 전략을 구현하는 것이 중요합니다. 특정 BillingResponseCode
값은 재시도로 해결할 수 있는 일시적인 문제를 나타내는 반면 다른 값은 영구적이며 재시도가 필요하지 않기 때문입니다.
사용자가 세션을 진행 중일 때 발생하는 오류의 경우 사용자 경험에 미치는 영향을 최소화하기 위해 최대 재시도 횟수가 설정된 간단한 재시도 전략을 사용하는 것이 좋습니다. 반대로 즉시 실행이 필요하지 않은 새 구매 확인과 같은 백그라운드 작업의 경우 지수 백오프가 권장되는 접근 방식입니다.
특정 응답 코드와 그에 상응하는 권장 재시도 전략에 관한 자세한 내용은 BillingResult 응답 코드 처리를 참고하세요.
6. 다음 단계
- Play 결제 통합을 최대한 활용하는 방법을 알아보세요.
- 사용자가 이러한 제품을 구매하기 시작하면 보안 백엔드에서 구매를 확인하고 처리하기 위한 권장사항을 따라야 합니다.
참조 문서
7. 축하합니다.
축하합니다. Google Play Console을 탐색하여 새 일회성 제품을 만들고, 결제 응답 코드를 테스트하고, 구매 이탈을 분석했습니다.
설문조사
이 Codelab에 대한 의견을 보내주시면 감사하겠습니다. 잠시 시간을 내어 설문조사에 참여해 주세요.