네이티브 안드로이드용 결제 모듈

now.gg 결제 모듈을 현 개발 환경에 적용하는 방법을 설명해 드립니다.

아래에는 now.gg 결제 모듈을 더 쉽게 이해하고 구현하기 위한 공식 now.gg 샘플 앱 및 게임 내 다양한 코드 샘플이 포함되어 있습니다.


SDK 라이브러리 추가

결제 라이브러리가 포함된 now.gg SDK 패키지를 다운로드한 뒤 아래 단계에 따라 개발 환경에 추가합니다.

중요: now.gg SDK 패키지에는 결제 모듈, 데모 앱 및 샘플 코드가 포함되어 있습니다.

1. now.gg 결제 모듈 파일의 압축을 풀고 패키지에서 .aar 파일을 찾습니다.

 Payments.aar

2. 게임의 build.gradle 파일에 다음 종속성(dependency)을 추가합니다.

dependencies {
         implementation fileTree(dir: 'libs', include: ['*.aar'])
 }


빌링 클라이언트 사용

구매 진행 사이클

now.gg 결제 모듈 진행 방식:

  • 유저가 구매할 수 있는 상품을 호출합니다.
  • 호출된 인앱 상품을 구매하기 위한 초기화가 시작됩니다.
  • 구매 진행:
    • 구매 인증
    • 상품/컨텐츠 전달
    • 구매한 상품을 소비/전달로 표시하여 재구매가 가능

일부 용어

now.gg 결제 모듈은 구매 토큰주문 ID를 사용하여 게임이나 앱 내 구매를 추적합니다.

구매 토큰
  • 구매자의 상품 소유권 및 권한을 표시합니다.
  • 특정 제품 및 관련 제품 SKU와 연결되어 있습니다.
  • 인앱 구매 시 성공한 후에만 생성됩니다.
  • 일회성 구매 상품의 경우, 거래할 때마다 새로운 구매 토큰이 생성됩니다.
주문 ID
  • now.gg 결제 서비스를 통한 거래를 표시합니다.
  • 구매, 환불 또는 분쟁을 추적하기 위한 용도로 사용됩니다.
  • 모든 거래에 대해 고유한 주문 ID가 생성됩니다.
소유권 및 권한

Entitlement  또는 권한이라는 용어는 구매 후 유저에게 ‘인앱 상품 전달’한다는 의미입니다.

오류 처리
앱/게임 내에서 발생한 오류 및 메시지는 빌링 메시지 코드를 사용하여 참조됩니다.
예: SERVICE_DISCONNECTED 오류 코드는 now.gg 결제 서비스와의 연결이 끊어짐을 뜻하며 이에 대한 해결책으로는 앱/게임을 다시 초기화하고 now.gg 결제 서비스와 다시 연결하는 것입니다.

1. BillingClient 생성 및 초기화

BillingClient 인터페이스는 now.gg 결제 모듈과 앱/게임 간의 기본 통신 메커니즘으로 일반 결제 청구 작업 관련 동기식 및 비동기식 방법을 모두 제공합니다.

BillingClient 생성                                --> Use newBuilder()

 구매 메시지 리스너 설정                                --> Call setListener()

 앱/게임 내 구매 관련 메시지 처리                         --> PurchasesUpdatedListener

 빌링을 위한 PAYMENT_ID 설정                          --> use setAppId(PAYMENT_ID)

 기존 인게임 유저 ID 사용 시 IN_GAME_ID 설정             --> use setInGameId(IN_GAME_ID)

 초기화 후 미소비 구매 조회.                             --> use queryPurchasesAsync()
now.gg 결제 SDK는 유저를 식별하고 거래를 시작하기 위해 고유한 유저ID를 필요로 하며 이때 선택적으로 유저는 결제 정보를 저장하여  향후 거래 시 해당 세부 정보를 입력하지 않을 수 있습니다.

다음 샘플 코드와 같이 BillingClient를 생성하고 초기화하는 동안 사용자의 고유 userID(IN_GAME_ID)를 now.gg 결제 SDK에 전달합니다.

