Play 결제 통합 극대화

Play 결제 통합 극대화

이 Codelab 정보

subject최종 업데이트: 5월 19, 2025
account_circle작성자: Fangyi Luo, Steven Natiello

1. 개요

Google Play의 Play 결제 라이브러리 통합 Codelab을 통해 수익원을 최적화하고 앱을 자신 있게 출시하세요. 이 Codelab에서는 안정적인 구매 처리를 설정, 테스트, 구현하는 방법을 안내하여 수익 창출 목표를 지원하고 더 원활한 사용자 환경을 제공할 수 있도록 합니다.

앱과 게임의 정기 결제 및 일회성 제품에 대해 실시간 개발자 알림 (RTDN) 및 Play 결제 실험실을 설정할 수 있도록 지원해 드립니다. 구독자 이탈을 줄이고, 사기 및 악용을 방지하고, 특이 사례를 테스트하고, 잠재적 문제를 시뮬레이션, 재현, 해결하고, 사용자에게 영향을 주지 않고 제품 및 가격 변경을 실험하는 방법을 알아봅니다.

과정이 끝나면 재가입 전략을 구현하고, 통합 문제를 빠르게 해결하고, ROI를 개선하고, 프리미엄 환경을 제공하고, 앱과 업데이트를 자신 있게 출시할 수 있습니다.

기본 요건

학습할 내용

  • 구매 전환 및 고객 유지를 개선하는 기법으로 성장을 최적화할 수 있도록 구매 수명 주기를 적절하게 관리하는 방법
  • Google Cloud Pub/Sub를 사용하여 실시간 개발자 알림 (RTDN)을 설정하는 방법을 알아봅니다. 이 알림은 이후 회원 재가입 캠페인 및 기타 수명 주기 관리 전략을 구현하는 데 활용할 수 있습니다.
  • 의도치 않은 환불 또는 사기 및 악용 위험을 줄이기 위해 정확한 추적 및 사용 권한으로 알림을 안전하게 처리하도록 백엔드 서버에서 수신기를 설정하는 방법
  • Play 결제 실험실을 사용하여 통합을 테스트하고 오류를 시뮬레이션하여 개발 비용을 절감하면서 사용자 환경을 개선하는 방법

필요한 항목

2. 구독 및 일회성 구매를 위한 수익 창출 전략

앱을 통해 디지털 제품을 판매할 때 성공적인 수익 창출 전략은 일회성 구매와 정기 결제 모두에서 전체 사용자 환경을 고려해야 합니다. 원활한 환경을 제공하면 구매 의도를 높이고 이탈을 줄일 수 있습니다.

일회성 구매 또는 정기 결제의 일반적인 구매 흐름에는 여러 단계가 포함됩니다.

  1. 사용자가 구매할 상품을 둘러봅니다.
  2. 사용자가 구매 및 결제를 완료할 수 있도록 구매 흐름을 시작합니다.
  3. 완료된 구매에 관해 서버에 알림
  4. 서버에서 구매를 인증합니다.
  5. 사용자에게 콘텐츠를 제공합니다.
  6. 콘텐츠 전송을 확인합니다. 소비성 제품의 경우 사용자가 항목을 다시 구매할 수 있도록 적절한 시점에 구매 제품을 소비합니다.

인앱 통합을 사용하면 구매 흐름을 시작하고 이 사용자 환경을 관리할 수 있지만, 백엔드에서 사용자가 구매하는 사용 권한을 최신 상태로 유지하는 것이 중요합니다. 이는 구매를 추적하고 크로스 플랫폼 사용 권한과 같은 사용자 환경의 다른 여러 측면을 관리하는 데 중요합니다.

실시간 개발자 알림 (RTDN)은 구매 수명 주기의 다양한 단계를 파악하는 데 유용하며 실시간 실적 추적 도구와 정기 결제 사용자 재획득 전략을 구현하는 도구로 모두 효과적으로 사용할 수 있습니다.

예를 들어 사용자가 새 상품을 구매했거나 결제를 놓쳐 정기 결제에 유예 기간이 적용되었다고 가정해 보겠습니다. 적절한 RTDN을 사용하면 사용자의 상태가 변경되었음을 거의 실시간으로 인식하고, 사용자가 방금 구매한 상품에 더 많은 참여를 유도하거나, 정기 결제를 계속할 수 있도록 결제 세부정보를 업데이트하라는 알림 이메일을 보내는 등 적절하게 조치를 취할 수 있습니다.

