앱에 Google Play 결제 라이브러리 통합

이 주제에서는 제품 판매를 시작하기 위해 Google Play 결제 라이브러리를 앱에 통합하는 방법을 설명합니다.

구매 진행 과정

다음은 일회성 구매 또는 정기 결제의 일반적인 구매 흐름입니다.

  1. 사용자에게 구입할 수 있는 항목을 보여줍니다.
  2. 사용자가 구매를 수락할 수 있도록 구매 흐름을 시작합니다.
  3. 서버에서 구매를 인증합니다.
  4. 사용자에게 콘텐츠를 제공합니다.
  5. 콘텐츠 전송을 확인합니다. 소비성 제품의 경우 사용자가 항목을 다시 구매할 수 있도록 구매 제품을 소비합니다.

정기 결제는 취소될 때까지 자동으로 갱신됩니다. 정기 결제는 다음 상태를 거칠 수 있습니다.

  • 활성: 사용자가 콘텐츠 사용에 문제가 없는 양호한 상태이며 정기 결제에 액세스할 수 있습니다.
  • 취소됨: 사용자가 정기 결제를 취소했지만 만료 시까지 계속 액세스할 수 있습니다.
  • 유예 기간 중: 사용자에게 결제 문제가 발생했지만 Google에서 결제 수단을 다시 시도하는 동안 사용자가 계속 액세스할 수 있습니다.
  • 보류 중: 사용자에게 결제 문제가 발생하여 Google에서 결제 수단을 다시 시도하는 동안 사용자가 더 이상 액세스할 수 없습니다.
  • 일시중지됨: 사용자가 액세스를 일시중지했으며 다시 시작할 때까지 액세스할 수 없습니다.
  • 만료됨: 사용자가 정기 결제를 취소했으며 정기 결제 액세스 권한을 잃었습니다. 만료 시 사용자가 이탈한 것으로 간주합니다.

Google Play 연결 초기화

Google Play 결제 시스템과 통합하는 첫 번째 단계는 Google Play 결제 라이브러리를 앱에 추가하고 연결을 초기화하는 것입니다.

Google Play 결제 라이브러리 종속 항목 추가

다음과 같이 앱의 build.gradle 파일에 Google Play 결제 라이브러리 종속 항목을 추가합니다.

Groovy

dependencies {
    def billing_version = "7.1.1"

    implementation "com.android.billingclient:billing:$billing_version"
}

Kotlin

dependencies {
    val billing_version = "7.1.1"

    implementation("com.android.billingclient:billing:$billing_version")
}

Kotlin을 사용한다면 Google Play 결제 라이브러리 KTX 모듈에 Kotlin 확장 프로그램과 코루틴 지원이 포함되어 있으므로 Google Play 결제 라이브러리를 사용할 때 직관적인 Kotlin을 작성할 수 있습니다. 프로젝트에서 이러한 확장 프로그램을 포함하려면 다음과 같이 앱의 build.gradle 파일에 다음 종속 항목을 추가합니다.

Groovy

dependencies {
    def billing_version = "7.1.1"

    implementation "com.android.billingclient:billing-ktx:$billing_version"
}

Kotlin

dependencies {
    val billing_version = "7.1.1"

    implementation("com.android.billingclient:billing-ktx:$billing_version")
}

BillingClient 초기화

Google Play 결제 라이브러리의 종속 항목을 추가한 후에는 BillingClient 인스턴스를 초기화해야 합니다. BillingClient는 Google Play 결제 라이브러리와 앱의 나머지 부분 간의 통신을 위한 기본 인터페이스입니다. BillingClient는 여러 일반적인 결제 작업을 위한 편의 메서드(동기식 및 비동기식 모두 포함)를 제공합니다. 다음 사항에 유의하세요.

  • 단일 이벤트에 관한 여러 개의 PurchasesUpdatedListener 콜백이 발생하는 것을 피할 수 있도록 한 번에 활성 BillingClient 연결을 하나만 열어 두는 것이 좋습니다.
  • 앱이 실행되거나 포그라운드로 전환될 때 BillingClient의 연결을 시작하면 앱에서 구매를 적시에 처리할 수 있습니다. 이는 registerActivityLifecycleCallbacks에 의해 등록된 ActivityLifecycleCallbacks를 사용하고 onActivityResumed를 수신 대기하여 활동이 재개되는 것을 처음 감지할 때 연결을 초기화하면 됩니다. 이 권장사항을 따라야 하는 이유에 관한 자세한 내용은 구매 처리 섹션을 참고하세요. 또한 앱이 닫히면 연결을 종료해야 합니다.