private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
    @Override
    public void onPurchasesUpdated(int billingResult, List<Purchase> purchases) {
         if (billingResult == BillingResponse.OK) {
             if (purchases == null) {
                 return;
             }
             for (Purchase purchase: purchases) {
                 String payload = purchase.getDeveloperPayload(); // 개발자 페이로드 가져오기
                  // 여기에서 성공적인 구매 처리
             }
         } else if (billingResult == BillingResponse.USER_CANCELED) {
            Log.i(TAG, "onPurchasesUpdated() - user cancelled the purchase flow - skipping");
         } else {
            Log.w(TAG, "onPurchasesUpdated() got unknown billingResult: " + billingResult);
         }
    }
 };

 private BillingClient billingClient = BillingClient.newBuilder(activity)
    .setListener(purchasesUpdatedListener)
    .setAppId(PAYMENT_ID)
    .setInGameId(IN_GAME_ID) // unique ID to identify the user.
    .build();
private val purchasesUpdatedListener = object : PurchasesUpdatedListener {
    override fun onPurchasesUpdated(billingResult: Int, purchases: List<Purchase>?) {
        if (billingResult == BillingResponse.OK) {
            if (purchases == null) {
                return
            }
            for (purchase in purchases) {
                val payload = purchase.developerPayload // 개발자 페이로드 가져오기
                 // 여기에서 성공적인 구매 처리
            }
        } else if (billingResult == BillingResponse.USER_CANCELED) {
            Log.i(TAG, "onPurchasesUpdated() - user cancelled the purchase flow - skipping")
        } else {
            Log.w(TAG, "onPurchasesUpdated() got unknown billingResult: $billingResult")
        }
    }
}

private val billingClient = BillingClient.newBuilder(activity)
    .setListener(purchasesUpdatedListener)
    .setAppId(PAYMENT_ID)
    .setInGameId(IN_GAME_ID) // unique ID to identify the user.
    .build()

중요:

  • IN_GAME_ID 는 유저를 식별하기 위해 사용됩니다.
    • IN_GAME_ID는 각 유저마다 고유해야 합니다.
      • 유저가 구매 중에 결제 관련 정보를 저장하기로 결정하면 해당 정보가 해당 IN_GAME_ID에 연결되므로 IN_GAME_ID는 고유해야 합니다.
      • 결제 통합 테스트: 특정 IN_GAME_ID를 사용하여 결제를 테스트하려면 nowStudio의 내부 테스터로 추가하십시오.
  • PAYMENT_ID 는 앱을 식별하기 위해 사용됩니다.
    • 해당 PAYMENT_IDnowStudio의 앱 세부 정보 아래 자격 증명 탭에서 확인하실 수 있습니다. (자세히)
    • PAYMENT_ID는 nowStudio에 앱/게임을 추가한 후에 사용할 수 있습니다.
  • 환불 – now.gg 결제 모듈과 관련된 환불 절차는 여기에 설명되어 있습니다.
참조

2. 연결 설정

now.gg 결제 서비스는 비동기식(asynchronous) 연결 프로세스를 채택하며 클라이언트 설정 완료 시 콜백 메시지를 처리하기 위해 BillingClientStateListener를 사용합니다.

  • now.gg 결제 서비스와의 연결을 설정하려면 startConnection()을 호출합니다.
  • 끊어진 연결을 처리하기 위해 재연결 로직이 필요하며 이를 위해 onBillingServiceDisconnected() 콜백 함수를 오버라이딩해야 합니다.
  • onBillingServiceDisconnected() 오버라이딩 중 BillingClientstartConnection()를 호출하여 now.gg 결제 서비스와의 연결을 다시 설정하는지 확인합니다.
  • 관련 함수 호출 전 BillingClient와의 연결을 확인합니다.
  • 미소비 구매 조회: 초기화 후 queryPurchasesAsync() 메서드를 사용하여 미소비된 구매를 조회해야 하며, 이를 통해 사용자의 이전 게임 세션에서 미소비된 구매가 올바르게 할당되도록 해야 합니다.
    • 이 방법은 구매 직후 앱이 충돌하거나 구매 과정에서 인터넷 연결이 끊어진 경우 특히 유용합니다.