또한 RTDN은 사용자의 클라이언트에 문제가 있는 경우에도 구매를 관리하는 데 도움이 되는 서버 측 컨트롤을 추가하는 데 유용합니다. 사용자가 구매를 완료하고 Google에서 확인을 받았지만 기기와 앱이 구매 리스너를 통해 구매 알림을 받기 전에 기기의 네트워크 연결이 끊어졌다고 가정해 보겠습니다. RTDN을 사용하면 서버를 통해 독립적인 알림을 받을 수 있으므로 클라이언트 문제와 관계없이 구매를 인식하고 사용자에게 사용 권한을 부여하여 안정적인 구매 절차를 보장할 수 있습니다.

현재 지원되는 모든 유형의 RTDN에 대한 자세한 내용은 여기를 참고하세요. 각 유형의 RTDN은 고유한 구매 상태를 나타냅니다. 사용 사례에 따라 적절하게 처리할 수 있도록 상응하는 처리 메커니즘을 구현하는 것이 중요합니다. 이 Codelab에서는 사용자가 앱에서 구매를 완료하면 보안 백엔드 서버에서 메시지를 수신하고, 구매를 확인하고, 올바른 사용자에게 사용 권한을 부여하는 등 RTDN 메시지를 처리하는 예를 안내합니다. 그런 다음 앱에 RTDN을 구성하는 방법을 보여줍니다.

3. 실시간 개발자 알림 (RTDN) 구성

실시간 개발자 알림 (RTDN)은 Google Cloud Pub/Sub를 활용하여 구매 상태 변경에 즉시 대응할 수 있도록 합니다. Cloud Pub/Sub는 독립적인 애플리케이션 간에 메시지를 주고받는 데 사용할 수 있는 완전 관리형 실시간 메시지 서비스입니다. Google Play에서는 Cloud Pub/Sub를 사용하여 정기 결제하는 주제에 대한 푸시 알림을 게시합니다.

RTDN을 사용 설정하려면 먼저 자체 Google Cloud Platform (GCP) 프로젝트를 사용하여 Cloud Pub/Sub를 설정한 후 앱의 알림을 사용 설정해야 합니다. GCP와 Cloud Pub/Sub를 잘 모르는 경우 빠른 시작 가이드를 참고하세요.

주제 만들기

알림 수신을 시작하려면 Google Play에서 알림을 게시할 주제를 만들어야 합니다. 주제를 만들려면 주제 만들기의 안내를 따르세요.

Pub/Sub 구독 만들기

주제에 게시된 메시지를 받으려면 해당 주제에 대한 Pub/Sub 구독을 만들어야 합니다. Pub/Sub 구독을 만들려면 다음 단계를 따르세요.

  1. Cloud Pub/Sub 구독자 가이드를 읽고 구독을 푸시 구독으로 구성할지 또는 풀 구독으로 구성할지 알아봅니다. 이 Codelab에서는 보안 백엔드 서버에서 Cloud Pub/Sub 서버에 메시지 가져오기 요청을 시작해야 하는 풀 구독을 사용합니다.
  1. 정기 결제 추가의 안내에 따라 정기 결제를 만듭니다.

주제에 게시 권한 부여

Cloud Pub/Sub를 사용하려면 주제에 알림을 게시할 권한을 Google Play에 부여해야 합니다.

  1. Google Cloud 콘솔을 엽니다.
  2. 프로젝트를 선택한 다음 검색창에서 'Pub/Sub'를 검색하고 Pub/Sub 구성 페이지로 이동합니다. Pub/Sub 구성 페이지를 검색하여 이동합니다.
  3. 주제를 찾아 권한 설정을 엽니다. 권한 설정 열기
  4. ADD PRINCIPAL(주 구성원 추가)을 클릭하여 서비스 계정 google-play-developer-notifications@system.gserviceaccount.com을 추가하고 Pub/Sub 게시자 역할을 부여합니다. 서비스 계정 google-play-developer-notifications@system.gserviceaccount.com을 추가하고 Pub/Sub 게시자 역할을 부여합니다.
  5. 저장을 클릭하여 주제 설정을 완료합니다. 저장을 클릭하여 주제 설정을 완료합니다.

앱에 RTDN 사용 설정

실시간 개발자 알림 (RTDN)을 설정하여 Play 결제 통합을 크게 개선하는 방법을 알아보세요. 맞춤 메시지로 구매 안정성을 개선하고 사기 및 악용을 방지하여 전반적인 ROI를 개선할 수 있습니다.

RTDN은 정기 결제 갱신, 신규 구매, 결제 문제와 같은 주요 이벤트에 대해 Google Play에서 직접 서버 간 즉각적인 업데이트를 제공합니다. 이를 통해 백엔드 시스템이 사용자 사용 권한의 실제 상태와 자동으로 동기화되므로 클라이언트 측 제한을 뛰어넘고 즉각적이고 적절하게 대응할 수 있습니다.