BillingClient를 생성하려면 newBuilder를 사용합니다. newBuilder()에 모든 컨텍스트를 전달할 수 있으며 BillingClient는 이 컨텍스트를 사용하여 애플리케이션 컨텍스트를 가져옵니다. 따라서 메모리 누수를 걱정할 필요가 없습니다. 구매 관련 업데이트를 수신하려면 setListener를 호출하여 PurchasesUpdatedListener에 대한 참조도 전달해야 합니다. 이 리스너는 앱의 모든 구매 관련 업데이트를 수신합니다.

Kotlin

private val purchasesUpdatedListener =
   PurchasesUpdatedListener { billingResult, purchases ->
       // To be implemented in a later section.
   }

private var billingClient = BillingClient.newBuilder(context)
   .setListener(purchasesUpdatedListener)
   // Configure other settings.
   .build()

자바

private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
        // To be implemented in a later section.
    }
};

private BillingClient billingClient = BillingClient.newBuilder(context)
    .setListener(purchasesUpdatedListener)
    // Configure other settings.
    .build();

Google Play에 연결

BillingClient를 만든 후 Google Play에 연결해야 합니다.

Google Play에 연결하려면 startConnection을 호출합니다. 연결 프로세스는 비동기적이며, 클라이언트 설정이 완료되고 추가로 요청할 준비가 되면 BillingClientStateListener를 구현하여 콜백을 수신해야 합니다.

또한 Google Play와 연결이 끊어진 문제를 처리하려면 재시도 로직을 구현해야 합니다. 재시도 로직을 구현하려면 onBillingServiceDisconnected() 콜백 메서드를 재정의해야 합니다. 그리고 추가 요청을 하기 전에 BillingClientstartConnection() 메서드를 호출하여 Google Play에 다시 연결하는지 확인해야 합니다.

다음 예는 연결을 시작하고 사용 준비가 되었는지 테스트하는 방법을 보여줍니다.

Kotlin