중요: 재연결 로직을 직접 작성하시거나 아래 예시를 사용하실 수도 있습니다.

billingClient.startConnection(new BillingClientStateListener() {
  @Override
  public void onBillingSetupFinished(int billingResult) {
    if (billingResult == 0) {
      // BillingClient가 준비되었습니다. 이제 여기서 구매 관련 처리를 하실 수 있습니다. 
      QueryPurchasesParams params = QueryPurchasesParams.newBuilder().setProductType(BillingClient.SkuType.ALL).build();
      billingClient.queryPurchasesAsync(params, new PurchasesResponseListener() {
        @Override
        public void onQueryPurchasesResponse(BillingResult billingResult, List < Purchase > list) {
        // 구매한 항목을 사용자에게 할당하고 소비/확인을 호출하십시오.
        }
      });
    }
  }
  @Override
  public void onBillingServiceDisconnected() {
    // startConnection()를 호출하여 이후 결제 모듈 연결 요청을 하실 수 있습니다. 
  }
});
billingClient.startConnection(object: BillingClientStateListener {
 override fun onBillingSetupFinished(billingResult: int) {
     if (billingResult == 0) {
        // BillingClient가 준비되었습니다. 이제 여기서 구매 관련 처리를 하실 수 있습니다.
         val params = QueryPurchasesParams.newBuilder()
             .setProductType(BillingClient.SkuType.ALL)
             .build()

         billingClient.queryPurchasesAsync(params, object: PurchasesResponseListener {
             override fun onQueryPurchasesResponse(billingResult: BillingResult, list: List) {
                 // 구매한 항목을 사용자에게 할당하고 소비/확인을 호출하십시오.
             }
         })

     }
 }
 override fun onBillingServiceDisconnected() {
     // startConnection()를 호출하여 이후 결제 모듈 연결 요청을 하실 수 있습니다.
 }
});
참조


상품 및 구매

앱/게임에서 사용 가능한 인앱 상품을 확인 및 호출하고 구매를 처리하는 과정입니다.

단계:

  1. 사용 가능한 상품을 호출합니다.
  2. 초기화합니다.
  3. 결제를 처리합니다.

요구 사항:

1. 사용 가능한 상품 호출

첫 단계는 사용 가능한 상품을 호출하고 유저에게 표시하는 것으로 인앱 상품 세부정보를 얻기 위해서는 querySkuDetailsAsync()를 호출하게 되며 이때 SKU 세부 정보 호출 시 now.gg 결제 모듈은 유저에게 표시될 현지화된 상품 정보를 반환합니다.

중요: 인앱 상품을 유저에게 표시하기 전 업데이트된 상품 정보를 반환하는 SKU 세부 정보를 호출합니다.

SKU 유형 및 주문 문자열

SKUType:

  • SkuType.SUBS – 구독
  • SkuType.INAPP – 인앱 구매
  • SkuType.ALL – 모든 SKUs

querySkuDetailsAsync() 호출 시 nowStudio에서 설정한 상품 ID와 함께 SKUType을 가지는 SkuDetailsParams 인스턴스를 전달합니다.

결과

사용 가능한 상품을 호출한 후 결과를 처리하기 위해 리스너를 할당합니다.

다음 예는 SkuDetailsResponseListener 인터페이스를 통해 onSkuDetailsResponse()를 오버라이딩하여 호출이 완료되면 리스너에서 결과를 처리합니다.

List<String> skuList = new ArrayList<> (); 
 skuList.add("premium_upgrade"); 
 skuList.add("gas"); 
 SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder(); 
 params.setSkusList(skuList).setType(SkuType.ALL); 
 billingClient.querySkuDetailsAsync(params.build(), 
     new SkuDetailsResponseListener() { 
             @Override 
         public void onSkuDetailsResponse(int billingResult, 
                 List<SkuDetails> skuDetailsList) { 
                 // 결과를 처리합니다.
         } 
     });