앱에 실시간 개발자 알림을 사용 설정하는 방법은 다음과 같습니다.

  1. Google Play Console을 엽니다.
  2. 앱을 선택합니다.
  3. Play로 수익 창출 > 수익 창출 설정으로 이동합니다.
  4. 실시간 개발자 알림 섹션으로 스크롤합니다.
  5. 실시간 알림 사용 설정을 선택합니다.
  6. 주제 이름 입력란에 이전에 구성한 전체 Cloud Pub/Sub 주제 이름을 입력합니다. 주제 이름의 형식은 projects/{project_id}/topics/{topic_name}이어야 합니다. 여기에서 project_id는 프로젝트의 고유 식별자이고 topic_name은 이전에 만든 주제의 이름입니다.
  7. 테스트 메시지 보내기를 클릭하여 테스트 메시지를 보냅니다. 테스트 게시를 실행하면 모두 올바르게 설정되고 구성되었는지 확인할 수 있습니다. 테스트 게시에 성공하면 테스트 게시에 성공했다는 메시지가 표시됩니다. 이 주제의 정기 결제에 연결했다면 테스트 메시지를 받아야 합니다. 풀 정기 결제의 경우 Cloud Console에서 정기 결제로 이동하고 메시지 보기를 클릭한 다음 메시지 가져오기를 진행합니다. Cloud Pub/Sub의 반복 전송을 방지하기 위해 가져온 모든 메시지를 확인해야 합니다. 푸시 정기 결제의 경우 테스트 메시지가 푸시 엔드포인트로 전송되었는지 확인합니다. 성공하는 경우 응답 코드가 메시지 확인 역할을 합니다. 게시에 실패하면 오류가 표시됩니다. 주제 이름이 올바르고 google-play-developer-notifications@system.gserviceaccount.com 서비스 계정에 주제에 대한 Pub/Sub 게시자 액세스 권한이 있는지 확인하세요.
  8. 수신할 알림 유형을 선택합니다.
  • 정기 결제 및 모든 무효화된 구매에 관한 알림 받기 - 정기 결제 및 무효화된 구매와 관련된 실시간 개발자 알림을 받습니다. 일회성 제품 구매에 대한 알림은 전송되지 않습니다.
  • 정기 결제 및 일회성 제품에 관한 모든 알림 받기: 모든 정기 결제 및 무효화된 구매 이벤트에 대한 알림을 받습니다. ONE_TIME_PRODUCT_PURCHASEDONE_TIME_PRODUCT_CANCELED와 같은 일회성 제품 구매 이벤트도 수신됩니다. 이러한 구매 이벤트에 대해 자세히 알아보려면 일회성 구매 수명 주기를 참고하세요.

a266e5dec5c93cd8.png

  1. 변경사항 저장을 클릭합니다.

이제 앱의 실시간 개발자 알림을 완료했으므로 다시 앱을 사용하도록 유도하는 메시지를 통해 사용자 이탈을 방지하거나 사기 및 악용을 방지하는 등 일반적인 문제를 해결하는 도구를 사용할 수 있습니다. 다음 섹션에서는 Cloud Pub/Sub 주제에 전송된 메시지를 소비할 수 있는 구독자를 보안 백엔드 서버에 빌드합니다.

4. 알림 수신

앱에서 최고의 사용자 환경을 제공하려면 백엔드 서버의 구매 상태를 최신 상태로 유지하는 것이 중요합니다. 예를 들어 사용자가 앱에서 결제와 함께 구매를 완료하면 최대한 빨리 콘텐츠가 계정에 전송되어야 합니다.

이를 위해서는 구매 완료가 적시에 감지되고 처리되어야 합니다. Play 결제 라이브러리는 앱에서 구매를 감지하는 여러 가지 방법을 제공합니다. 완료된 구매가 감지되면 앱은 백엔드 서버에 알림을 보내 구매를 인증하고, 올바른 사용자에게 콘텐츠를 부여한 후 Google에 구매가 처리되었음을 알립니다. 하지만 여러 가지 이유로 앱에서 구매를 적시에 감지하지 못할 수 있습니다. 예를 들어 사용자가 구매를 완료하고 Google에서 확인을 받았지만 기기와 앱이 Play 결제 라이브러리 인터페이스를 통해 알림을 받기 전에 기기의 네트워크 연결이 끊어질 수 있습니다. RTDN은 사용자의 클라이언트에 문제가 있는 경우에도 구매를 관리하는 데 도움이 되는 추가 서버 측 제어 기능을 제공합니다. RTDN은 구매 상태가 변경될 때 서버에 독립적인 알림을 보장하므로 잠재적인 클라이언트 문제와 관계없이 두 번째 경로를 통해 거의 즉시 구매 상태 변경을 인식할 수 있어 구매 프로세스의 안정성을 높일 수 있습니다.