billingClient.startConnection(object : BillingClientStateListener {
    override fun onBillingSetupFinished(billingResult: BillingResult) {
        if (billingResult.responseCode ==  BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    override fun onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
})

Java

billingClient.startConnection(new BillingClientStateListener() {
    @Override
    public void onBillingSetupFinished(BillingResult billingResult) {
        if (billingResult.getResponseCode() ==  BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    @Override
    public void onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
});

구매 가능한 제품 표시

Google Play에 연결했다면 구매 가능한 제품을 쿼리하여 사용자에게 표시할 준비가 된 것입니다.

제품 세부정보 쿼리는 현지화된 제품 정보를 반환하므로 사용자에게 제품을 표시하기에 앞서 진행해야 하는 중요한 단계입니다. 정기 결제의 경우 제품 디스플레이가 모든 Play 정책을 준수해야 합니다.

인앱 상품 세부정보를 쿼리하려면 queryProductDetailsAsync를 호출합니다.

비동기 작업의 결과를 처리하려면 ProductDetailsResponseListener 인터페이스를 구현하는 리스너도 지정해야 합니다. 그런 후 다음 예에서와 같이 쿼리가 완료되면 리스너에 알리는 onProductDetailsResponse를 재정의할 수 있습니다.

Kotlin

val queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            ImmutableList.of(
                Product.newBuilder()
                    .setProductId("product_id_example")
                    .setProductType(ProductType.SUBS)
                    .build()))
        .build()

billingClient.queryProductDetailsAsync(queryProductDetailsParams) {
    billingResult,
    productDetailsList ->
      // check billingResult
      // process returned productDetailsList
}

Java

QueryProductDetailsParams queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            ImmutableList.of(
                Product.newBuilder()
                    .setProductId("product_id_example")
                    .setProductType(ProductType.SUBS)
                    .build()))
        .build();

billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    new ProductDetailsResponseListener() {
        public void onProductDetailsResponse(BillingResult billingResult,
                List<ProductDetails> productDetailsList) {
            // check billingResult
            // process returned productDetailsList
        }
    }
)

제품 세부정보를 쿼리할 때는 ProductType과 Google Play Console에서 생성한 제품 ID 문자열 목록을 함께 지정하는 QueryProductDetailsParams 인스턴스를 전달합니다. ProductType은 일회성 제품의 경우 ProductType.INAPP, 정기 결제의 경우 ProductType.SUBS가 될 수 있습니다.

Kotlin 확장 프로그램으로 쿼리

Kotlin 확장 프로그램을 사용하는 경우 queryProductDetails() 확장 함수를 호출하여 인앱 상품 세부정보를 쿼리할 수 있습니다.

queryProductDetails()는 Kotlin 코루틴을 활용하므로 별도의 리스너를 정의할 필요가 없습니다. 대신 쿼리가 완료될 때까지 함수가 정지되고 그 후에 결과를 처리할 수 있습니다.

suspend fun processPurchases() {
    val productList = listOf(
        QueryProductDetailsParams.Product.newBuilder()
            .setProductId("product_id_example")
            .setProductType(BillingClient.ProductType.SUBS)
            .build()
    )
    val params = QueryProductDetailsParams.newBuilder()
    params.setProductList(productList)

    // leverage queryProductDetails Kotlin extension function
    val productDetailsResult = withContext(Dispatchers.IO) {
        billingClient.queryProductDetails(params.build())
    }

    // Process the result.
}

드물지만 일부 기기는 Google Play 서비스 버전이 오래되어 ProductDetailsqueryProductDetailsAsync()를 지원하지 않습니다. 이 경우를 적절하게 지원하려면 Play 결제 라이브러리 5 이전 가이드에서 이전 버전과의 호환성 기능을 사용하는 방법을 알아보세요.

결과 처리

Google Play 결제 라이브러리는 ProductDetails 객체의 List에 쿼리 결과를 저장합니다. 그런 후 목록의 각 ProductDetails 객체에서 다양한 메서드를 호출하여 가격 또는 설명과 같은 인앱 상품에 관한 적절한 정보를 볼 수 있습니다. 사용 가능한 제품 세부정보를 보려면 ProductDetails 클래스의 메서드 목록을 참고하세요.

판매할 항목을 제공하기 전에 사용자가 그 항목을 이미 소유하고 있지 않은지 확인합니다. 사용자의 항목 라이브러리에 소비성 항목이 여전히 있다면 사용자가 항목을 다시 구매하기 전에 먼저 항목을 소비해야 합니다.

정기 결제를 제공하기 전에 사용자가 이미 정기 결제하지 않았는지 확인합니다. 또한 다음을 참고하세요.

  • queryProductDetailsAsync()는 정기 결제 제품 세부정보와 정기 결제당 최대 50개의 혜택을 반환합니다.
  • queryProductDetailsAsync()는 사용자가 대상이 되는 혜택만 반환합니다. 사용자가 사용할 수 없는 혜택을 구매하려고 하면(예: 앱에서 운영하지 않는 오래된 혜택 목록을 표시하는 경우) Play는 사용자에게 혜택을 사용할 수 없다고 알립니다. 대신 사용자는 기본 요금제 구매를 선택할 수 있습니다.

구매 흐름 시작

앱에서 구매 요청을 시작하려면 앱의 기본 스레드에서 launchBillingFlow() 메서드를 호출합니다. 이 메서드는 queryProductDetailsAsync 호출에서 얻은 관련 ProductDetails 객체가 포함된 BillingFlowParams 객체를 참조합니다. BillingFlowParams 객체를 생성하려면 BillingFlowParams.Builder 클래스를 사용하세요.

Kotlin

// An activity reference from which the billing flow will be launched.
val activity : Activity = ...;

val productDetailsParamsList = listOf(
    BillingFlowParams.ProductDetailsParams.newBuilder()
        // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
        .setProductDetails(productDetails)
        // For One-time products, "setOfferToken" method shouldn't be called.
        // For subscriptions, to get an offer token, call ProductDetails.subscriptionOfferDetails()
        // for a list of offers that are available to the user
        .setOfferToken(selectedOfferToken)
        .build()
)

val billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(productDetailsParamsList)
    .build()

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

Java

// An activity reference from which the billing flow will be launched.
Activity activity = ...;

ImmutableList<ProductDetailsParams> productDetailsParamsList =
    ImmutableList.of(
        ProductDetailsParams.newBuilder()
             // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
            .setProductDetails(productDetails)
            // For one-time products, "setOfferToken" method shouldn't be called.
            // For subscriptions, to get an offer token, call
            // ProductDetails.subscriptionOfferDetails() for a list of offers
            // that are available to the user.
            .setOfferToken(selectedOfferToken)
            .build()
    );

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

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

launchBillingFlow() 메서드는 BillingClient.BillingResponseCode에 나열된 몇 가지 응답 코드 중 하나를 반환합니다. 이 결과를 검토하여 구매 흐름을 시작하는 동안 발생한 오류가 없는지 확인합니다. OKBillingResponseCode는 성공적으로 시작되었음을 나타냅니다.

launchBillingFlow() 호출에 성공하면 시스템은 Google Play 구매 화면을 표시합니다. 그림 1은 정기 결제 구매 화면을 보여줍니다.

Google Play 구매 화면에 구매할 수 있는 정기 결제가 표시됨
그림 1. Google Play 구매 화면에 구매할 수 있는 정기 결제가 표시됨

Google Play는 onPurchasesUpdated()를 호출하여 PurchasesUpdatedListener 인터페이스를 구현하는 리스너에 구매 작업 결과를 전송합니다. 리스너는 클라이언트를 초기화할 때 setListener() 메서드를 사용하여 지정됩니다.

가능한 응답 코드를 처리하려면 onPurchasesUpdated()를 구현해야 합니다. 다음 예는 onPurchasesUpdated()를 재정의하는 방법을 보여줍니다.

Kotlin

override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
   if (billingResult.responseCode == BillingResponseCode.OK && purchases != null) {
       for (purchase in purchases) {
           // Process the purchase as described in the next section.
       }
   } else if (billingResult.responseCode == BillingResponseCode.USER_CANCELED) {
       // Handle an error caused by a user canceling the purchase flow.
   } else {
       // Handle any other error codes.
   }
}