fun querySkuDetails() { 
 val skuList = ArrayList<String>() 
 skuList.add("premium_upgrade") 
 skuList.add("gas") 
 val params = SkuDetailsParams.newBuilder() 
 params.setSkusList(skuList).setType(SkuType.ALL) 
    withContext(Dispatchers.IO) { 
            billingClient.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList -> 
            // 결과를 처리합니다. 
        } 
    } 
 }

중요

  • 유저는 첫 번째 주문이 완료되고 해당 상품이 전달된 후에만 재구매할 수 있습니다.
  • SkuDetails를 다양한 방식으로 호출하여 인앱 상품에 대한 정보를 호출할 수 있습니다.
  • now.gg 결제 모듈은 모든 호출 결과를 SkuDetails에 저장합니다.
참조

2. 빌링 초기화

유저가 구매할 수 있는 상품을 호출한 후에는 앱/게임에서 초기화를 시작합니다.

초기화를 시작하기 위해 앱의 메인 스레드에서 launchBillingFlow()를 호출합니다.

아래 BillingFlowParams는 SkuDetails에 저장된 관련 세부 정보를 포함하므로 참조 목적으로 호출되며 이때 SkuDetails는 querySkuDetailsAsync()를 호출하여 가져옵니다.

BillingFlowParams 생성

먼저 아래와 같이 BillingFlowParams.Builder 클래스를 사용하여 BillingFlowParams 객체를 생성합니다.

// 빌링 초기화 시 참조 목적
 Activity activity = ...; 
      
 // querySkuDetailsAsync()를 호출하여 skuDetails 값을 호출합니다.
 BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() 
    .setSkuDetails(skuDetails) 
    .setDeveloperPayload("optional developer payload")
    .build(); 
 billingClient.launchBillingFlow(activity, billingFlowParams); 
      
     // 결과
// 빌링 초기화 시 참조 목적
 val activity : Activity = ...; 
  
 // querySkuDetailsAsync()를 호출하여 skuDetails 값을 호출합니다. 
 val flowParams = BillingFlowParams.newBuilder() 
         .setSkuDetails(skuDetails) 
         .setDeveloperPayload("optional developer payload")
         .build() 
 billingClient.launchBillingFlow(activity, flowParams)
 // 결과
References:

결과 처리

BillingClient.BillingResponse를 사용해 반환된 메시지 코드를 처리하실 수 있습니다.

  • 응답 코드 OK(0)는 성공적인 실행을 나타냅니다.
  • launchBillingFlow() 호출이 성공적으로 완료되면 구매 화면이 표시됩니다.
  • now.gg 결제 모듈은 onPurchasesUpdated()를 호출하여 구매 결과를 PurchasesUpdatedListener 인터페이스에 연결된 리스너에 전달합니다.
  • 해당 리스너는 setListener()를 통해 빌링 클라이언트를 초기화하는 동안 설정됩니다.

다음은 onPurchasesUpdated() 오버라이딩 예시 입니다:

@Override 
 void onPurchasesUpdated(int billingResult, List<Purchase>) { 
     if (billingResult == 0 && purchases != null) { 
         for (Purchase purchase : purchases) { 
             String payload = purchase.getDeveloperPayload(); // 개발자 페이로드 가져오기
             handlePurchase(purchase); 
         } 
     } else if (billingResult == 1) { 
             // 오류 처리 - 유저가 구매 화면 닫음
     } else { 
             // 오류 처리 - 기타 (에러 코드 참조) 
     } 
 }
override fun onPurchasesUpdated(billingResult: int, purchases: List<Purchase>?) { 
     if (billingResult == 0 && purchases != null) { 
         for (purchase in purchases) { 
                 val payload: String = purchase.developerPayload // 개발자 페이로드 가져오기
                 handlePurchase(purchase)
         } 
     } else if (billingResult == 1) { 
             // 오류 처리 - 유저가 구매 화면 닫음
     } else { 
             // 오류 처리 - 기타 (에러 코드 참조) 
     } 
 }

중요: onPurchasesUpdated()를 통해 기타 응답 코드를 처리하실 수 있습니다.