이 섹션에서는 Cloud Pub/Sub 클라이언트 라이브러리를 사용하여 Cloud Pub/Sub 주제에 전송된 메시지를 소비하는 구독자를 빌드합니다. 이러한 라이브러리는 다양한 언어로 제공됩니다. 다음 섹션에서는 구매를 확인하고, 올바른 사용자에게 사용 권한을 부여하고, 서버에서 구매를 확인/사용하도록 구독자를 추가합니다. 이 Codelab에서는 Java를 사용합니다.

Cloud Pub/Sub 주제에 관한 각 게시에는 하나의 base64로 인코딩된 데이터 필드가 포함되어 있습니다.

{
 "message": {
   "attributes": {
     "key": "value"
   },
   "data": "eyAidmVyc2lvbiI6IHN0cmluZywgInBhY2thZ2VOYW1lIjogc3RyaW5nLCAiZXZlbnRUaW1lTWlsbGlzIjogbG9uZywgIm9uZVRpbWVQcm9kdWN0Tm90aWZpY2F0aW9uIjogT25lVGltZVByb2R1Y3ROb3RpZmljYXRpb24sICJzdWJzY3JpcHRpb25Ob3RpZmljYXRpb24iOiBTdWJzY3JpcHRpb25Ob3RpZmljYXRpb24sICJ0ZXN0Tm90aWZpY2F0aW9uIjogVGVzdE5vdGlmaWNhdGlvbiB9",
   "messageId": "136969346945"
 },
 "subscription": "projects/myproject/subscriptions/mysubscription"
}

base64로 인코딩된 데이터 필드를 디코딩하면 DeveloperNotification에 다음 필드가 포함됩니다.

{
 "version": string,
 "packageName": string,
 "eventTimeMillis": long,
 "oneTimeProductNotification": OneTimeProductNotification,
 "subscriptionNotification": SubscriptionNotification,
 "voidedPurchaseNotification": VoidedPurchaseNotification,
 "testNotification": TestNotification
}

자세한 내용은 실시간 개발자 알림 참조를 참고하세요.

다음은 보안 백엔드 서버가 Pub/Sub 메시지를 처리하기 위한 NotificationReceiver의 샘플 코드입니다. Security Command Center에 인증하려면 애플리케이션 기본 사용자 인증 정보를 설정합니다. 로컬 개발 환경의 인증 설정을 참고하세요.

import com.google.cloud.pubsub.v1.AckReplyConsumer;
import com.google.cloud.pubsub.v1.MessageReceiver;
import com.google.cloud.pubsub.v1.Subscriber;
import com.google.pubsub.v1.ProjectSubscriptionName;
import com.google.pubsub.v1.PubsubMessage;
import java.util.Base64;
import org.json.JSONObject;

/** Real-time developer notifications receiver. */
public class NotificationReceiver {

 private NotificationReceiver() {}

 /*
  * Receive notification messages from the subscription.
  *
  * @param projectId The project ID of your Google Cloud Project.
  * @param subscriptionId The subscription ID of the subscriber to the pub/sub topic.
  */
 public static void receiveNotificationMessages(String projectId, String subscriptionId) {
   ProjectSubscriptionName subscriptionName =
       ProjectSubscriptionName.of(projectId, subscriptionId);

   try {
     Subscriber subscriber =
         Subscriber.newBuilder(subscriptionName, new NotificationMessageReceiver()).build();
     // Start the subscriber.
     subscriber.startAsync().awaitRunning();

     subscriber.awaitTerminated();
   } catch (IllegalStateException e) {
     System.out.println("Subscriber stopped: " + e);
   }
 }

 static class NotificationMessageReceiver implements MessageReceiver {

   @Override
   public void receiveMessage(PubsubMessage message, AckReplyConsumer consumer) {
     // Decode the data into a String from the message data field.
     String jsonString = new String(Base64.getDecoder().decode(message.getData().toStringUtf8()));
     // Parse the String into a JSON object.
     JSONObject messageJson = new JSONObject(jsonString);

     // Fetch the value for certain fields.
     String version = messageJson.getString("version");
     String packageName = messageJson.getString("packageName");
     System.out.println("version: " + version);
     System.out.println("packageName: " + packageName);

     // Validate the purchase and grant the entitlement as needed.
     // More details in the following sections.
     // ......

     // Acknowledge the message to avoid repeated delivery.
     consumer.ack();
   }
 }
}