Java

@Override
void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
    if (billingResult.getResponseCode() == BillingResponseCode.OK
        && purchases != null) {
        for (Purchase purchase : purchases) {
            // Process the purchase as described in the next section.
        }
    } else if (billingResult.getResponseCode() == BillingResponseCode.USER_CANCELED) {
        // Handle an error caused by a user canceling the purchase flow.
    } else {
        // Handle any other error codes.
    }
}

구매 성공 시 그림 2와 유사한 Google Play 구매 성공 화면이 생성됩니다.

Google Play의 구매 성공 화면
그림 2. Google Play의 구매 성공 화면

또한 구매 성공 시 사용자가 구매한 인앱 상품의 사용자 및 제품 ID를 나타내는 고유 식별자인 구매 토큰도 생성됩니다. 앱은 구매 토큰을 로컬에 저장할 수 있습니다. 하지만, 구매를 인증하고 사기로부터 보호할 수 있는 보안 백엔드 서버로 토큰을 전달하는 것이 좋습니다. 이 프로세스는 구매 감지 및 처리에 자세히 설명되어 있습니다.

또한 사용자는 주문 ID 또는 거래의 고유 ID가 포함된 거래 영수증을 이메일로 받습니다. 사용자는 일회성 제품을 구매할 때마다 그리고 최초 정기 결제 구매 및 후속 구매가 자동 갱신될 때마다 고유 주문 ID가 포함된 이메일을 받습니다. Google Play Console에서 주문 ID를 사용하여 환불을 관리할 수 있습니다.

맞춤설정된 가격 표시

유럽 연합의 사용자에게 앱을 배포할 수 있는 경우 launchBillingFlow를 호출할 때 setIsOfferPersonalized() 메서드를 사용하여 상품 가격이 자동화된 의사결정을 통해 맞춤설정되었음을 사용자에게 알려야 합니다.

가격이 사용자에게 맞춤설정되었음을 나타내는 Google Play 구매 화면
그림 3. 가격이 사용자에게 맞춤설정되었음을 나타내는 Google Play 구매 화면

소비자 권리 지침의 6(1)(ea) CRD(2011/83/EU)에 따라 사용자에게 제공하는 가격이 맞춤설정되었는지 확인해야 합니다.

setIsOfferPersonalized()는 불리언 입력값을 사용합니다. true인 경우 Play UI에는 공개 정보가 포함됩니다. false인 경우 UI에서 공개 정보를 생략합니다. 기본값은 false입니다.

자세한 내용은 소비자 고객센터를 참고하세요.

사용자 식별자 연결

구매 흐름을 시작하면 앱은 obfuscatedAccountId 또는 obfuscatedProfileId를 사용하여 구매하는 사용자에 대해 보유한 모든 사용자 식별자를 연결할 수 있습니다. 식별자의 예로는 시스템에서 사용자 로그인의 난독화된 버전이 있습니다. 이러한 매개변수를 설정하면 Google에서 사기를 감지하는 데 도움이 됩니다. 또한 사용자에게 사용 권한 부여에서 설명한 대로 구매가 적절한 사용자에게 기여하도록 할 수 있습니다.

구매 감지 및 처리

이 섹션에 설명된 구매 감지 및 처리는 프로모션 사용과 같은 앱 외부 구매를 비롯한 모든 유형의 구매에 적용됩니다.

앱은 다음 방법 중 하나로 신규 구매 및 완료된 대기 중인 구매를 감지합니다.

  1. 앱에서 launchBillingFlow를 호출한 결과 onPurchasesUpdated가 호출되는 경우 (이전 섹션에서 설명함) 또는 앱 외부에서 구매가 이루어지거나 대기 중인 구매가 완료될 때 활성 결제 라이브러리 연결을 사용하여 앱이 실행 중인 경우 예를 들어 가족 구성원이 다른 기기에서 대기 중인 구매를 승인합니다.
  2. 앱에서 queryPurchasesAsync를 호출하여 사용자의 구매를 쿼리할 때