자세히

  • 구매 토큰은 구매가 성공적으로 완료될 시 생성됩니다.
    • 구매 토큰은 인앱 구매 상품과 구매한 해당 유저를 나타내는 고유 식별자이기도 합니다.
  • 주문 ID는 모든 주문 시 생성되며 now.gg 결제 서비스 관련 분쟁 또는 환불 시 사용될 수 있습니다.
  • 주문 ID 및 기타 관련 정보가 포함된 모든 결제 사본 역시 유저에게 이메일로 전송됩니다.
References:

3. 구매 처리

구매 시작 후에는 다음과 같은 방식으로 구매를 처리하실 수 있습니다.

  1. 구매 인증
  2. 권한 부여
  3. 구매 결과
  4. 미사용 구매 호출

3.1 구매 인증

구매 인증을 통해 구매의 진위 여부를 확인하실 수 있으며 이때 사기 피해를 줄이는 가장 좋은 방법으로는 상품을 유저에게 전달하기 전 모든 구매를 인증하는 것입니다.

인증 방식:

  • VerifyPurchase API
  • 공개 key
3.1.1 VerifyPurchase API 사용하기

구매를 확인하는 첫 번째 방법은 verifyPurchase API를 사용하는 것입니다. 이 API를 사용하여 우리의 백엔드 서버에서 구매를 확인할 수 있습니다.

구매를 확인하려면, 앱 백엔드 서버에서 purchaseToken을 사용하여 verifyPurchase API를 호출하십시오. 다음 샘플 요청 코드에 설명되어 있습니다:

curl --location 'https://cloud-api.bluestacks.cn/v2/seller/order/verifyPurchase' \
 --header 'Authorization: <payment_api_key_here>' \
 --header 'Content-Type: application/x-www-form-urlencoded' \
 --data-urlencode 'purchaseToken=<nowgg_purchase_token>'
import requests

  url = "https://cloud-api.bluestacks.cn/v2/seller/order/verifyPurchase"

  payload = 'purchaseToken=<nowgg_purchase_token>'
  headers = {
  'Authorization': '<payment_api_key_here>',
  'Content-Type': 'application/x-www-form-urlencoded'
  }

  response = requests.request("POST", url, headers=headers, data=payload)

  print(response.text)

중요

  • verifyPurchase API는 앱 백엔드 서버에서 호출되어야 합니다.
  • 구매가 성공적으로 완료되면 purchaseToken 가 반환됩니다. 더욱 자세한 내용은 아래를 참조하시기 바랍니다.

3.1.2 공개 키

구매를 인증하는 두 번째 방법은 공개 키를 사용하는 것으로 데모의 billingManager.java에 자세히 설명되어 있습니다.

  • billingManager.java 내
  • verifyValidSignature 및 verifyPurchase를 확인합니다.

데모의 billingManager.java 내 위에서 언급한 함수 및 샘플 코드를 참조하여 앱에서 구매 인증을 하실 수 있습니다.

중요:

  • 여기에 나열된 단계에 따라 공개 키를 생성하실 수 있습니다.
  • 앱에 백엔드 서버가 있는 경우 이를 통해 구매를 검증하는 것을 권장해 드립니다.
  • 유저에게 상품에 대한 권한을 부여하기 전에 모든 구매를 인증하시는 것을 권장해 드립니다.

3.2 권한 부여

구매를 확인한 후, 앱에서 사용자가 구매한 제품의 권한을 부여하고 제품을 소비된 것으로 표시(소모품의 경우)하거나 승인된 것으로 표시(구독의 경우)할 수 있습니다.

1. 소모품

구매를 인증한 후 구매한 상품에 대한 권한을 유저에게 부여하고 해당 제품 전달 완료 표시를 할 수 있습니다.

2. 구독

구매를 승인하는 데 사용할 수 있는 두 가지 방법을 제공했습니다:


3.2.1 소모품
A. 사용 consumeAsync

권한을 부여하기 위해 consumeAsync()를 호출하여 유저에게 상품에 대한 권한을 부여하고 소비/전달된 후 재구매가 가능하도록 합니다.