이제 보안 백엔드 서버에서 Cloud Pub/Sub 주제에 전송된 메시지를 소비하는 알림 수신기가 생겼습니다. 다음 섹션에서는 백엔드 서버에서 RTDN 메시지를 처리하는 권장사항을 설명합니다.

5. 앱의 구매 흐름에 사용자 식별자 연결

서버가 구매 상태 업데이트에 관한 RTDN 메시지를 수신하면 서버는 콘텐츠를 올바른 사용자에게 전송하는 등의 처리를 위해 구매한 사용자를 알아야 합니다. 앱에서 구매 흐름을 시작할 때 obfuscatedAccountId를 사용하여 구매하는 사용자의 사용자 식별자를 연결하면 됩니다. 식별자의 예로는 시스템에서 사용자 로그인의 난독화된 버전이 있습니다. 이 매개변수를 설정하면 Google에서 사기를 감지하는 데 도움이 됩니다. 또한 사용자에게 사용 권한 부여에서 설명한 대로 구매가 적절한 사용자에게 기여하도록 할 수 있습니다.

다음은 obfuscatedAccountId를 설정하여 앱에서 구매 흐름을 시작할 때 사용자 식별자를 연결하는 샘플 코드입니다.

// An activity reference from which the billing flow will be launched.
Activity activity = ...;
// A user identifier, e.g. an obfuscated user id in your system.
String obfuscatedAccountId = ...;

ImmutableList<ProductDetailsParams> productDetailsParamsList =
    ImmutableList.of(
        ProductDetailsParams.newBuilder()
            // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
            .setProductDetails(productDetails)
            // set the offer token to specify the offer to purchase when applicable, e.g., subscription products
            // .setOfferToken(offerToken)
            .build()
    );

BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(productDetailsParamsList)
    .setObfuscatedAccountId(obfuscatedAccountId)
    .build();

// Launch the billing flow
BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);

다음 섹션에서 볼 수 있듯이 구매 흐름에 설정된 사용자 식별자는 구매에 포함되며 올바른 사용자에게 사용 권한을 부여하는 데 사용할 수 있습니다.

6. 자격을 부여하기 전에 구매 확인

이 섹션에서는 보안 백엔드 서버에서 사용 권한을 부여하기 전에 구매를 인증하기 위한 권장사항을 살펴봅니다.

사용자가 일회성 제품을 구매하면 보안 백엔드 서버의 Pub/Sub 구독자에게 Pub/Sub 메시지가 수신됩니다. 백엔드 서버에서 다음을 실행해야 합니다.

  1. Pub/Sub 메시지에서 purchaseToken를 파싱합니다. 모든 구매의 모든 purchaseToken 값을 기록으로 유지해야 합니다.
  2. 현재 구매의 purchaseToken 값이 이전의 purchaseToken 값과 일치하지 않는지 확인합니다. purchaseToken은 전역에서 고유하므로 이 값을 데이터베이스에서 기본 키로 안전하게 사용할 수 있습니다.
  3. Google Play Developer API의 purchases.products:get 엔드포인트를 사용하여 구매가 합법적인지 Google로 확인합니다.
  1. 구매가 합법적이며 과거에 사용되지 않았다면 인앱 상품 또는 정기 결제에 안전하게 자격을 부여할 수 있습니다.
  2. 구매 상태가 PURCHASED인 경우에만 자격을 부여해야 하며 PENDING 구매를 올바르게 처리해야 합니다. 자세한 내용은 대기 중인 거래 처리를 참고하세요.

다음 코드 예에서는 Google Play Developer API의 API 클라이언트를 만듭니다. 나중에 API를 호출하는 데 사용합니다.

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.androidpublisher.AndroidPublisher;
import com.google.api.services.androidpublisher.AndroidPublisherScopes;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

/** Helper class to initialize the publisher APIs client library. */
public class AndroidPublisherHelper {
 /* Your application name */
 private static final String APPLICATION_NAME = "YourApplicationName";

 /* Load credentials from a JSON key file. Replace with the actual path to your downloaded service
  * account key file.
  */
 private static final String RESOURCES_CLIENT_SECRETS_JSON =
     "/path/to/your/service_account_key.json";

 /** Global instance of the JSON factory. */
 private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();

 /* The API client */
 private static final AndroidPublisher ANDROID_PUBLISHER = init();