#1의 경우 앱이 실행 중이고 활성 Google Play 결제 라이브러리 연결이 있는 한 새 구매 또는 완료된 구매에 대해 onPurchasesUpdated가 자동으로 호출됩니다. 애플리케이션이 실행 중이 아니거나 앱에 활성 Google Play 결제 라이브러리 연결이 없는 경우 onPurchasesUpdated이 호출되지 않습니다. 앱이 포그라운드에 있는 동안에는 앱이 적시에 구매 업데이트를 받을 수 있도록 활성 연결을 유지하는 것이 좋습니다.

2의 경우 앱이 모든 구매를 처리할 수 있도록 BillingClient.queryPurchasesAsync()를 호출해야 합니다. 앱이 Google Play 결제 라이브러리와 연결을 성공적으로 설정할 때 이 작업을 실행하는 것이 좋습니다. 이는 BillingClient 초기화에서 설명한 대로 앱이 실행되거나 포그라운드로 전환될 때 권장됩니다. 이는 onServiceConnected의 성공 결과를 수신할 때 queryPurchasesAsync를 호출하여 실행할 수 있습니다. 다음과 같은 이벤트와 상황을 처리하려면 이 권장사항을 따르는 것이 중요합니다.

  • 구매 중 네트워크 문제: 사용자가 구매를 성공적으로 완료하고 Google에서 확인을 받았지만 기기와 앱이 PurchasesUpdatedListener를 통해 구매 알림을 받기 전에 기기의 네트워크 연결이 끊어졌습니다.
  • 여러 기기: 사용자가 한 기기에서 상품을 구매한 후 기기를 전환할 때 이 상품이 표시되기를 기대할 수 있습니다.
  • 앱 외부에서 이루어진 구매 처리: 프로모션 사용과 같은 일부 구매는 앱 외부에서 이루어질 수 있습니다.
  • 구매 상태 전환 처리: 애플리케이션이 실행되지 않는 동안 사용자가 대기 중인 구매에 대한 결제를 완료하고 앱을 열 때 구매가 완료되었다는 확인을 받을 수 있습니다.

앱에서 새 구매 또는 완료된 구매를 감지하면 다음을 실행해야 합니다.

  • 구매를 인증합니다.
  • 완료된 구매에 대해 사용자에게 콘텐츠를 부여합니다.
  • 사용자에게 알립니다.
  • 앱에서 완료된 구매를 처리했음을 Google에 알립니다.

이러한 단계는 다음 섹션에서 자세히 설명한 후 모든 단계를 요약하는 섹션이 이어집니다.

구매 확인

앱은 사용자에게 혜택을 부여하기 전에 항상 구매가 적법한지 확인해야 합니다. 자격을 부여하기 전에 구매 확인에 설명된 가이드라인에 따라 이 작업을 실행할 수 있습니다. 구매를 확인한 후에만 앱에서 구매를 계속 처리하고 사용자에게 사용 권한을 부여해야 합니다(다음 섹션 참고).

사용자에게 사용 권한 부여

앱에서 구매를 확인하면 계속해서 사용자에게 사용 권한을 부여하고 사용자에게 알릴 수 있습니다. 자격을 부여하기 전에 앱에서 구매 상태PURCHASED인지 확인해야 합니다. 구매가 대기 중인 상태인 경우 앱은 사용자에게 자격이 부여되기 전에 구매를 완료하기 위해 아직 작업을 완료해야 한다고 알려야 합니다. 구매가 PENDING에서 SUCCESS로 전환될 때만 사용 권한을 부여합니다. 자세한 내용은 대기 중인 거래 처리를 참고하세요.

사용자 식별자 연결에 설명된 대로 구매에 사용자 식별자를 연결한 경우 이를 검색하여 시스템에서 올바른 사용자에게 기여도를 부여할 수 있습니다. 이 기법은 앱에서 구매 대상 사용자에 관한 컨텍스트를 잃을 수 있는 구매를 조정할 때 유용합니다. 앱 외부에서 이루어진 구매에는 이러한 식별자가 설정되지 않습니다. 이 경우 앱은 로그인한 사용자에게 사용 권한을 부여하거나 사용자에게 원하는 계정을 선택하라는 메시지를 표시할 수 있습니다.

사용자에게 알리기