사용 consumeAsync():

  • consumeAsync()에 구매 토큰을 포함하여 구매한 삼품의 상태를 consumed(소비됨) 으로 전환 시 now.gg 결제 서비스에서 재구매가 가능하도록 설정됩니다.
  • 소비 작업의 결과를 업데이트하기 위해 ConsumeResponseListener 인터페이스와 연결된 객체를 전달합니다.
  • 소비 작업이 완료되면 now.gg 빌링 서비스는 오버라이딩할 수 있는 onConsumeResponse()를 호출합니다.

중요 정보:

  • 구매 토큰을 사용하여 동일한 구매에 대한 여러 권한 부여를 확인해야 합니다.
  • 이는 소비 요청이 실패하고 ConsumeResponseListener 인터페이스를 업데이트하는 객체가 업데이트되지 않는 경우 발생할 수 있습니다.
  • 구매일로부터 3일 이내에 권한이 부여되지 않으면 거래가 취소되고 사용자에게 환불이 시작됩니다.

다음 샘플 코드는 구매 인증 및 소비 작업을 간단히 보여드립니다.

void handlePurchase(Purchase purchase) {
    // BillingClient#queryPurchasesAsync 또는 PurchasesUpdatedListener로부터 구매 호출
    Purchase purchase = ...;

    // 구매를 인증합니다.
    // 해당 purchaseToken에 대한 권한이 이미 부여되지 않았는지 확인합니다.
    // 유저에게 권한을 부여합니다.

    ConsumeResponseListener listener = new ConsumeResponseListener() {
        @Override
        public void onConsumeResponse(int billingResult, String purchaseToken) {
            if (billingResult == 0) {
                // 여기서 소비 작업 성공을 처리합니다.
            }
        }
    };

    billingClient.consumeAsync(purchase.getPurchaseToken(), listener);
}
fun handlePurchase(purchase: Purchase) {
    // BillingClient#queryPurchasesAsync 또는 PurchasesUpdatedListener로부터 구매 호출
    val purchase: Purchase = ...;

    // 구매를 인증합니다.
    // 해당 purchaseToken에 대한 권한이 이미 부여되지 않았는지 확인합니다.
    // 유저에게 권한을 부여합니다.


    billingClient.consumeAsync(purchase.getPurchaseToken(), {
        billingResult,
        outToken - >
        if (billingResult == 0) {
            // 여기서 소비 작업 성공을 처리합니다.
        }
    })
}
참조:

B. 사용 consumePurchase API

백엔드 서버가 있는 앱의 서버측 구매 소비 시 consumePurchase API를 사용하시기 바랍니다.

구매를 인증한 후에는 소비됨으로 표시해야 합니다.

구매 소비에는 다음이 포함됩니다.

  • 구매한 제품을 유저에게 할당합니다.
  • 제품이 소비되었음을 now.gg에 알립니다.
샘플 코드

다음 샘플 코드는 consumePurchase API를 통한 관련 요청 방법을 보여드립니다

import requests

  url = "https://cloud-api.bluestacks.cn/v2/order/consumePurchase"

  payload = 'purchaseToken=nowgg-_purchase_token'
  headers = {
    'Authorization': 'payment_api_key_here',
    'Content-Type': 'application/x-www-form-urlencoded'
  }

  response = requests.request("POST", url, headers=headers, data=payload)

  print(response.text)

Reference

Important Information

  • 구매한 제품을 consumePurchase API를 호출하기 전에 사용자에게 할당해야 합니다.
  • API 키Payment API Key는 nowStudio의 자격 증명에 있습니다. (자세히)
  • 구매가 성공적으로 완료되면 purchaseToken 가 반환됩니다. 더 자세한 내용은 다음 문서를 참조하시기 바랍니다.


3.2.2 구독

중요 정보

  • 구독 구매를 인정받는 것은 사용자에게 구매 권한을 부여하는 중요한 단계입니다.
  • 구독 기간은 인정받은 날짜부터 시작됩니다.
  • 구매가 구매일로부터 3일 이내에 인정받지 않을 경우 구매가 취소되고 결제 금액이 사용자에게 환불됩니다.