 /**
  * Performs all necessary setup steps for running requests against the API.
  *
  * @return the {@link AndroidPublisher} service
  */
 private static AndroidPublisher init(){
   try {
     // Authorization.
     Credential credential =
         GoogleCredential.fromStream(
                 AndroidPublisherHelper.class.getResourceAsStream(RESOURCES_CLIENT_SECRETS_JSON))
             .createScoped(Collections.singleton(AndroidPublisherScopes.ANDROIDPUBLISHER));

     HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();

     // Set up and return API client.
     return new AndroidPublisher.Builder(httpTransport, JSON_FACTORY, credential)
         .setApplicationName(ApplicationConfig.APPLICATION_NAME)
         .build();
   } catch (GeneralSecurityException | IOException ex) {
     throw new RuntimeException("fail to initialize the publisher APIs client library", ex);
   }
 }
}

그런 다음 API를 호출하는 로직을 추가하고 이전에 빌드된 수신기를 수정하여 구매를 확인하고 올바른 사용자에게 사용 권한을 부여합니다.

AndroidPublisherHelper에서 다음 메서드를 추가하여 Google Play Developer API의 Purchases.products:get 엔드포인트에서 ProductPurchase를 가져옵니다.

 /* Fetch the ProductPurchase for the one-time product purchase from
  * Purchases.products.get endpoint in the Google Play Developer API
  */
 public static ProductPurchase executeProductPurchasesGet(
     String packageName, String sku, String purchaseToken) {
   try {
     ProductPurchase productPurchase =
         ANDROID_PUBLISHER.purchases().products().get(packageName, sku, purchaseToken).execute();
     return productPurchase;
   } catch (IOException ex) {
     log.error("Exception was thrown while getting a product purchase", ex);
     // It is recommended to apply some retry mechanism, such as exponential backoff, to fetch the purchase in case of transient failures.
     return null;
   }
 }

NotificationMessageReceiver에서 구매를 확인하고 알림에 포함된 데이터를 기반으로 시스템에서 올바른 사용자에게 사용 권한을 부여합니다. 중복 처리를 방지하려면 서버에서 purchaseToken를 계속 추적해야 합니다.

 @Override
 public void receiveMessage(PubsubMessage message, AckReplyConsumer consumer) {
   // Decode the data into a String from the message data field.
   String jsonString = new String(Base64.getDecoder().decode(message.getData().toStringUtf8()));
   // Parse the String into a JSON object.
   JSONObject messageJson = new JSONObject(jsonString);

   // Fetch the value for certain fields.
   String version = messageJson.getString("version");
   String packageName = messageJson.getString("packageName");

   // Process notification data based on your business requirements.
   // Process oneTimeProductNotification in the message.
   JSONObject oneTimeProductNotificationJson =
       messageJson.getJSONObject("oneTimeProductNotification");
   if (oneTimeProductNotificationJson != null) {
     String purchaseToken = oneTimeProductNotificationJson.getString("purchaseToken");
     String sku = oneTimeProductNotificationJson.getString("sku");
     int notificationType = oneTimeProductNotificationJson.getInt("notificationType");

     if (notificationType == 1) {
       // ONE_TIME_PRODUCT_PURCHASED - A one-time product was successfully purchased by a user.
       // Verify that the purchaseToken value does not match any previous purchaseToken values in
       // your backend system to avoid duplicate processing.
       ......
       // Fetch the ProductPurchase from Purchases.products.get endpoint
       ProductPurchase productPurchase =
         AndroidPublisherHelper.executeProductPurchasesGet(packageName, sku, purchaseToken);
       if (productPurchase != null && productPurchase.getPurchaseState() == 0) {
         // The purchase is valid and in PURCHASED state.

         // The account Id set in the App when launching the billing flow.
         String obfuscatedExternalAccountId = productPurchase.getObfuscatedExternalAccountId();
         // Grant the entitlement to the correct account for obfuscatedExternalAccountId in your
         // system.
       ......
       }
     }
     // Process subscriptionNotification in the message.
     JSONObject subscriptionNotificationJson = messageJson.getJSONObject("subscriptionNotification");
     if (subscriptionNotificationJson != null) {
       ......
     }
     // Process other notification data in the message as needed.
     ......
   }

   // Acknowledge the message to avoid repeated delivery.
   consumer.ack();
 }

7. Google에 구매가 처리되었다고 알림

자격을 부여한 후에는 보안 백엔드 서버에서 Play Developer API의 purchases.products:consume 또는 purchases.products:acknowledge 엔드포인트 포인트를 호출하여 소비성 제품을 소비하거나 비소비성 제품을 확인하여 구매가 처리되었음을 Google에 알려야 합니다.