사용자에게 사용 권한을 부여한 후 앱은 구매가 완료되었음을 확인하는 알림을 표시해야 합니다. 이렇게 하면 사용자가 구매가 완료되었는지 혼동하지 않아 앱 사용을 중지하거나 사용자 지원팀에 문의하거나 소셜 미디어에 불만을 제기하는 일이 방지됩니다. 앱은 애플리케이션 수명 주기 중 언제든지 구매 업데이트를 감지할 수 있습니다. 예를 들어 부모가 다른 기기에서 대기 중인 구매를 승인하는 경우 앱은 적절한 시점에 사용자에게 알리는 것을 지연할 수 있습니다. 지연이 적절한 경우의 예는 다음과 같습니다.

  • 게임이나 컷신의 액션 플레이 중에 메시지를 표시하면 사용자의 주의가 산만해질 수 있습니다. 이 경우 액션 플레이가 끝난 후에 사용자에게 알려야 합니다.
  • 게임의 초기 튜토리얼 및 사용자 설정 중에도 사용자에게 즉시 알리지 않아도 됩니다. 예를 들어 사용자가 앱을 설치하기 전에 앱 외부에서 구매했을 수 있습니다. 신규 사용자가 게임을 연 직후 또는 처음 사용자 설정 중에 리워드를 알리는 것이 좋습니다. 앱에서 사용자에게 사용 권한을 부여하기 전에 계정을 만들거나 로그인해야 하는 경우 사용자에게 구매 항목을 소유하기 위해 완료해야 하는 단계를 알리는 것이 좋습니다. 앱에서 구매를 처리하지 않으면 3일 후에 구매가 환불되므로 이는 중요합니다.

사용자에게 구매에 관해 알릴 때 Google Play에서는 다음 메커니즘을 권장합니다.

  • 인앱 대화상자를 표시합니다.
  • 인앱 메시지 상자에 메시지를 전송하고 인앱 메시지 상자에 새 메시지가 있음을 명확히 알립니다.
  • OS 알림 메시지를 사용합니다.

알림은 사용자에게 받은 혜택을 알려야 합니다. 예를 들어 '골드 코인 100개를 구매했습니다'와 같이 표시됩니다. 또한 Play Pass와 같은 프로그램의 혜택으로 구매가 이루어진 경우 앱은 이를 사용자에게 전달합니다. 예: '상품이 수신되었습니다. Play Pass로 보석 100개를 받았습니다. 계속 진행하세요.' 각 프로그램에는 사용자에게 혜택을 알리기 위해 표시할 권장 텍스트에 관한 안내가 있을 수 있습니다.

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

앱에서 사용자에게 사용 권한을 부여하고 거래가 완료되었음을 알린 후에는 앱에서 구매가 처리되었음을 Google에 알리기 위해 구매를 확인하여 구매를 처리하며 3일 이내에 구매가 자동으로 환불되고 사용 권한이 취소되지 않도록 처리해야 합니다. 다양한 유형의 구매를 확인하는 프로세스는 다음 섹션에 설명되어 있습니다.

소비성 제품

소비성 제품의 경우 앱에 보안 백엔드가 있으면 Purchases.products:consume을 사용하여 안정적으로 구매를 소비하는 것이 좋습니다. Purchases.products:get 호출 결과에서 consumptionState를 확인하여 구매가 아직 소비되지 않았는지 확인합니다. 앱이 백엔드가 없는 클라이언트 전용인 경우 Google Play 결제 라이브러리의 consumeAsync()를 사용합니다. 두 방법 모두 확인 요구사항을 충족하며 앱에서 사용자에게 자격을 부여했음을 나타냅니다. 또한 이러한 방법을 사용하면 앱에서 입력 구매 토큰에 해당하는 일회성 제품을 재구매할 수 있습니다. consumeAsync()를 사용하면 ConsumeResponseListener 인터페이스를 구현하는 객체도 전달해야 합니다. 이 객체는 소비 작업의 결과를 처리합니다. 작업 완료 시 Google Play 결제 라이브러리가 호출하는 onConsumeResponse() 메서드를 재정의할 수 있습니다.

다음 예는 관련 구매 토큰을 사용하여 Google Play 결제 라이브러리로 제품을 소비하는 방법을 보여줍니다.

Kotlin

    val consumeParams =
        ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build()
    val consumeResult = withContext(Dispatchers.IO) {
        client.consumePurchase(consumeParams)
    }

Java

    ConsumeParams consumeParams =
            ConsumeParams.newBuilder()
                .setPurchaseToken(purchase.getPurchaseToken())
                .build();

    ConsumeResponseListener listener = new ConsumeResponseListener() {
        @Override
        public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
            if (billingResult.getResponseCode() == BillingResponseCode.OK) {
                // Handle the success of the consume operation.
            }
        }
    };

    billingClient.consumeAsync(consumeParams, listener);

비소비성 제품

비소비성 구매를 확인하려면 앱에 보안 백엔드가 있는 경우 Purchases.products:acknowledge를 사용하여 구매를 안정적으로 확인하는 것이 좋습니다. Purchases.products:get 호출 결과에서 acknowledgementState를 확인하여 이전에 구매를 확인하지 않았는지 확인합니다.

앱이 클라이언트 전용인 경우 앱에서 Google Play 결제 라이브러리의 BillingClient.acknowledgePurchase()를 사용합니다. 구매를 확인하기 전에 앱은 Google Play 결제 라이브러리의 isAcknowledged() 메서드를 사용하여 이미 확인되었는지 여부를 점검해야 합니다.