구매를 인정하는 두 가지 방법을 제공했습니다.

A. 클라이언트 – 사용 acknowledgePurchase
다음 플로우는 클라이언트 측에서 구매 항목을 인정하는 데 사용됩니다.

참고: 백엔드 서버가 없는 클라이언트 전용 앱인 경우 이 방법을 사용해야 합니다.

방법:

  • acknowledgePurchase() 메서드를 호출합니다.
    • 이 메서드는 구매한 제품의 권한을 사용자에게 부여하고, 인정된 후에는 재구매할 수 있는 상태가 됩니다.
  • acknowledgePurchase()에 구매 토큰을 포함하여 acknowledgePurchase()를 호출하여 구매한 제품의 상태를 acknowledged로 업데이트합니다. 이로 인해 now.gg 결제 서비스가 재구매를 가능하게 합니다.
  • 인정 작업의 결과를 업데이트하려면 AcknowledgePurchaseResponseListener 인터페이스를 구현한 객체를 전달해야 합니다.
  • 인정 작업이 완료되면 now.gg 결제 서비스가 onAcknowledgePurchaseResponse()를 호출합니다. 이 메서드는 사용자 정의가 가능합니다.

중요한 정보:

  • 같은 구매에 대해 여러 권한이 부여되지 않았는지, 구매 토큰을 사용하여 확인해야 합니다.
  • 인정 요청이 실패하고 AcknowledgePurchaseResponseListener 인터페이스를 업데이트하지 않은 경우 이와 같은 상황이 발생할 수 있습니다.

다음 코드 스니펫은 구매 확인인정 작업을 보여줍니다:

void handlePurchase(Purchase purchase) {
    // BillingClient#queryPurchasesAsync 또는 PurchasesUpdatedListener로부터 구매 호출
    Purchase purchase = ...;

    // 구매를 인증합니다.
    // 해당 purchaseToken에 대한 권한이 이미 부여되지 않았는지 확인합니다.
    // 유저에게 권한을 부여합니다.
   
    // 구매 확인
    final AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = new AcknowledgePurchaseResponseListener() {
          @Override
          public void onAcknowledgePurchaseResponse(@BillingResponse int responseCode) {
              if (responseCode == 0) {
                       // Handle the success of the acknowledgement operation.
                   }

          }
    };


   AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
          .setPurchaseToken(purchaseToken)
          .build();
   BillingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
}
fun handlePurchase(purchase: Purchase) {
    // BillingClient#queryPurchasesAsync 또는 PurchasesUpdatedListener로부터 구매 호출
    val purchase: Purchase = ...;

    // 구매를 인증합니다.
    // 해당 purchaseToken에 대한 권한이 이미 부여되지 않았는지 확인합니다.
    // 유저에게 권한을 부여합니다.

    // 구매 확인
    val acknowledgePurchaseResponseListener = object : AcknowledgePurchaseResponseListener {
     override fun onAcknowledgePurchaseResponse(@BillingResponse responseCode: Int) {
         if (responseCode == 0) {
             // Handle the success of the acknowledgement operation.
         }
     }

    val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
        .setPurchaseToken(purchaseToken)
        .build()
    BillingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener)
}
참조:

B. Server side – 사용 acknowledgePurchase API

acknowledgePurchase API는 백엔드 서버를 가진 앱의 서버 측 구매 인정에 사용해야 합니다.

구매를 확인한 후에는 반드시 인정 처리해야 합니다.

샘플 코드

다음 샘플 코드는 acknowledgePurchase API를 사용한 관련 요청을 설명합니다.

import requests

  url = "https://cloud-api.bluestacks.cn/v2/seller/order/acknowledgepurchase"

  payload = 'purchaseToken=nowgg-_purchase_token'
  headers = {
    'Authorization': 'payment_api_key_here',
    'Content-Type': 'application/x-www-form-urlencoded'
  }

  response = requests.request("POST", url, headers=headers, data=payload)

  print(response.text)

참조:

Important Information

  • API 키Payment API Key는 nowStudio의 자격 증명에 있습니다. (자세히)
  • 구매가 성공적으로 완료되면 purchaseToken 가 반환됩니다. 더 자세한 내용은 다음 문서를 참조하시기 바랍니다.


3.3 구매 결과

PurchasesUpdatedListener로 구매 결과를 처리하실 수 있지만 일부 상황 속 유저가 성공적으로 구매한 모든 항목이 앱/게임에 바로 업데이트되지 않을 수도 있습니다.

일부 상황:

  • 다중 기기
    유저가 한 기기에서 구매한 후 구입한 제품이 업데이트되지 않은 다른 기기에서 해당 항목을 사용하려고 할 시
  • 연결/네트워크 손실
    연결/네트워크 끊김 현상으로 인해 앱/게임이 PurchasesUpdatedListener로부터 성공적인 구매 알림을 받지 못할 시
  • 앱 외부에서 구매
    외부에서 이루어진 구매를 유저가 사용할 수 있도록 수동 처리해야 할 시

해결 방법:

  • onResume()onCreate()에서 앱이 BillingClient.queryPurchasesAsync(SkuType.ALL)를 호출하는지 확인합니다.

4. 미소비 구매 처리

다음 섹션에서는 미소비된인앱 구매 및 구독을 확인하고 할당하는 과정을 설명합니다.

아래와 같이 queryPurchasesAsync를 사용하여 유저의 사용되지 않은 구매 목록을 호출하실 수 있습니다.

QueryPurchasesParams params = QueryPurchasesParams.newBuilder().setProductType(BillingClient.SkuType.ALL).build();
 billingClient.queryPurchasesAsync(params, new PurchasesResponseListener() {
   @Override
   public void onQueryPurchasesResponse(BillingResult billingResult, List < Purchase > list) {
         // 구매한 항목을 사용자에게 할당하고 소비/확인을 호출하십시오.
   }
 });
val params = QueryPurchasesParams.newBuilder()
    .setProductType(BillingClient.SkuType.ALL)
    .build()

 billingClient.queryPurchasesAsync(params, object : PurchasesResponseListener {
     override fun onQueryPurchasesResponse(billingResult: BillingResult, list: List) {
          // 구매한 항목을 사용자에게 할당하고 소비/확인을 호출하십시오.
     }
 })

중요 정보

  • 초기화 후 queryPurchasesAsync() 메서드를 사용하여 미소비된 구매를 조회해야 하며, 이를 통해 사용자의 이전 게임 세션에서 미소비된 구매가 올바르게 할당되도록 해야 합니다.
    • 이 방법은 구매 직후 앱이 충돌하거나 구매 과정에서 인터넷 연결이 끊어진 경우 특히 유용합니다.
  • 자세한 내용은 연결 설정 섹션을 참조하십시오.

5. 구독 상태 콜백 API

(개발자 제공)

구독 상태 업데이트에 대한 알림을 보내기 위해 SubscriptionStatusCallback API를 제공하는 데 필요한 API입니다.
다음은 관련 워크플로입니다:

  • nowStudio는 이 API를 사용하여 요청 데이터를 게임 백엔드로 보냅니다.
  • 귀하의 게임 백엔드는 당사가 제공한 API 키를 사용하여 요청을 인증할 수 있습니다. (웹훅 API 키).
  • 구독의 현재 상태에 따라 사용자의 구매 권한을 할당할 수 있습니다. (여기에서 정의됨).
  • 제공한 콜백 URL을 사용하여 요청 데이터를 보낸 후, 귀하의 API에서 응답을 기다립니다.
    • 상태 코드 200과 성공 여부가 true인 응답을 받지 못하면 일정 시간 동안 재시도합니다.

중요: Subscription Status Callback API.



중요

  • SDK 적용 테스트 시 테스트 단계로 계속 진행합니다.
  • now.gg 결제 모듈 관련 데모 프로젝트는 여기에서 확인해 보실 수 있습니다.
×
클립보드에 복사된 텍스트
copyLinkText
질문이 있으신가요? 다음 주소로 문의하세요. dev-support@now.gg