AndroidPublisherHelper에서 다음 메서드를 추가하여 Google Play Developer API에서 purchases.products:consume 또는 purchases.products:acknowledge를 호출합니다.

 /* Consume the one-time product purchase by calling
  * Purchases.products.consume endpoint in the Google Play Developer API
  */
 public static void executeProductPurchasesConsume(
     String packageName, String sku, String purchaseToken) {
   try {
     ANDROID_PUBLISHER
       .purchases().products().consume(packageName, sku, purchaseToken).execute();
   } catch (IOException ex) {
     log.error("Exception was thrown while consuming a product purchase", ex);
     // It is recommended to apply some retry mechanism, such as exponential backoff, to ensure the purchase is correctly consumed in case of transient failures.
   }
 }

 /* Acknowledge the one-time product purchase by calling
  * Purchases.products.acknowledge endpoint in the Google Play Developer API
  */
 public static void executeProductPurchasesAcknowledge(
     String packageName, String sku, String purchaseToken) {
   try {
     ANDROID_PUBLISHER
       .purchases().products().acknowledge(packageName, sku, purchaseToken, new ProductPurchasesAcknowledgeRequest()).execute();
   } catch (IOException ex) {
     log.error("Exception was thrown while acknowledging a product purchase", ex);
     // It is recommended to apply some retry mechanism, such as exponential backoff, to ensure the purchase is correctly acknowledged in case of transient failures.
   }
 }

NotificationMessageReceiver에서 백엔드 서버에서 사용 권한을 부여한 후 소모성 제품 구매를 소비하거나 비소비성 제품 구매를 확인합니다.

 @Override
 public void receiveMessage(PubsubMessage message, AckReplyConsumer consumer) {
   ......
       String obfuscatedExternalAccountId = productPurchase.getObfuscatedExternalAccountId();
       // Grant the entitlement to the correct account for obfuscatedExternalAccountId in your
       // system.
       ......
       // If the product is a consumable product, consume the purchase.
       AndroidPublisherHelper.executeProductPurchasesConsume(packageName, sku, purchaseToken);
       // Or if the product is a non-consumable product, acknowledge the purchase.
       // AndroidPublisherHelper.executeProductPurchasesAcknowledge(packageName, sku, purchaseToken);
  ......

 }

확인은 사용자가 구매에 대한 자격을 부여받았음을 Google Play에 알리므로 필요합니다. 사용 권한을 부여한 직후 구매를 확인해야 합니다.

훌륭합니다. 이 Codelab에서 보여준 것처럼 실시간 개발자 알림과 통합하여 안정적인 구매 처리를 지원했습니다. 이제 모든 것이 제대로 작동하는지 확인해 보겠습니다. Play 결제 통합을 테스트하는 데 도움이 되도록 설계된 사용자 친화적인 도구인 Play 결제 실험실을 살펴보겠습니다.

8. Play 결제 실험실로 테스트

확신을 가지고 출시하려면 개발 과정에서 통합을 테스트해야 합니다. Play 결제 실험실은 개발자가 Google Play 결제 시스템과의 통합을 테스트하는 데 도움이 되는 무료 Android 앱으로, 개발자가 Play 결제 기능을 테스트하고, 더 빠르게 통합하며, 자신 있게 출시할 수 있는 쉽고 편리한 방법을 제공합니다.

Play 결제 실험실은 다음을 비롯한 다양한 시나리오를 테스트하는 데 도움이 되는 다양한 테스트 기능을 제공합니다.

Play 결제 실험실 앱에 새로운 테스트 기능이 지속적으로 추가되고 있습니다. Play 스토어에서 Play 결제 실험실을 다운로드하여 설치하거나 통합 테스트에서 Play 결제 실험실로 테스트하는 방법을 자세히 알아보세요.

Play 결제 실험실로 BillingResponseCode 테스트

앱을 Play 결제 라이브러리와 통합할 때 모든 BillingResponseCode 흐름을 테스트하는 것은 일반적인 과제입니다. Play 스토어와 Play의 백엔드 간의 통신을 거의 제어할 수 없기 때문입니다. Play 결제 실험실 앱의 응답 시뮬레이터 기능을 사용하면 Play 결제 라이브러리의 오류 코드 응답을 구성하여 다양한 복잡한 오류 시나리오를 테스트할 수 있습니다.

예를 들어 앱에서 구매가 완료된 것을 감지한 후 구매를 소비하는 로직을 앱에 구현했습니다. 네트워크 오류로 인해 앱에서 구매를 소비하지 못했지만 백엔드 서버의 RTDN 수신기가 메시지를 수신하여 구매를 올바르게 처리하는 시나리오를 테스트하려고 합니다. 응답 시뮬레이터를 활용하여 테스트 시나리오를 시뮬레이션할 수 있습니다. 다음은 Play 결제 실험실 응답 시뮬레이터로 테스트하는 단계를 안내합니다.

응답 시뮬레이터로 테스트

