아래에는 now.gg 결제 모듈을 더 쉽게 이해하고 구현하기 위한 공식 now.gg 샘플 앱 및 게임 내 다양한 코드 샘플이 포함되어 있습니다.
1. now.gg 결제 모듈 파일의 압축을 풀고 패키지에서 .aar 파일을 찾습니다.
payments.aar
2. 게임의 build.gradle 파일에 다음 종속성(dependency)을 추가합니다.
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar'])
}
now.gg 결제 모듈 진행 방식:
now.gg 결제 모듈은 구매 토큰과 주문 ID를 사용하여 게임이나 앱 내 구매를 추적합니다.
Entitlement 또는 권한이라는 용어는 구매 후 유저에게 ‘인앱 상품 전달’한다는 의미입니다.
SERVICE_DISCONNECTED 오류 코드는 now.gg 결제 서비스와의 연결이 끊어짐을 뜻하며 이에 대한 해결책으로는 앱/게임을 다시 초기화하고 now.gg 결제 서비스와 다시 연결하는 것입니다.now.gg Payments를 초기화하기 전에 반드시 now.gg IAP가 사용 가능한지 확인하세요. now.gg IAP를 사용할 수 없는 경우에만 다른 IAP 서비스를 초기화해야 합니다.
if (!BillingClient.checkIAPAvailability(activity)) {
// now.gg iap를 사용할 수 없음
return;
}
BillingClient 인터페이스는 now.gg 결제 모듈과 앱/게임 간의 기본 통신 메커니즘으로 일반 결제 청구 작업 관련 동기식 및 비동기식 방법을 모두 제공합니다.
BillingClient 생성 --> Use newBuilder() 구매 메시지 리스너 설정 --> Call setListener() 앱/게임 내 구매 관련 메시지 처리 --> PurchasesUpdatedListener 빌링을 위한 PAYMENT_ID 설정 --> use setAppId(PAYMENT_ID) 초기화 후 미소비 구매 조회. --> use queryPurchasesAsync()
다음 예제는 BillingClient를 생성하고 초기화하는 방법을 보여줍니다:
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)
.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)
.build()
now.gg 결제 서비스는 비동기식(asynchronous) 연결 프로세스를 채택하며 클라이언트 설정 완료 시 콜백 메시지를 처리하기 위해 BillingClientStateListener를 사용합니다.
startConnection()을 호출합니다.startConnection()을 한 번만 호출하도록 하십시오.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()를 호출하여 이후 결제 모듈 연결 요청을 하실 수 있습니다.
}
});
앱/게임에서 사용 가능한 인앱 상품을 확인 및 호출하고 구매를 처리하는 과정입니다.
단계:
요구 사항:
첫 단계는 사용 가능한 상품을 호출하고 유저에게 표시하는 것으로 인앱 상품 세부정보를 얻기 위해서는 querySkuDetailsAsync()를 호출하게 되며 이때 SKU 세부 정보 호출 시 now.gg 결제 모듈은 유저에게 표시될 현지화된 상품 정보를 반환합니다.
querySkuDetailsAsync()를 호출할 때, nowStudio에서 구성한 상품 ID를 포함한 SkuDetailsParams 인스턴스를 전달해야 합니다.결과
다음 예는 SkuDetailsResponseListener 인터페이스를 통해 onSkuDetailsResponse()를 오버라이딩하여 호출이 완료되면 리스너에서 결과를 처리합니다.
List<String> skuList = new ArrayList<> ();
skuList.add("premium_upgrade");
skuList.add("gas");
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList);
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)
withContext(Dispatchers.IO) {
billingClient.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList ->
// 결과를 처리합니다.
}
}
}
SkuDetails를 다양한 방식으로 호출하여 인앱 상품에 대한 정보를 호출할 수 있습니다.SkuDetails에 저장합니다.유저가 구매할 수 있는 상품을 호출한 후에는 앱/게임에서 초기화를 시작합니다.
초기화를 시작하기 위해 앱의 메인 스레드에서 launchBillingFlow()를 호출합니다.
아래 BillingFlowParams는 SkuDetails에 저장된 관련 세부 정보를 포함하므로 참조 목적으로 호출되며 이때 SkuDetails는 querySkuDetailsAsync()를 호출하여 가져옵니다.
먼저 아래와 같이 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)
// 결과
BillingClient.BillingResponse를 사용해 반환된 메시지 코드를 처리하실 수 있습니다.
0)는 성공적인 실행을 나타냅니다.launchBillingFlow() 호출이 성공적으로 완료되면 구매 화면이 표시됩니다.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()를 통해 기타 응답 코드를 처리하실 수 있습니다.
구매 시작 후에는 다음과 같은 방식으로 구매를 처리하실 수 있습니다.
구매는 verifyPurchase API를 사용하여 확인할 수 있습니다.
구매를 확인하려면, 앱 백엔드 서버에서 purchaseToken을 사용하여 verifyPurchase API를 호출하십시오. 다음 샘플 요청 코드에 설명되어 있습니다:
import requests
url = "https://payments-api.now.gg/v2/sellers/order/verifyPurchase"
headers = {
"Authorization": "<payment_api_key_here>",
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"purchaseToken": "<purchase_token_here>"
}
response = requests.post(url, headers=headers, data=data)
print("响应状态码:", response.status_code)
print("响应正文:", response.text)
curl -X \ POST "https://payments-api.now.gg/v2/sellers/order/verifyPurchase" \ -H \ "Authorization: <payment_api_key_here>" \ -H \ "Content-Type: application/x-www-form-urlencoded" \ -d "purchaseToken=<purchase_token_here>"
구매를 확인한 후, 앱에서 사용자가 구매한 제품의 권한을 부여하고 제품을 소비된 것으로 표시(소모품의 경우)하거나 승인된 것으로 표시(구독의 경우)할 수 있습니다.
구매를 인증한 후 구매한 상품에 대한 권한을 유저에게 부여하고 해당 제품 전달 완료 표시를 할 수 있습니다.
구매를 승인하는 데 사용할 수 있는 두 가지 방법을 제공했습니다:
권한을 부여하기 위해 consumeAsync()를 호출하여 유저에게 상품에 대한 권한을 부여하고 소비/전달된 후 재구매가 가능하도록 합니다.
사용 consumeAsync():
consumed(소비됨) 으로 전환 시 now.gg 결제 서비스에서 재구매가 가능하도록 설정됩니다.ConsumeResponseListener 인터페이스와 연결된 객체를 전달합니다.onConsumeResponse()를 호출합니다.ConsumeResponseListener 인터페이스를 업데이트하는 객체가 업데이트되지 않는 경우 발생할 수 있습니다.queryPurchasesAsync()를 호출하여 사용되지 않은 구매를 확인하는 것을 권장합니다. 여기를 참고하세요.다음 샘플 코드는 구매 인증 및 소비 작업을 간단히 보여드립니다.
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) {
// 여기서 소비 작업 성공을 처리합니다.
}
})
}
백엔드 서버가 있는 앱의 서버측 구매 소비 시 consumePurchase API를 사용하시기 바랍니다.
구매를 인증한 후에는 소비됨으로 표시해야 합니다.
구매 소비에는 다음이 포함됩니다.
다음 샘플 코드는 consumePurchase API를 통한 관련 요청 방법을 보여드립니다
import requests
url = "https://payments-api.now.gg/v2/order/consumePurchase"
headers = {
"Authorization": "<payment_api_key_here>",
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"purchaseToken": "<purchase_token_here>",
"developerPayload": "developerPayload" // 선택 사항
}
response = requests.post(url, headers=headers, data=data)
print("응답 상태 코드:", response.status_code)
print("응답 본문:", response.text)
curl --location --request POST 'https://payments-api.now.gg/v2/order/consumePurchase' \
--header 'Authorization: <payment_api_key_here>' \
--header 'Content-Type': 'application/x-www-form-urlencoded' \
--data '{
"purchaseToken": "<purchase_token_here>",
"developerPayload" : "developerPayload" // 선택 사항
}'
Reference
consumePurchase API를 호출하기 전에 사용자에게 할당해야 합니다.queryPurchasesAsync를 호출하여 미소비 구매가 없는지 확인하세요. 여기를 참고하세요.Payment API Key는 nowStudio의 자격 증명에 있습니다. (자세히)구매를 인정하는 두 가지 방법을 제공했습니다.
참고: 백엔드 서버가 없는 클라이언트 전용 앱인 경우 이 방법을 사용해야 합니다.
방법:
acknowledgePurchase() 메서드를 호출합니다.
acknowledgePurchase()에 구매 토큰을 포함하여 acknowledgePurchase()를 호출하여 구매한 제품의 상태를 acknowledged로 업데이트합니다. 이로 인해 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)
}
acknowledgePurchase API는 백엔드 서버를 가진 앱의 서버 측 구매 인정에 사용해야 합니다.
구매를 확인한 후에는 반드시 인정 처리해야 합니다.
다음 샘플 코드는 acknowledgePurchase API를 사용한 관련 요청을 설명합니다.
import requests
url = "https://payments-api.now.gg/v2/seller/order/acknowledgepurchase"
payload = 'purchaseToken=<purchase_token_here>'
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)
참조:
PurchasesUpdatedListener로 구매 결과를 처리하실 수 있지만 일부 상황 속 유저가 성공적으로 구매한 모든 항목이 앱/게임에 바로 업데이트되지 않을 수도 있습니다.
일부 상황:
PurchasesUpdatedListener로부터 성공적인 구매 알림을 받지 못할 시해결 방법:
onResume() 및 onCreate()에서 앱이 BillingClient.queryPurchasesAsync(SkuType.ALL)를 호출하는지 확인합니다.다음 섹션에서는 미소비된인앱 구매 및 구독을 확인하고 할당하는 과정을 설명합니다.
아래와 같이 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() 메서드를 사용하여 미소비된 구매를 조회해야 하며, 이를 통해 사용자의 이전 게임 세션에서 미소비된 구매가 올바르게 할당되도록 해야 합니다.
구독 상태 업데이트에 대한 알림을 보내기 위해 SubscriptionStatusCallback API를 제공하는 데 필요한 API입니다.
다음은 관련 워크플로입니다:
중요: Subscription Status Callback API.
목차
목차
문서 Rev. 1.0