다음 예는 Google Play 결제 라이브러리를 사용하여 구매를 확인하는 방법을 보여줍니다.

Kotlin

val client: BillingClient = ...
val acknowledgePurchaseResponseListener: AcknowledgePurchaseResponseListener = ...

val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
    .setPurchaseToken(purchase.purchaseToken)
val ackPurchaseResult = withContext(Dispatchers.IO) {
     client.acknowledgePurchase(acknowledgePurchaseParams.build())
}

Java

BillingClient client = ...
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = ...

AcknowledgePurchaseParams acknowledgePurchaseParams =
                AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.getPurchaseToken())
                    .build();
 client.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);

정기 결제

정기 결제 구매는 비소비성 구매와 유사하게 처리됩니다. 가능하면 Google Play Developer API의 Purchases.subscriptions.acknowledge를 사용하여 보안 백엔드에서 구매를 안정적으로 확인하세요. Purchases.subscriptions:get의 구매 리소스에서 acknowledgementState를 확인하여 구매가 이전에 확인되지 않았는지 점검합니다. 그 외의 경우 isAcknowledged()를 확인한 후 Google Play 결제 라이브러리에서 BillingClient.acknowledgePurchase()를 사용하여 정기 결제를 확인할 수 있습니다. 최초 정기 결제 구매는 모두 확인해야 합니다. 정기 결제 갱신은 확인하지 않아도 됩니다. 정기 결제를 확인해야 하는 경우에 관한 자세한 내용은 정기 결제 판매 주제를 참고하세요.

요약

다음 코드 스니펫은 이러한 단계를 요약하여 보여줍니다.

Kotlin