응답 시뮬레이터로 테스트할 때 앱은 Play 결제 실험실과 통신하여 Play 결제 실험실 응답 시뮬레이터에서 구성한 응답 코드를 가져옵니다.

Play 결제 라이브러리의 결제 재정의 테스트 사용 설정

응답 시뮬레이터와 앱 간의 통신을 사용 설정하려면 먼저 앱 내에서 Play 결제 라이브러리의 결제 재정의 테스트를 사용 설정해야 합니다. 이렇게 하려면 앱의 AndroidManifest.xml 파일에 다음 메타데이터 태그를 추가합니다.

<manifest ... >
  <application ... >
    ...
     <meta-data
      android:name="com.google.android.play.largest_release_audience.NONPRODUCTION"
      android:value="" />
    <meta-data
      android:name="com.google.android.play.billingclient.enableBillingOverridesTesting"
      android:value="true" />
  </application>
</manifest>

업데이트된 AndroidManifest.xml 파일을 사용하여 앱을 빌드합니다. 이제 앱을 Play 결제 실험실 응답 시뮬레이터에 사용할 수 있습니다.

테스트 후 앱을 프로덕션 환경에 배포할 때는 이러한 메타데이터 태그가 포함되지 않은 별도의 AndroidManifest.xml 파일을 사용하거나 AndroidManifest.xml 파일에서 이러한 태그를 삭제해야 합니다.

Play 결제 라이브러리 오류 시뮬레이션

시뮬레이션된 Play 결제 라이브러리 오류로 테스트하려면 먼저 Play 결제 실험실 앱에서 응답 코드를 구성한 다음 앱에서 테스트를 실행합니다.

응답 코드 구성

  1. 앱의 라이선스 테스터 계정으로 Play 결제 실험실 앱에 로그인합니다. 다음 그림에는 응답 시뮬레이터 카드가 포함된 Play 결제 실험실 대시보드가 표시되어 있습니다.

응답 시뮬레이터가 있는 Play 결제 실험실 대시보드

  1. 응답 시뮬레이터 카드에서 관리를 클릭하여 응답 시뮬레이터 화면으로 이동합니다.
  2. 메시지가 표시되면 Play 결제 실험실의 알림을 허용하여 앱의 연결 상태를 확인하세요.
  3. Play 결제 라이브러리 응답 시뮬레이션 스위치를 아직 사용 설정하지 않은 경우 사용 설정합니다.

c841baa4c96bf306.png

  1. 테스트할 Play 결제 라이브러리 API의 응답 코드를 선택합니다. 구매 소비 오류를 시뮬레이션하려면 consumeAsync API의 오류 코드를 선택합니다. 선택사항이 자동으로 저장됩니다. 이제 응답 시뮬레이터가 선택한 응답 코드를 앱에 전송할 준비가 되었습니다.

앱 테스트

이제 앱을 테스트하여 구성된 오류 시나리오에서 모든 것이 예상대로 작동하는지 확인할 수 있습니다. 앱을 열고 Play 결제 라이브러리 API 메서드를 트리거합니다. 앱에서 consumeAsync를 호출하여 구매 항목을 사용하면 앱에 방금 구성한 오류 코드가 수신됩니다. 오류 코드에 따라 앱이 올바르게 작동하고 백엔드 서버가 구매를 올바르게 처리하는지 확인할 수 있습니다.

테스트가 완료되면 Play 결제 라이브러리 응답 시뮬레이션 스위치를 사용 중지하여 응답 시뮬레이션을 중지하면 됩니다.

Play 결제 실험실을 사용한 테스트에 대해 자세히 알아보거나 고객센터에서 라이선스 테스터를 사용한 인앱 결제 테스트에 관해 자세히 알아보세요.

9. 축하합니다.

이제 이 Codelab을 완료했으므로 사용자 만족도, 구매 전환, 구독자 이탈을 개선하기 위해 사용자 환경을 개선하는 방식으로 앱 수익 창출을 전략적으로 최적화할 수 있습니다.

실시간 개발자 알림과 Play 결제 실험실 호환 앱을 활용하면 일회성 구매정기 결제 모두의 구매 수명 주기 이벤트를 사전에 처리할 수 있습니다.

이러한 도구를 사용하면 참여를 유도하는 재가입 전략을 효과적으로 구현하고 통합 문제를 신속하게 해결하며 궁극적으로 사용자 경험과 수익원을 개선하여 자신 있게 앱 또는 게임을 출시할 수 있습니다.

이 Codelab을 완료하면 전체 구매 여정을 관리하고 Play 결제 실험실에서 구현을 철저히 테스트하여 원활한 사용자 환경을 보장하고 Google Play에서 수익 창출 가능성을 극대화하는 기술을 갖게 됩니다.