여기에서는 앱에서 인앱 결제 기능을 사용하기 위해 필요한 설정 방법을 알아보겠습니다.
Gamebase는 하나의 통합된 결제 API를 제공해 게임에서 손쉽게 많은 스토어의 인앱 결제를 연동할 수 있도록 돕습니다.
[주의]
ONE Store는 v17, v19, v21을 지원합니다. ONE Store는 버전별로 v17, v19, v21, external 중 하나만 사용이 가능하며, 동시에 사용이 불가능합니다.
String STORE_CODE = "GG"; // Google
GamebaseConfiguration configuration = GamebaseConfiguration.newBuilder(APP_ID, APP_VERSION, STORE_CODE)
.build();
Gamebase.initialize(activity, configuration, callback);
아이템 구매는 크게 결제 Flow 와 Consume Flow, 재처리 Flow 로 나누어 볼 수 있습니다. 결제 Flow는 다음과 같은 순서로 구현하시기 바랍니다.
미소비 결제 내역 목록에 값이 있으면 다음과 같은 순서로 Consume Flow 를 진행하시기 바랍니다.
[주의]
아이템이 중복 지급되는 일이 발생하지 않도록, 게임 서버에서 반드시 중복 지급 여부를 체크하시기 바랍니다.
구매하고자 하는 아이템의 gamebaseProductId 를 이용해 다음의 API를 호출해 구매를 요청합니다.
gamebaseProductId 는 일반적으로는 스토어에 등록한 아이템의 id와 동일하지만, Gamebase 콘솔에서 변경할 수도 있습니다.
게임 유저가 구매를 취소하는 경우 GamebaseError.PURCHASE_USER_CANCELED 오류가 반환됩니다. 취소 처리를 해 주시기 바랍니다.
API
+ (void)Gamebase.Purchase.requestPurchase(@NonNull final Activity activity,
@NonNull final String gamebaseProductId,
@NonNull final GamebaseDataCallback<PurchasableReceipt> callback);
Example
Gamebase.Purchase.requestPurchase(activity, gamebaseProductId, new GamebaseDataCallback<PurchasableReceipt>() {
@Override
public void onCallback(PurchasableReceipt receipt, GamebaseException exception) {
if (Gamebase.isSuccess(exception)) {
// Succeeded.
} else if(exception.getCode() == GamebaseError.PURCHASE_USER_CANCELED) {
// User canceled.
} else {
// To Purchase Item Failed cause of the error
}
}
});
VO
class PurchasableReceipt {
// 구매한 아이템의 상품 ID입니다.
@Nullable
String gamebaseProductId;
// Gamebase.Purchase.requestPurchase API 호출 시 payload로 전달했던 값입니다.
// 스토어 서버 상태에 따라 정보가 유실되는 경우가 있으므로 사용을 권장하지 않습니다.
@Nullable
String payload;
// 구매한 상품의 가격입니다.
float price;
// 통화 코드입니다.
@NonNull
String currency;
// 결제 식별자입니다.
// purchaseToken 과 함께 'Consume' 서버 API 를 호출하는데 사용하는 중요한 정보입니다.
//
// Consume API : https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchase-iap
// 주의 : Consume API 는 게임 서버에서 호출하세요!
@NonNull
String paymentSeq;
// 결제 식별자입니다.
// paymentSeq 와 함께 'Consume' 서버 API 를 호출하는데 사용하는 중요한 정보입니다.
// Consume API 에서는 'accessToken' 라는 이름의 파라메터로 전달해야 합니다.
//
// Consume API : https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchase-iap
// 주의 : Consume API 는 게임 서버에서 호출하세요!
@Nullable
String purchaseToken;
// Google, Apple 과 같이 스토어 콘솔에 등록된 상품 ID입니다.
@NonNull
String marketItemId;
// 상품 타입으로, 다음 값들이 올 수 있습니다.
// * UNKNOWN : 인식 불가능한 타입. Gamebase SDK 를 업데이트 하거나 Gamebase 고객 센터로 문의하세요.
// * CONSUMABLE : 소비성 상품.
// * AUTO_RENEWABLE : 구독형 상품.
// * CONSUMABLE_AUTO_RENEWABLE : 구독형 상품을 구매한 유저에게 정기적으로 소비가 가능한 상품을 지급하고자 하는 경우 사용되는 '소비가 가능한 구독 상품'.
@NonNull
String productType;
// 상품을 구매했던 User ID.
// 상품을 구매하지 않은 User ID 로 로그인 한다면 구매한 아이템을 획득할 수 없습니다.
@NonNull
String userId;
// 스토어의 결제 식별자입니다.
@Nullable
String paymentId;
// 상품을 구매했던 시각입니다.(epoch time)
long purchaseTime;
// 구독이 종료되는 시각입니다.(epoch time)
long expiryTime;
// Google 결제시 사용되는 값으로, 다음 값들이 올 수 있습니다.
// 하지만 Google 서버에서 장애가 발생하여 Gamebase 결제 서버에서 일시적으로 검증 로직을 끄는 경우에는
// null 로만 반환되므로 항상 유효한 값을 보장하지는 않음에 주의하시기 바랍니다.
// * null : 일반 결제
// * Test : 테스트 결제
// * Promo : Promotion 결제
@Nullable
String purchaseType;
// 구독 상품은 갱신될 때마다 paymentId가 변경됩니다.
// 이 필드는 맨 처음 구독 상품을 결제 했을때의 paymentId 를 알려줍니다.
// 스토어에 따라, 결제 서버 상태에 따라 값이 존재하지 않을 수 있으므로
// 항상 유효한 값을 보장하지는 않습니다.
@Nullable
String originalPaymentId;
// itemSeq 로 상품을 구매하는 Legacy API용 식별자입니다.
long itemSeq;
// 상품을 구매했던 Store Code.
@NonNull
public String storeCode;
}
Response Example
{
"gamebaseProductId": "my_product_001",
"price": 1000.0,
"currency": "KRW",
"paymentSeq": "2021032510000001",
"purchaseToken": "5U_NVCLKSDFKLJJ...",
"marketItemId": "my_product_001",
"productType": "CONSUMABLE",
"userId": "AS@123456ABCDEFGHIJ",
"paymentId": "GPA.1111-2222-3333-44444",
"purchaseTime": 1616649225531,
"expiryTime": 0,
"itemSeq": 1000001
}
{
"gamebaseProductId": "my_subcription_product_001",
"price": 1000.0,
"currency": "KRW",
"paymentSeq": "2021032510000001",
"purchaseToken": "5U_NVCLKKLJLSDG...",
"marketItemId": "my_subcription_product_001",
"productType": "CONSUMABLE_AUTO_RENEWABLE",
"userId": "AS@123456ABCDEFGHIJ",
"paymentId": "GPA.1111-2222-3333-56789",
"purchaseTime": 1617069916128,
"expiryTime": 1617070323784,
"purchaseType": "Test",
"originalPaymentId": "GPA.1111-2222-3333-56789",
"itemSeq": 1000002
}
아이템 목록을 조회하려면 다음 API를 호출합니다. 콜백으로 반환되는 배열(array) 안에는 각 아이템들에 대한 정보가 담겨 있습니다.
API
+ (void)Gamebase.Purchase.requestItemListPurchasable(Activity activity, GamebaseDataCallback<List<PurchasableItem>> callback);
Example
Gamebase.Purchase.requestItemListPurchasable(activity, new GamebaseDataCallback<List<PurchasableItem>>() {
@Override
public void onCallback(List<PurchasableItem> data, GamebaseException exception) {
if (Gamebase.isSuccess(exception)) {
// Succeeded.
} else {
// Failed.
Log.e(TAG, "Request item list failed- "
+ "errorCode: " + exception.getCode()
+ "errorMessage: " + exception.getMessage());
}
}
});
VO
class PurchasableItem {
// Gamebase 콘솔에 등록된 상품 ID입니다.
// Gamebase.Purchase.requestPurchase API로 상품을 구매할 때 사용됩니다.
@Nullable
String gamebaseProductId;
// 상품의 가격입니다.
float price;
// 통화 코드입니다.
@Nullable
String currency;
// Gamebase 콘솔에 등록된 상품 이름입니다.
@Nullable
String itemName;
// Google, Apple 과 같이 스토어 콘솔에 등록된 상품 ID입니다.
@NonNull
String marketItemId;
// 상품 타입으로, 다음 값들이 올 수 있습니다.
// * UNKNOWN : 인식 불가능한 타입. Gamebase SDK 를 업데이트 하거나 Gamebase 고객 센터로 문의하세요.
// * CONSUMABLE : 소비성 상품.
// * AUTORENEWABLE : 구독형 상품.
// * CONSUMABLE_AUTO_RENEWABLE : 구독형 상품을 구매한 유저에게 정기적으로 소비가 가능한 상품을 지급하고자 하는 경우 사용되는 '소비가 가능한 구독 상품'.
@NonNull
String productType;
// 통화 기호가 포함된 현지화 된 가격 정보입니다.
@Nullable
String localizedPrice;
// 스토어 콘솔에 등록된 현지화된 상품 이름입니다.
@Nullable
String localizedTitle;
// 스토어 콘솔에 등록된 현지화된 상품 설명입니다.
@Nullable
String localizedDescription;
// Gamebase 콘솔에서 해당 상품의 '사용 여부'를 나타냅니다.
boolean isActive;
// itemSeq 로 상품을 구매하는 Legacy API용 식별자입니다.
long itemSeq;
}
Response Example
{
"gamebaseProductId": "my_product_001",
"price": 1000.0,
"currency": "KRW",
"itemName": "Consumable product for test",
"marketItemId": "my_product_001",
"productType": "CONSUMABLE",
"localizedPrice": "₩1,000",
"localizedTitle": "TEST PRODUCT 001",
"localizedDescription": "Product for test 001",
"isActive": true,
"itemSeq": 1000001
}
PurchasableConfiguration
API | Mandatory(M) / Optional(O) | Description |
---|---|---|
newBuilder() | M | Configuration 객체 생성을 위한 Builder를 생성합니다. |
build() | M | 설정을 마친 Builder를 Configuration 객체로 변환합니다. |
setAllStores(boolean allStores) | O | 동일한 UserID로 다른 스토어에서 구매한 미소비 내역도 반환합니다. 기본값은 false입니다. |
API
+ (void)Gamebase.Purchase.requestItemListOfNotConsumed(@NonNull final Activity activity,
@NonNull final PurchasableConfiguration configuration,
@NonNull final GamebaseDataCallback<List<PurchasableReceipt>> callback);
Example
final PurchasableConfiguration configuration = PurchasableConfiguration.newBuilder().build();
Gamebase.Purchase.requestItemListOfNotConsumed(activity, configuration, new GamebaseDataCallback<List<PurchasableReceipt>>() {
@Override
public void onCallback(List<PurchasableReceipt> data, GamebaseException exception) {
if (Gamebase.isSuccess(exception)) {
// Succeeded.
} else {
// Failed.
Log.e(TAG, "Request item list failed- "
+ "errorCode: " + exception.getCode()
+ "errorMessage: " + exception.getMessage());
}
}
});
현재 사용자 ID 기준으로 활성화된 구독 목록을 조회합니다. 결제가 완료된 구독 상품(자동 갱신형 구독, 자동 갱신형 소비성 구독 상품)은 만료되기 전까지 계속 조회할 수 있습니다. 구독 수명 주기 처리는 다음 문서를 참조하시기 바랍니다. NHN Cloud > SDK 사용 가이드 > IAP > Android > Google Play Store 구독(정기 결제) 기능 > 구독 수명 주기 처리
[주의]
현재 구독 상품은 Android의 경우 Google Play 스토어만 지원합니다.
PurchasableConfiguration
API | Mandatory(M) / Optional(O) | Description |
---|---|---|
newBuilder() | M | Configuration 객체 생성을 위한 Builder를 생성합니다. |
build() | M | 설정을 마친 Builder를 Configuration 객체로 변환합니다. |
setAllStores(boolean allStores) | O | 동일한 UserID로 다른 스토어에서 구매한 구독도 반환합니다. 기본값은 false입니다. |
API
+ (void)Gamebase.Purchase.requestActivatedPurchases(@NonNull final Activity activity,
@NonNull final PurchasableConfiguration configuration,
@NonNull final GamebaseDataCallback<List<PurchasableReceipt>> callback);
Example
final PurchasableConfiguration configuration = PurchasableConfiguration.newBuilder()
.setAllStores(true)
.build();
Gamebase.Purchase.requestActivatedPurchases(activity, configuration, new GamebaseDataCallback<List<PurchasableReceipt>>() {
@Override
public void onCallback(List<PurchasableReceipt> data, GamebaseException exception) {
if (Gamebase.isSuccess(exception)) {
// Succeeded.
} else {
// Failed.
Log.e(TAG, "Request subscription list failed- "
+ "errorCode: " + exception.getCode()
+ "errorMessage: " + exception.getMessage());
}
}
});
현재 사용자 ID 기준으로 구입한 구독 상품의 상태를 조회할 수 있습니다. 결제가 완료된 구독 상품(자동 갱신형 구독, 자동 갱신형 소비성 구독 상품)은 만료되기 전까지 계속 조회할 수 있습니다. PurchasableConfiguration.setIncludeExpiredSubscriptions(true) API로 만료된 구독 상품의 상태도 조회할 수 있습니다. 구독 상태 코드는 다음 문서를 참조하시기 바랍니다. NHN Cloud > SDK 사용 가이드 > IAP > Android > NHN Cloud IAP Class Reference > IapSubscriptionStatus.StatusCode
[주의]
- 구독 상태 코드는 아래 가이드를 따라 구독 이벤트 설정을 해야 정상적으로 반환됩니다.
- Game > Gamebase > 스토어 콘솔 가이드 > Google 콘솔 가이드 > Google 시스템 내 실시간 구독 정보 이벤트 전파 설정
- 이벤트 설정을 하지 않은 상태에서 구매한 구독 상품의 상태 코드는 항상 0(PURCHASED)이 반환됩니다.
- 현재 구독 상품은 Google Play 스토어만 지원합니다.
PurchasableConfiguration
API | Mandatory(M) / Optional(O) | Description |
---|---|---|
newBuilder() | M | Configuration 객체 생성을 위한 Builder를 생성합니다. |
build() | M | 설정을 마친 Builder를 Configuration 객체로 변환합니다. |
setIncludeExpiredSubscriptions(boolean include) | O | 만료된 구독 상품을 포함합니다. 기본값은 false입니다. |
API
+ (void)Gamebase.Purchase.requestSubscriptionsStatus(@NonNull final Activity activity,
@NonNull final PurchasableConfiguration configuration,
@NonNull final GamebaseDataCallback<List<PurchasableSubscriptionStatus>> callback);
Example
final PurchasableConfiguration configuration = PurchasableConfiguration.newBuilder()
.setIncludeExpiredSubscriptions(true)
.build();
Gamebase.Purchase.requestSubscriptionsStatus(activity, configuration, new GamebaseDataCallback<List<PurchasableSubscriptionStatus>>() {
@Override
public void onCallback(List<PurchasableSubscriptionStatus> data, GamebaseException exception) {
if (Gamebase.isSuccess(exception)) {
// Succeeded.
} else {
// Failed.
Log.e(TAG, "Request status of subscription list failed- "
+ "errorCode: " + exception.getCode()
+ "errorMessage: " + exception.getMessage()
+ "errorDetail: " + exception.toString());
}
}
});
VO
class PurchasableSubscriptionStatus {
// 구매한 아이템의 상품 ID입니다.
@Nullable
String gamebaseProductId;
// 구독 상태 코드입니다.
//
// IapSubscriptionStatus.StatusCode : https://docs.nhncloud.com/en/TOAST/en/toast-sdk/iap-android/#iapsubscriptionstatusstatuscode
public int statusCode;
// 구독 상태 코드에 대한 설명입니다.
@NonNull
public String statusDescription;
// 구매한 상품의 가격입니다.
float price;
// 통화 코드입니다.
@NonNull
String currency;
// 결제 식별자입니다.
// purchaseToken과 함께 'Consume' 서버 API를 호출하는 데 사용하는 중요한 정보입니다.
//
// Consume API: https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchase-iap
// 주의: Consume API는 게임 서버에서 호출하세요!
@NonNull
String paymentSeq;
// 결제 식별자입니다.
// paymentSeq와 함께 'Consume' 서버 API를 호출하는 데 사용하는 중요한 정보입니다.
// Consume API에서는 'accessToken' 이라는 이름의 파라미터로 전달해야 합니다.
//
// Consume API: https://docs.toast.com/en/Game/Gamebase/en/api-guide/#purchase-iap
// 주의: Consume API는 게임 서버에서 호출하세요!
@NonNull
String purchaseToken;
// Google, Apple과 같이 스토어 콘솔에 등록된 상품 ID입니다.
@NonNull
String marketItemId;
// 상품 타입으로, 다음 값들이 올 수 있습니다.
// * UNKNOWN: 인식 불가능한 타입. Gamebase SDK를 업데이트하거나 Gamebase 고객 센터로 문의하세요.
// * CONSUMABLE: 소비성 상품.
// * AUTO_RENEWABLE: 구독형 상품.
// * CONSUMABLE_AUTO_RENEWABLE: 구독형 상품을 구매한 유저에게 정기적으로 소비가 가능한 상품을 지급하고자 하는 경우 사용되는 '소비가 가능한 구독 상품'.
@NonNull
String productType;
// 상품을 구매했던 User ID.
// 상품을 구매하지 않은 User ID로 로그인하면 구매한 아이템을 획득할 수 없습니다.
@NonNull
String userId;
// 상품을 구매했던 Store Code.
@NonNull
public String storeCode;
// 스토어의 결제 식별자입니다.
@Nullable
String paymentId;
// 상품을 구매했던 시각입니다.(epoch time)
long purchaseTime;
// 구독이 종료되는 시각입니다.(epoch time)
long expiryTime;
// Google 결제 시 사용되는 값으로, 다음 값들이 올 수 있습니다.
// Google 서버에서 장애가 발생하여 Gamebase 결제 서버에서 일시적으로 검증 로직을 종료하는 경우
// null로만 반환되므로 항상 유효한 값을 보장하지 않을 수 있습니다.
// * null: 일반 결제
// * Test: 테스트 결제
// * Promo: Promotion 결제
@Nullable
String purchaseType;
// 구독 상품은 갱신될 때마다 paymentId가 변경됩니다.
// 이 필드는 구독 상품을 최초 결제했을 때의 paymentId를 알려줍니다.
// 스토어 및 결제 서버 상태에 따라 값이 존재하지 않을 수 있으므로
// 항상 유효한 값을 보장하지는 않습니다.
@Nullable
String originalPaymentId;
// itemSeq로 상품을 구매하는 Legacy API용 식별자입니다.
long itemSeq;
// Gamebase.Purchase.requestPurchase API 호출 시 payload로 전달했던 값입니다.
// 스토어 서버 상태에 따라 정보가 유실되는 경우가 있으므로 사용을 권장하지 않습니다.
@Nullable
String payload;
}
Response Example
{
"gamebaseProductId": "my_subcription_product_002",
"statusCode": 13,
"statusDescription": "EXPIRED",
"userId": "AS@123456ABCDEFGHIJ",
"storeCode": "GG",
"currency": "KRW",
"expiryTime": 1675012345678,
"itemSeq": 1000003,
"marketItemId": "my_subcription_product_002",
"originalPaymentId": "GPA.1111-2222-3333-56789",
"paymentId": "GPA.1111-2222-3333-56789",
"paymentSeq": "2021032510000002",
"price": 1000.0,
"productType": "CONSUMABLE_AUTO_RENEWABLE",
"purchaseTime": 1675001234567,
"purchaseToken": "kfetTfGk4...",
"purchaseType": "Test"
}
프로모션 결제가 완료되었을때 GamebaseEventHandler 를 통해 이벤트를 받아 처리할 수 있습니다. GamebaseEventHandler 로 프로모션 결제 이벤트를 처리하는 방법은 아래 가이드를 확인하세요. Game > Gamebase > Android SDK 사용 가이드 > ETC > Gamebase Event Handler
Error | Error Code | Description |
---|---|---|
PURCHASE_NOT_INITIALIZED | 4001 | Purchase 모듈이 초기화되지 않았습니다. gamebase-adapter-purchase-IAP 모듈을 프로젝트에 추가했는지 확인해주세요. |
PURCHASE_USER_CANCELED | 4002 | 게임 유저가 아이템 구매를 취소하였습니다. |
PURCHASE_NOT_FINISHED_PREVIOUS_PURCHASING | 4003 | 구매 로직이 아직 완료되지 않은 상태에서 API가 호출되었습니다. |
PURCHASE_INACTIVE_PRODUCT_ID | 4005 | 해당 상품이 활성화 상태가 아닙니다. |
PURCHASE_NOT_EXIST_PRODUCT_ID | 4006 | 존재하지 않는 GamebaseProductID 로 결제를 요청하였습니다. |
PURCHASE_LIMIT_EXCEEDED | 4007 | 월 구매 한도를 초과했습니다. |
PURCHASE_NOT_SUPPORTED_MARKET | 4010 | 지원하지 않는 스토어입니다. 선택 가능한 스토어는 GG(Google), ONESTORE, GALAXY, AMAZON, HUAWEI, MYCARD입니다. |
PURCHASE_EXTERNAL_LIBRARY_ERROR | 4201 | NHN Cloud IAP 라이브러리 오류입니다. 상세 오류를 확인하십시오. |
PURCHASE_UNKNOWN_ERROR | 4999 | 정의되지 않은 구매 오류입니다. 전체 로그를 고객 센터에 올려 주시면 가능한 한 빠르게 답변 드리겠습니다. |
PURCHASE_EXTERNAL_LIBRARY_ERROR
Gamebase.Purchase.requestPurchase(activity, gamebaseProductId, new GamebaseDataCallback<PurchasableReceipt>() {
@Override
public void onCallback(PurchasableReceipt data, GamebaseException exception) {
if (Gamebase.isSuccess(exception)) {
Log.d(TAG, "Purchase successful");
...
} else {
Log.e(TAG, "Purchase failed");
// Gamebase Error Info
int errorCode = exception.getCode();
String errorMessage = exception.getMessage();
if (errorCode == GamebaseError.PURCHASE_EXTERNAL_LIBRARY_ERROR) {
// IAP Error Info
int moduleErrorCode = exception.getDetailCode();
String moduleErrorMessage = exception.getDetailMessage();
...
}
}
}
});