fun handlePurchase(Purchase purchase) {
    // Purchase retrieved from BillingClient#queryPurchasesAsync or your
    // onPurchasesUpdated.
    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 (delaying
    // notification if needed as discussed above).

    // Step 4: Notify Google the purchase was processed using the steps
    // discussed in the processing purchases section.
}

자바

void handlePurchase(Purchase purchase) {
    // Purchase retrieved from BillingClient#queryPurchasesAsync or your
    // onPurchasesUpdated.
    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 (delaying
    // notification if needed as discussed above).

    // Step 4: Notify Google the purchase was processed using the steps
    // discussed in the processing purchases section.
}

앱이 이러한 단계를 올바르게 구현했는지 확인하려면 테스트 가이드를 따르세요.

대기 중인 거래 처리

Google Play는 대기 중인 거래 또는 사용자가 구매를 시작한 시점과 구매 결제 수단이 처리되는 시점 사이에 하나 이상의 추가 단계가 필요한 거래를 지원합니다. Google에서 사용자의 결제 수단으로 요금이 청구되었다는 알림을 받을 때까지 앱에서 이러한 유형의 구매에 자격을 부여해서는 안 됩니다.

예를 들어 사용자가 나중에 현금으로 결제할 오프라인 상점을 선택하여 거래를 시작할 수 있습니다. 사용자는 알림과 이메일을 통해 코드를 수신합니다. 사용자는 오프라인 상점에 도착하면 계산원에게 코드를 사용하여 현금으로 결제할 수 있습니다. 그러면 Google은 개발자와 사용자 모두에게 결제가 수령되었음을 알립니다. 다음으로, 앱에서 사용자에게 자격을 부여할 수 있습니다.

BillingClient 초기화의 일부로 enablePendingPurchases()를 호출하여 앱의 대기 중인 거래를 사용 설정합니다. 앱은 일회성 제품의 대기 중인 거래를 사용 설정하고 지원해야 합니다. 지원을 추가하기 전에 대기 중인 거래의 구매 수명 주기를 이해해야 합니다.

앱이 PurchasesUpdatedListener를 통해 또는 queryPurchasesAsync를 호출한 결과로 새 구매를 수신한 경우 getPurchaseState() 메서드를 사용하여 구매 상태가 PURCHASED인지 또는 PENDING인지 확인합니다. 상태가 PURCHASED인 경우에 자격을 부여해야 합니다.

사용자가 구매를 완료할 때 앱이 실행 중이고 활성 Play 결제 라이브러리 연결이 있는 경우 PurchasesUpdatedListener가 다시 호출되며 PurchaseState는 이제 PURCHASED가 됩니다. 이 시점에서 앱은 구매 감지 및 처리를 위한 표준 메서드를 사용하여 구매를 처리할 수 있습니다. 또한 앱이 실행되지 않는 동안 PURCHASED 상태로 전환된 구매를 처리하려면 앱의 onResume() 메서드에서 queryPurchasesAsync()를 호출해야 합니다.

구매가 PENDING에서 PURCHASED로 전환되면 real_time_developer_notifications 클라이언트는 ONE_TIME_PRODUCT_PURCHASED 또는 SUBSCRIPTION_PURCHASED 알림을 수신합니다. 구매가 취소되면 ONE_TIME_PRODUCT_CANCELED 또는 SUBSCRIPTION_PENDING_PURCHASE_CANCELED 알림이 전송됩니다. 이 이벤트는 고객이 필수 기간 내에 결제를 완료하지 않은 경우에 발생할 수 있습니다. 언제든지 Google Play Developer API를 사용하여 구매의 현재 상태를 확인할 수 있습니다.

다중 수량 구매 처리

Google Play 결제 라이브러리 버전 4.0 이상에서 지원되는 Google Play에서는 고객이 장바구니에서 수량을 지정하여 한 번의 거래로 같은 인앱 상품을 두 개 이상 구매할 수 있습니다. 앱은 다중 수량 구매를 처리하고 지정된 구매 수량에 따라 자격을 부여해야 합니다.

다중 수량 구매를 적용하려면 앱의 프로비저닝 로직이 항목 수량을 확인해야 합니다. 다음 API 중 하나에서 quantity 필드에 액세스할 수 있습니다.

다중 수량 구매를 처리하는 로직을 추가한 후 Google Play Console의 인앱 상품 관리 페이지에서 해당 제품에 다중 수량 기능을 사용 설정해야 합니다.

사용자의 결제 구성 쿼리

getBillingConfigAsync()는 사용자가 Google Play에 사용하는 국가를 제공합니다.

BillingClient만든 후에 사용자의 결제 구성을 쿼리할 수 있습니다. 다음 코드 스니펫은 getBillingConfigAsync()를 호출하는 방법을 설명합니다. BillingConfigResponseListener를 구현하여 응답을 처리합니다. 이 리스너는 앱에서 시작된 모든 결제 구성 쿼리의 업데이트를 수신합니다.

반환된 BillingResult에 오류가 없으면 BillingConfig 객체의 countryCode 필드를 확인하여 사용자의 Play 국가를 가져올 수 있습니다.

Kotlin

// Use the default GetBillingConfigParams.
val getBillingConfigParams = GetBillingConfigParams.newBuilder().build()
billingClient.getBillingConfigAsync(getBillingConfigParams,
    object : BillingConfigResponseListener {
        override fun onBillingConfigResponse(
            billingResult: BillingResult,
            billingConfig: BillingConfig?
        ) {
            if (billingResult.responseCode == BillingResponseCode.OK
                && billingConfig != null) {
                val countryCode = billingConfig.countryCode
                ...
            } else {
                // TODO: Handle errors
            }
        }
    })

자바

// Use the default GetBillingConfigParams.
GetBillingConfigParams getBillingConfigParams = GetBillingConfigParams.newBuilder().build();
billingClient.getBillingConfigAsync(getBillingConfigParams,
    new BillingConfigResponseListener() {
      public void onBillingConfigResponse(
          BillingResult billingResult, BillingConfig billingConfig) {
        if (billingResult.getResponseCode() == BillingResponseCode.OK
            && billingConfig != null) {
            String countryCode = billingConfig.getCountryCode();
            ...
         } else {
            // TODO: Handle errors
        }
      }
    });

Google Play 게임즈 홈의 장바구니 이탈 알림 (기본적으로 사용 설정됨)

IAP를 통해 수익을 창출하는 게임 개발자의 경우 Google Play Console에서 활성 상태인 SKU를 앱 외부에서 판매하는 한 가지 방법은 사용자가 Google Play 스토어를 탐색하는 동안 이전에 중단한 구매를 완료하도록 유도하는 장바구니 이탈 알림 기능을 사용하는 것입니다. 이러한 구매는 앱 외부에서, 즉 Google Play 스토어의 Google Play 게임즈 홈에서 이루어집니다.

이 기능은 기본적으로 사용 설정되어 있습니다. 사용자가 중단한 부분부터 이어서 시작하고 개발자가 판매를 극대화할 수 있도록 돕기 위해서입니다. 하지만 장바구니 이탈 알림 기능 선택 해제 양식을 제출하여 앱에서 이 기능을 선택 해제할 수 있습니다. Google Play Console에서 SKU를 관리하는 권장사항은 인앱 상품 만들기를 참고하세요.

다음 이미지는 Google Play 스토어에 표시되는 장바구니 이탈 알림을 보여줍니다.

Google Play 스토어 화면에 이전에 중단한 구매에 대한 구매 메시지가 표시됨
그림 2. Google Play 스토어 화면에 이전에 중단한 구매에 대한 구매 메시지가 표시됩니다.

Google Play 스토어 화면에 이전에 중단한 구매에 대한 구매 메시지가 표시됨
그림 3. Google Play 스토어 화면에 이전에 중단한 구매에 대한 구매 메시지가 표시됩니다.