GS2-Money2 SDK for Game Engine API 레퍼런스
모델
EzWallet
지갑
지갑 내 통화는 유료로 구매한 통화와 무료로 얻은 통화로 크게 나누어 관리됩니다.
유료로 구매한 통화는 다시 구매 시점의 단가별로 관리되어, 서비스가 종료되었을 때의 환불이나 자금결제법에 해당하는 잔액이 존재하는지를 집계할 수 있습니다.
지갑에는 슬롯이 있으며, 슬롯마다 서로 다른 잔액을 관리할 수 있습니다.
플랫폼 간에 잔액을 공유할 수 없는 경우에는 플랫폼마다 다른 슬롯을 사용함으로써 나누어 관리할 수 있습니다.
이때 무료로 얻은 통화는 모든 플랫폼에서 공통된 값을 사용할 수도 있습니다.
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| slot | int | ✓ | 0 ~ 100000000 | 슬롯 번호 지갑 슬롯을 식별합니다. 플랫폼 간 잔액 공유가 허용되지 않는 경우, 서로 다른 슬롯을 사용하여 플랫폼별로 통화를 나누어 관리할 수 있습니다(예: iOS용과 Android용). | ||
| summary | EzWalletSummary | ✓ | 지갑 상태 지갑의 현재 잔액 요약으로, 유료 통화, 무료 통화, 합계 금액으로 나누어 표시됩니다. 입금 트랜잭션으로부터 산출됩니다. | |||
| sharedFreeCurrency | bool | ✓ | 무료 통화 공유 여부 이 지갑의 무료 통화가 모든 슬롯 간에 공유되는지 여부를 나타냅니다. 지갑 생성 시 네임스페이스 설정으로부터 상속됩니다. | |||
| updatedAt | long | ※ | 현재 시각 | 최종 갱신일시 UNIX 시간·밀리초 ※ 서버가 자동으로 설정 |
EzSubscribeTransaction
구독 구매 정보
스토어 플랫폼으로부터의 구독 구매 레코드를 나타냅니다. 유효, 트라이얼, 초회 할인, 유예 기간, 해지, 기한 만료, 취소 등 라이프사이클을 통한 상세한 구독 상태를 추적합니다. 각 트랜잭션은 특정 스토어 플랫폼과 사용자에 연결됩니다.
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| contentName | string | ✓ | ~ 128자 | 스토어 구독 콘텐츠 모델 이름 | ||||||||||||||||||||||
| store | 문자열 열거형 enum { “AppleAppStore”, “GooglePlay”, “fake” } | ✓ | 스토어 구매가 이루어진 스토어 플랫폼입니다. 영수증 검증에 사용되는 검증 방법을 결정합니다.
| |||||||||||||||||||||||
| transactionId | string | ✓ | ~ 1024자 | 트랜잭션 ID 스토어 플랫폼에 의해 할당된 고유한 트랜잭션 식별자입니다. 동일 구매의 중복 처리를 방지하는 데 사용됩니다. | ||||||||||||||||||||||
| statusDetail | 문자열 열거형 enum { “active@active”, “active@converted_from_trial”, “active@in_trial”, “active@in_intro_offer”, “grace@canceled”, “grace@grace_period”, “grace@on_hold”, “inactive@expired”, “inactive@revoked” } | ✓ | 상태 상세한 구독 상태입니다. 간략 카테고리(active/grace/inactive) 뒤에 구체적인 상태가 이어집니다. active 상태는 구독을 사용할 수 있음을, grace 상태는 결제에 문제가 있지만 일시적으로 이용 가능함을, inactive 상태는 구독이 더 이상 유효하지 않음을 나타냅니다.
| |||||||||||||||||||||||
| expiresAt | long | ✓ | 유효기간 이 구독 트랜잭션이 만료되는 일시입니다. 스토어 플랫폼에 의해 구독이 갱신되면 함께 업데이트됩니다. |
EzSubscriptionStatus
구독 계약 상태
특정 구독 콘텐츠에 대한 사용자의 구독 계약 상태를 추적합니다. 상세한 구독 트랜잭션 상태로부터 도출된 간이 유효/무효 상태와 유효기간, 관련 구독 트랜잭션 목록을 제공합니다.
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| contentName | string | ✓ | ~ 128자 | 스토어 구독 콘텐츠 모델 이름 | ||||||||
| userId | string | ~ 128자 | 사용자ID | |||||||||
| status | 문자열 열거형 enum { “active”, “inactive” } | ✓ | 상태 간이 구독 상태입니다. “active"는 유효, 트라이얼, 초회 할인, 유예 기간 상태를 포함합니다. “inactive"는 기한 만료와 취소 상태를 포함합니다.
| |||||||||
| expiresAt | long | ✓ | 유효기간 구독이 만료되는 일시입니다. 구독이 갱신되거나 상태가 변경될 때 업데이트됩니다. | |||||||||
| detail | List<EzSubscribeTransaction> | [] | 0 ~ 100 items | 계약 상태 상세 정보 이 구독과 관련된 구독 트랜잭션 목록입니다. 각 트랜잭션은 스토어 플랫폼의 구매 레코드로, 상세한 상태 정보(유효, 트라이얼, 유예 기간, 기한 만료, 취소 등)를 가집니다. |
EzStoreContentModel
스토어 콘텐츠 모델
다양한 스토어 플랫폼의 콘텐츠를 저장하는 모델입니다.
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| name | string | ✓ | ~ 128자 | 스토어 콘텐츠 모델 이름 | ||
| metadata | string | ~ 1024자 | 메타데이터 메타데이터에는 임의의 값을 설정할 수 있습니다. 이 값들은 GS2의 동작에는 영향을 주지 않으므로, 게임 내에서 사용하는 정보를 저장하는 용도로 사용할 수 있습니다. | |||
| appleAppStore | EzAppleAppStoreContent | Apple App Store의 콘텐츠 이 스토어 콘텐츠의 Apple App Store 상품 정보(프로덕트 ID)입니다. 영수증 검증 시 구매한 상품과의 대조에 사용됩니다. | ||||
| googlePlay | EzGooglePlayContent | Google Play의 콘텐츠 이 스토어 콘텐츠의 Google Play 상품 정보(프로덕트 ID)입니다. 영수증 검증 시 구매한 상품과의 대조에 사용됩니다. |
EzWalletSummary
지갑 상태
지갑의 통화 잔액을 요약해서 보여주며, 유상 금액과 무상 금액을 구분합니다. 지갑 내 모든 입금 트랜잭션을 가격을 기준으로 집계하여 산출됩니다(가격 > 0은 유상, 가격 = 0은 무상).
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| paid | int | 0 | 0 ~ 2147483646 | 유상 통화 실제 돈으로 구매된 통화의 합계량입니다(가격 > 0인 입금 트랜잭션). | ||
| free | int | 0 | 0 ~ 2147483646 | 무상 통화 무상으로 획득한 통화의 합계량입니다(가격 = 0인 입금 트랜잭션). 로그인 보너스나 이벤트 보상 등이 포함됩니다. | ||
| total | int | 0 | 0 ~ 2147483646 | 총수 통화 잔액의 합계(유상 + 무상)입니다. 지갑에서 이용 가능한 전체 수량을 나타냅니다. |
EzDepositTransaction
입금 트랜잭션
지갑 내 단일 입금 레코드를 나타냅니다. 유상 입금(가격 > 0)은 정확한 환불 계산과 자금결제법 준수를 위해 단가별로 추적됩니다. 무상 입금(가격 = 0)은 별도로 추적됩니다. 출금 시에는 네임스페이스의 재화 소비 우선순위에 기반하여 입금 트랜잭션이 소비됩니다.
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| price | double | ✓ | 0.0 ~ 100000000.0 | 구매 가격 이 입금에 대해 현지 통화로 지급된 실제 금액입니다. 0은 무료 통화를 나타냅니다. 환불 목적의 단가 계산에 사용됩니다. | ||
| currency | string | {price} > 0 | ✓※ | ~ 8자 | 통화 코드 실제 결제의 ISO 통화 코드(예: “JPY”, “USD”)입니다. 유료 입금(가격 > 0)의 경우에만 적용됩니다. ※ price이(가) 0 보다 크면 필수 | |
| count | int | ✓ | 0 ~ 2147483646 | 과금 통화 수량 이 입금에서의 가상 통화 단위 수입니다. 지갑에서 출금되면 감소합니다. |
EzAppleAppStoreContent
Apple App Store의 콘텐츠
인앱 결제 상품에 대응하는 Apple App Store의 프로덕트 ID를 포함합니다. 영수증 검증 시 대조에 사용됩니다.
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| productId | string | ~ 1024자 | 프로덕트 ID 이 인앱 결제 아이템에 대해 App Store Connect에 등록된 Apple App Store의 프로덕트 식별자입니다. |
EzGooglePlayContent
Google Play의 콘텐츠
인앱 결제 상품에 대응하는 Google Play의 프로덕트 ID를 포함합니다. 영수증 검증 시 대조에 사용됩니다.
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| productId | string | ~ 1024자 | 프로덕트 ID 이 인앱 결제 아이템에 대해 Google Play Console에 등록되어 있는 Google Play의 프로덕트 식별자입니다. |
EzAppleAppStoreSubscriptionContent
Apple App Store의 기간 결제 콘텐츠
구독 기반 상품의 Apple App Store 구독 그룹 식별자를 포함합니다. 자동 갱신 구독의 관리와 검증에 사용됩니다.
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| subscriptionGroupIdentifier | string | ~ 64자 | 구독 그룹 ID App Store Connect에 등록된 구독 그룹 식별자입니다. 동일 그룹 내의 구독은 상호 배타적이며, 사용자는 동시에 하나만 계약할 수 있습니다. |
EzGooglePlaySubscriptionContent
Google Play 구독 콘텐츠
구독 기반 상품의 Google Play 프로덕트 ID를 포함합니다. Google Play에서 자동 갱신 구독을 관리하고 검증하는 데 사용됩니다.
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| productId | string | ~ 1024자 | 프로덕트 ID |
메서드
get
플레이어의 유료 통화 지갑 잔액을 취득한다
지정한 슬롯의 플레이어 지갑을 취득하여 유료 통화와 무료 통화 각각의 현재 잔액을 확인할 수 있습니다.
“유료” 통화는 플레이어가 실제 돈으로 구매한 것(예: 100 젬을 ¥120에 구매)이고, “무료” 통화는 게임 플레이를 통해 획득한 것(예: 이벤트 보상, 로그인 보너스)입니다.
일부 기능에서는 유료 통화만을 요구하는 경우가 있기 때문에(예: 특정 가챠나 특별 오퍼) 별도로 관리됩니다.
플레이어의 통화 잔액을 표시하는 데 사용합니다. 예를 들어 상점 화면이나 헤더 UI에서 “젬: 350(유료: 100, 무료: 250)“과 같이 표시하는 데 유용합니다.
Request
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128자 | 네임스페이스 이름 네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. | ||
| gameSession | GameSession | ✓ | GameSession | |||
| slot | int | ✓ | 0 ~ 100000000 | 슬롯 번호 지갑 슬롯을 식별합니다. 플랫폼 간 잔액 공유가 허용되지 않는 경우, 서로 다른 슬롯을 사용하여 플랫폼별로 통화를 나누어 관리할 수 있습니다(예: iOS용과 Android용). |
Result
| 타입 | 설명 | |
|---|---|---|
| item | EzWallet | 지갑 |
구현 예제
var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Wallet(
slot: 0
);
var item = await domain.ModelAsync(); var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Wallet(
slot: 0
);
var future = domain.ModelFuture();
yield return future;
var item = future.Result; const auto Domain = Gs2->Money2->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->Wallet(
0 // slot
);
const auto Future = Domain->Model();
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
return false;
}var domain = ez.money2.namespace_(
"namespace-0001"
).me(game_session).wallet(
0
)
var async_result = await domain.model()
if async_result.error != null:
push_error(str(async_result.error))
return
var result = async_result.result값 변경 이벤트 핸들링
var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Wallet(
slot: 0
);
// 이벤트 핸들링 시작
var callbackId = domain.Subscribe(
value => {
// 값이 변화했을 때 호출됨
// value에는 변경 후의 값이 전달됨
}
);
// 이벤트 핸들링 정지
domain.Unsubscribe(callbackId); var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Wallet(
slot: 0
);
// 이벤트 핸들링 시작
var callbackId = domain.Subscribe(
value => {
// 값이 변화했을 때 호출됨
// value에는 변경 후의 값이 전달됨
}
);
// 이벤트 핸들링 정지
domain.Unsubscribe(callbackId); const auto Domain = Gs2->Money2->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->Wallet(
0 // slot
);
// 이벤트 핸들링 시작
const auto CallbackId = Domain->Subscribe(
[](TSharedPtr<Gs2::Money2::Model::FWallet> value) {
// 값이 변화했을 때 호출됨
// value에는 변경 후의 값이 전달됨
}
);
// 이벤트 핸들링 정지
Domain->Unsubscribe(CallbackId);var domain = ez.money2.namespace_(
"namespace-0001"
).me(game_session).wallet(
0
)
# 이벤트 핸들링 시작
var callback_id = domain.subscribe_model(func(value):
# 값이 변화했을 때 호출됨
# value에는 변경 후의 값이 전달됩니다
pass
)
# 이벤트 핸들링 정지
domain.unsubscribe_model(callback_id)이 이벤트는 SDK가 가진 로컬 캐시의 값이 변경되었을 때 호출됩니다.
로컬 캐시는 SDK가 가진 API의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-Distributor를 통한 스탬프 시트의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-JobQueue의 실행에 의해 변화한 것만이 대상이 됩니다.
따라서 이 방법 이외로 값이 변경된 경우 콜백은 호출되지 않습니다.
list
플레이어의 과금 통화 지갑 목록 조회
플레이어가 현재 보유한 모든 지갑을 조회합니다. 각 지갑은 독립적인 잔액을 가지며, 유상 통화와 무상 통화가 별도로 관리됩니다.
“유상” 통화는 플레이어가 실제 화폐로 구매한 것(예: 100젬을 120엔에 구매)이며, “무상” 통화는 게임 플레이를 통해 획득한 것(예: 이벤트 보상, 로그인 보너스)입니다.
플레이어의 통화 전체 현황을 표시할 때 사용합니다. 예를 들어, 통화 관리 화면에서 모든 지갑 슬롯과 잔액을 목록으로 표시하는 경우에 유용합니다.
Request
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128자 | 네임스페이스 이름 네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. | ||
| gameSession | GameSession | ✓ | GameSession |
Result
| 타입 | 설명 | |
|---|---|---|
| items | List<EzWallet> | 지갑 목록 |
| nextPageToken | string | 목록의 나머지를 취득하기 위한 페이지 토큰 |
구현 예제
var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
);
var items = await domain.WalletsAsync(
).ToListAsync(); var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
);
var it = domain.Wallets(
);
List<EzWallet> items = new List<EzWallet>();
while (it.HasNext())
{
yield return it.Next();
if (it.Error != null)
{
onError.Invoke(it.Error, null);
break;
}
if (it.Current != null)
{
items.Add(it.Current);
}
else
{
break;
}
} const auto Domain = Gs2->Money2->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
);
const auto It = Domain->Wallets(
);
TArray<Gs2::UE5::Money2::Model::FEzWalletPtr> Result;
for (auto Item : *It)
{
if (Item.IsError())
{
return false;
}
Result.Add(Item.Current());
}값 변경 이벤트 핸들링
var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
);
// 이벤트 핸들링 시작
var callbackId = domain.SubscribeWallets(
() => {
// 리스트의 요소가 변화했을 때 호출됨
}
);
// 이벤트 핸들링 정지
domain.UnsubscribeWallets(callbackId); var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
);
// 이벤트 핸들링 시작
var callbackId = domain.SubscribeWallets(
() => {
// 리스트의 요소가 변화했을 때 호출됨
}
);
// 이벤트 핸들링 정지
domain.UnsubscribeWallets(callbackId); const auto Domain = Gs2->Money2->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
);
// 이벤트 핸들링 시작
const auto CallbackId = Domain->SubscribeWallets(
[]() {
// 리스트의 요소가 변화했을 때 호출됨
}
);
// 이벤트 핸들링 정지
Domain->UnsubscribeWallets(CallbackId);이 이벤트는 SDK가 가진 로컬 캐시의 값이 변경되었을 때 호출됩니다.
로컬 캐시는 SDK가 가진 API의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-Distributor를 통한 스탬프 시트의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-JobQueue의 실행에 의해 변화한 것만이 대상이 됩니다.
따라서 이 방법 이외로 값이 변경된 경우 콜백은 호출되지 않습니다.
withdraw
플레이어의 지갑에서 유료 통화 소비하기
플레이어의 지갑에서 지정된 수량의 유료 통화를 차감합니다.
기본값(paidOnly = false)에서는 무료 통화가 먼저 소비되고, 부족한 만큼 유료 통화가 소비됩니다. 이를 통해 플레이어는 구매한 통화를 사용하기 전에 무료 통화를 모두 소진할 수 있습니다.
paidOnly를 true로 설정하면 유료 통화만 소비됩니다. 법률상 유료 통화만 사용해야 하는 기능(예: 일부 지역의 유료 전용 가챠)에 필요합니다.
플레이어가 유료 통화로 구매할 때 사용합니다. 예를 들어 특별한 아이템을 구매하거나 가챠를 뽑기 위해 젬 100개를 소비하는 경우입니다.
참고: GS2-Showcase를 통한 상품 구매의 대가로 통화를 소비하는 경우에는 자동으로 처리되므로, 이 API를 호출할 필요가 없습니다.
Request
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128자 | 네임스페이스 이름 네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. | ||
| gameSession | GameSession | ✓ | GameSession | |||
| slot | int | ✓ | 0 ~ 100000000 | 슬롯 번호 지갑 슬롯을 식별합니다. 플랫폼 간 잔액 공유가 허용되지 않는 경우, 서로 다른 슬롯을 사용하여 플랫폼별로 통화를 나누어 관리할 수 있습니다(예: iOS용과 Android용). | ||
| withdrawCount | int | ✓ | 1 ~ 2147483646 | 소비할 유료 통화 수량 | ||
| paidOnly | bool | false | 유료 통화만을 대상으로 할지 여부 |
Result
| 타입 | 설명 | |
|---|---|---|
| item | EzWallet | 소비 후 지갑 |
| withdrawTransactions | List<EzDepositTransaction> | 소비한 입금 트랜잭션 목록 |
Error
이 API에는 특별한 예외가 정의되어 있습니다.
GS2-SDK for Game Engine에서는 게임 내에서 핸들링이 필요할 것으로 예상되는 에러를, 일반적인 예외에서 파생된 특수화된 예외로 제공하여 다루기 쉽게 하고 있습니다.
일반적인 에러의 종류와 핸들링 방법은 여기 문서를 참고해 주세요.
| 타입 | 베이스 클래스 | 설명 |
|---|---|---|
| ConflictException | ConflictException | 지갑 조작 처리가 충돌했습니다. 재시도가 필요합니다 |
| InsufficientException | BadRequestException | 지갑의 잔액이 부족합니다 |
구현 예제
try {
var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Wallet(
slot: 0
);
var result = await domain.WithdrawAsync(
withdrawCount: 50,
paidOnly: null
);
var item = await result.ModelAsync();
var withdrawTransactions = result.WithdrawTransactions;
} catch(Gs2.Gs2Money2.Exception.ConflictException e) {
// The wallet operation process conflicted. Retry required.
} catch(Gs2.Gs2Money2.Exception.InsufficientException e) {
// Wallet balance is insufficient.
} var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Wallet(
slot: 0
);
var future = domain.WithdrawFuture(
withdrawCount: 50,
paidOnly: null
);
yield return future;
if (future.Error != null)
{
if (future.Error is Gs2.Gs2Money2.Exception.ConflictException)
{
// The wallet operation process conflicted. Retry required.
}
if (future.Error is Gs2.Gs2Money2.Exception.InsufficientException)
{
// Wallet balance is insufficient.
}
onError.Invoke(future.Error, null);
yield break;
}
var future2 = future.Result.ModelFuture();
yield return future2;
if (future2.Error != null)
{
onError.Invoke(future2.Error, null);
yield break;
}
var result = future2.Result;
var withdrawTransactions = future.Result.WithdrawTransactions; const auto Domain = Gs2->Money2->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->Wallet(
0 // slot
);
const auto Future = Domain->Withdraw(
50 // withdrawCount
// paidOnly
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
auto e = Future->GetTask().Error();
if (e->IsChildOf(Gs2::Money2::Error::FConflictError::Class))
{
// The wallet operation process conflicted. Retry required.
}
if (e->IsChildOf(Gs2::Money2::Error::FInsufficientError::Class))
{
// Wallet balance is insufficient.
}
return false;
}
// 변경된 값 / 결과 값을 취득
const auto Future2 = Future->GetTask().Result()->Model();
Future2->StartSynchronousTask();
if (Future2->GetTask().IsError())
{
return Future2->GetTask().Error();
}
const auto Result = Future2->GetTask().Result();
const auto WithdrawTransactions = Result->WithdrawTransactions;var domain = ez.money2.namespace_(
"namespace-0001"
).me(game_session).wallet(
0
)
var async_result = await domain.withdraw(
50, # withdraw_count
null # paid_only
)
if async_result.error != null:
if async_result.error is Gs2Money2ConflictException:
# 지갑 조작 처리가 충돌했습니다. 재시도가 필요합니다
pass
if async_result.error is Gs2Money2InsufficientException:
# 지갑의 잔액이 부족합니다
pass
push_error(str(async_result.error))
return
var result = async_result.resultallocateSubscriptionStatus
스토어 영수증을 사용하여 구독 등록
App Store나 Google Play의 영수증을 검증하여 구독 구매를 플레이어에게 연결합니다.
플레이어가 단말기에서 구독(예: “월정액 패스”)을 구매한 후, 스토어 영수증을 이 API에 전달하여 서버 측에서 구독을 활성화합니다.
스토어 구매를 플레이어의 계정에 연결하기 위한 필수 단계입니다. 이 호출이 없으면 서버는 플레이어가 계약했음을 인식할 수 없습니다.
구매 흐름에서 사용합니다. 예를 들어, 인앱 결제 대화상자에서 플레이어가 “월정액 패스"를 구매한 후 영수증을 전송하여 VIP 혜택을 활성화하는 경우입니다.
Request
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128자 | 네임스페이스 이름 네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. | ||
| gameSession | GameSession | GameSession | ||||
| receipt | string | ✓ | ~ 1024자 | 영수증 |
Result
| 타입 | 설명 | |
|---|---|---|
| item | EzSubscriptionStatus | 구독 계약 상태 |
Error
이 API에는 특별한 예외가 정의되어 있습니다.
GS2-SDK for Game Engine에서는 게임 내에서 핸들링이 필요할 것으로 예상되는 에러를, 일반적인 예외에서 파생된 특수화된 예외로 제공하여 다루기 쉽게 하고 있습니다.
일반적인 에러의 종류와 핸들링 방법은 여기 문서를 참고해 주세요.
| 타입 | 베이스 클래스 | 설명 |
|---|---|---|
| AlreadyUsedException | BadRequestException | 이미 해당 구독 계약은 다른 사용자가 사용하고 있습니다 |
구현 예제
try {
var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
);
var result = await domain.AllocateSubscriptionStatusAsync(
receipt: "{\"Store\": \"AppleAppStore\", \"TransactionID\": \"transaction-0001\", \"Payload\": \"payload\"}"
);
var item = await result.ModelAsync();
} catch(Gs2.Gs2Money2.Exception.AlreadyUsedException e) {
// The subscription contract for that period has already been used by another user.
} var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
);
var future = domain.AllocateSubscriptionStatusFuture(
receipt: "{\"Store\": \"AppleAppStore\", \"TransactionID\": \"transaction-0001\", \"Payload\": \"payload\"}"
);
yield return future;
if (future.Error != null)
{
if (future.Error is Gs2.Gs2Money2.Exception.AlreadyUsedException)
{
// The subscription contract for that period has already been used by another user.
}
onError.Invoke(future.Error, null);
yield break;
}
var future2 = future.Result.ModelFuture();
yield return future2;
if (future2.Error != null)
{
onError.Invoke(future2.Error, null);
yield break;
}
var result = future2.Result; const auto Domain = Gs2->Money2->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
);
const auto Future = Domain->AllocateSubscriptionStatus(
"{\"Store\": \"AppleAppStore\", \"TransactionID\": \"transaction-0001\", \"Payload\": \"payload\"}" // receipt
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
auto e = Future->GetTask().Error();
if (e->IsChildOf(Gs2::Money2::Error::FAlreadyUsedError::Class))
{
// The subscription contract for that period has already been used by another user.
}
return false;
}
// 변경된 값 / 결과 값을 취득
const auto Future2 = Future->GetTask().Result()->Model();
Future2->StartSynchronousTask();
if (Future2->GetTask().IsError())
{
return Future2->GetTask().Error();
}
const auto Result = Future2->GetTask().Result();var domain = ez.money2.namespace_(
"namespace-0001"
).me(game_session)
var async_result = await domain.allocate_subscription_status(
"{\"Store\": \"AppleAppStore\", \"TransactionID\": \"transaction-0001\", \"Payload\": \"payload\"}" # receipt
)
if async_result.error != null:
if async_result.error is Gs2Money2AlreadyUsedException:
# 이미 해당 구독 계약은 다른 사용자가 사용하고 있습니다
pass
push_error(str(async_result.error))
return
var result = async_result.resultgetSubscriptionStatus
특정 구독 계약 상태 조회
콘텐츠 이름을 지정하여 특정 구독의 계약 상태를 조회합니다.
상태에는 플레이어가 현재 계약 중인지 여부와 유효기간 등의 관련 정보가 포함됩니다.
플레이어가 특정 구독을 보유하고 있는지 확인할 때 사용합니다. 예를 들어, 데일리 보너스 보상을 지급하기 전에 “월정액 패스"가 유효한지 확인하거나, 플레이어 프로필에 “VIP 멤버십: 유효"라고 표시하는 경우에 유용합니다.
Request
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128자 | 네임스페이스 이름 네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. | ||
| gameSession | GameSession | GameSession | ||||
| contentName | string | ✓ | ~ 128자 | 스토어 구독 콘텐츠 모델 이름 |
Result
| 타입 | 설명 | |
|---|---|---|
| item | EzSubscriptionStatus | 구독 계약 상태 |
구현 예제
var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).SubscriptionStatus(
contentName: "content-0001"
);
var item = await domain.ModelAsync(); var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).SubscriptionStatus(
contentName: "content-0001"
);
var future = domain.ModelFuture();
yield return future;
var item = future.Result; const auto Domain = Gs2->Money2->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->SubscriptionStatus(
"content-0001" // contentName
);
const auto Future = Domain->Model();
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
return false;
}var domain = ez.money2.namespace_(
"namespace-0001"
).me(game_session).subscription_status(
"content-0001"
)
var async_result = await domain.model()
if async_result.error != null:
push_error(str(async_result.error))
return
var result = async_result.result값 변경 이벤트 핸들링
var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).SubscriptionStatus(
contentName: "content-0001"
);
// 이벤트 핸들링 시작
var callbackId = domain.Subscribe(
value => {
// 값이 변화했을 때 호출됨
// value에는 변경 후의 값이 전달됨
}
);
// 이벤트 핸들링 정지
domain.Unsubscribe(callbackId); var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).SubscriptionStatus(
contentName: "content-0001"
);
// 이벤트 핸들링 시작
var callbackId = domain.Subscribe(
value => {
// 값이 변화했을 때 호출됨
// value에는 변경 후의 값이 전달됨
}
);
// 이벤트 핸들링 정지
domain.Unsubscribe(callbackId); const auto Domain = Gs2->Money2->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->SubscriptionStatus(
"content-0001" // contentName
);
// 이벤트 핸들링 시작
const auto CallbackId = Domain->Subscribe(
[](TSharedPtr<Gs2::Money2::Model::FSubscriptionStatus> value) {
// 값이 변화했을 때 호출됨
// value에는 변경 후의 값이 전달됨
}
);
// 이벤트 핸들링 정지
Domain->Unsubscribe(CallbackId);var domain = ez.money2.namespace_(
"namespace-0001"
).me(game_session).subscription_status(
"content-0001"
)
# 이벤트 핸들링 시작
var callback_id = domain.subscribe_model(func(value):
# 값이 변화했을 때 호출됨
# value에는 변경 후의 값이 전달됩니다
pass
)
# 이벤트 핸들링 정지
domain.unsubscribe_model(callback_id)이 이벤트는 SDK가 가진 로컬 캐시의 값이 변경되었을 때 호출됩니다.
로컬 캐시는 SDK가 가진 API의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-Distributor를 통한 스탬프 시트의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-JobQueue의 실행에 의해 변화한 것만이 대상이 됩니다.
따라서 이 방법 이외로 값이 변경된 경우 콜백은 호출되지 않습니다.
listSubscriptionStatuses
플레이어의 구독 계약 상태 목록 조회
플레이어가 계약했을 가능성이 있는 모든 구독 콘텐츠의 계약 상태를 조회합니다.
구독이란 App Store나 Google Play에서 구매하는 “월정액 패스"나 “VIP 멤버십"과 같은 정기 결제를 의미합니다.
각 상태는 플레이어가 해당 콘텐츠를 현재 계약하고 있는지 여부를 나타냅니다.
구독 개요를 표시할 때 사용합니다. 예를 들어, 멤버십 화면에서 “월정액 패스: 유효(3월 15일까지)”, “VIP 멤버십: 미계약"과 같이 표시하는 경우에 유용합니다.
Request
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128자 | 네임스페이스 이름 네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. | ||
| gameSession | GameSession | GameSession |
Result
| 타입 | 설명 | |
|---|---|---|
| items | List<EzSubscriptionStatus> | 구독 계약 상태 목록 |
구현 예제
var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
);
var items = await domain.SubscriptionStatusesAsync(
).ToListAsync(); var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
);
var it = domain.SubscriptionStatuses(
);
List<EzSubscriptionStatus> items = new List<EzSubscriptionStatus>();
while (it.HasNext())
{
yield return it.Next();
if (it.Error != null)
{
onError.Invoke(it.Error, null);
break;
}
if (it.Current != null)
{
items.Add(it.Current);
}
else
{
break;
}
} const auto Domain = Gs2->Money2->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
);
const auto It = Domain->SubscriptionStatuses(
);
TArray<Gs2::UE5::Money2::Model::FEzSubscriptionStatusPtr> Result;
for (auto Item : *It)
{
if (Item.IsError())
{
return false;
}
Result.Add(Item.Current());
}값 변경 이벤트 핸들링
var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
);
// 이벤트 핸들링 시작
var callbackId = domain.SubscribeSubscriptionStatuses(
() => {
// 리스트의 요소가 변화했을 때 호출됨
}
);
// 이벤트 핸들링 정지
domain.UnsubscribeSubscriptionStatuses(callbackId); var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
);
// 이벤트 핸들링 시작
var callbackId = domain.SubscribeSubscriptionStatuses(
() => {
// 리스트의 요소가 변화했을 때 호출됨
}
);
// 이벤트 핸들링 정지
domain.UnsubscribeSubscriptionStatuses(callbackId); const auto Domain = Gs2->Money2->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
);
// 이벤트 핸들링 시작
const auto CallbackId = Domain->SubscribeSubscriptionStatuses(
[]() {
// 리스트의 요소가 변화했을 때 호출됨
}
);
// 이벤트 핸들링 정지
Domain->UnsubscribeSubscriptionStatuses(CallbackId);이 이벤트는 SDK가 가진 로컬 캐시의 값이 변경되었을 때 호출됩니다.
로컬 캐시는 SDK가 가진 API의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-Distributor를 통한 스탬프 시트의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-JobQueue의 실행에 의해 변화한 것만이 대상이 됩니다.
따라서 이 방법 이외로 값이 변경된 경우 콜백은 호출되지 않습니다.
takeOverSubscriptionStatus
다른 계정에서 이 플레이어에게 구독 이전
현재 다른 플레이어 계정에 연결되어 있는 구독을 이 플레이어의 계정으로 이동합니다.
플레이어가 계정 이전을 수행하는 경우에 사용합니다. 예를 들어, 기기 변경으로 새 계정을 생성한 경우, 스토어 영수증을 사용하여 기존 계정의 “월정액 패스” 구독을 새 계정으로 이전할 수 있습니다.
구독은 기존 계정에서 해제되어 새 계정에 연결되므로, 기존 계정에서는 더 이상 구독 혜택을 받을 수 없게 됩니다.
계정 이전 흐름에서 사용합니다. 예를 들어, 플레이어가 새 계정으로 로그인하여 이전을 확인한 후 호출합니다.
Request
| 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 | |
|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128자 | 네임스페이스 이름 네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. | ||
| gameSession | GameSession | GameSession | ||||
| receipt | string | ✓ | ~ 1024자 | 영수증 |
Result
| 타입 | 설명 | |
|---|---|---|
| item | EzSubscriptionStatus | 구독 계약 상태 |
Error
이 API에는 특별한 예외가 정의되어 있습니다.
GS2-SDK for Game Engine에서는 게임 내에서 핸들링이 필요할 것으로 예상되는 에러를, 일반적인 예외에서 파생된 특수화된 예외로 제공하여 다루기 쉽게 하고 있습니다.
일반적인 에러의 종류와 핸들링 방법은 여기 문서를 참고해 주세요.
| 타입 | 베이스 클래스 | 설명 |
|---|---|---|
| LockPeriodNotElapsedException | BadRequestException | 마지막 사용자 전환 이후 잠금 기간이 경과하지 않았습니다 |
구현 예제
try {
var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
);
var result = await domain.TakeOverSubscriptionStatusAsync(
receipt: "{\"Store\": \"AppleAppStore\", \"TransactionID\": \"transaction-0001\", \"Payload\": \"payload\"}"
);
var item = await result.ModelAsync();
} catch(Gs2.Gs2Money2.Exception.LockPeriodNotElapsedException e) {
// The lock period has not elapsed since the last user change.
} var domain = gs2.Money2.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
);
var future = domain.TakeOverSubscriptionStatusFuture(
receipt: "{\"Store\": \"AppleAppStore\", \"TransactionID\": \"transaction-0001\", \"Payload\": \"payload\"}"
);
yield return future;
if (future.Error != null)
{
if (future.Error is Gs2.Gs2Money2.Exception.LockPeriodNotElapsedException)
{
// The lock period has not elapsed since the last user change.
}
onError.Invoke(future.Error, null);
yield break;
}
var future2 = future.Result.ModelFuture();
yield return future2;
if (future2.Error != null)
{
onError.Invoke(future2.Error, null);
yield break;
}
var result = future2.Result; const auto Domain = Gs2->Money2->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
);
const auto Future = Domain->TakeOverSubscriptionStatus(
"{\"Store\": \"AppleAppStore\", \"TransactionID\": \"transaction-0001\", \"Payload\": \"payload\"}" // receipt
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
auto e = Future->GetTask().Error();
if (e->IsChildOf(Gs2::Money2::Error::FLockPeriodNotElapsedError::Class))
{
// The lock period has not elapsed since the last user change.
}
return false;
}
// 변경된 값 / 결과 값을 취득
const auto Future2 = Future->GetTask().Result()->Model();
Future2->StartSynchronousTask();
if (Future2->GetTask().IsError())
{
return Future2->GetTask().Error();
}
const auto Result = Future2->GetTask().Result();var domain = ez.money2.namespace_(
"namespace-0001"
).me(game_session)
var async_result = await domain.take_over_subscription_status(
"{\"Store\": \"AppleAppStore\", \"TransactionID\": \"transaction-0001\", \"Payload\": \"payload\"}" # receipt
)
if async_result.error != null:
if async_result.error is Gs2Money2LockPeriodNotElapsedException:
# 마지막 사용자 전환 이후 잠금 기간이 경과하지 않았습니다
pass
push_error(str(async_result.error))
return
var result = async_result.result이벤트 핸들러
OnChangeSubscriptionStatus
구독 계약 상태가 변경되었을 때 사용하는 푸시 알림
| 이름 | 타입 | 설명 |
|---|---|---|
| namespaceName | string | 네임스페이스 이름 네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| userId | string | 사용자ID |
| contentName | string | 스토어 구독 콘텐츠 모델 이름 |
구현 예제
gs2.Money2.OnChangeSubscriptionStatus += notification =>
{
var namespaceName = notification.NamespaceName;
var userId = notification.UserId;
var contentName = notification.ContentName;
}; gs2.Money2.OnChangeSubscriptionStatus += notification =>
{
var namespaceName = notification.NamespaceName;
var userId = notification.UserId;
var contentName = notification.ContentName;
}; Gs2->Money2->OnChangeSubscriptionStatus().AddLambda([](const auto Notification)
{
const auto NamespaceName = Notification->NamespaceNameValue;
const auto UserId = Notification->UserIdValue;
const auto ContentName = Notification->ContentNameValue;
}); ez.money2.change_subscription_status.connect(func(notification):
var namespace_name = notification.namespace_name
var user_id = notification.user_id
var content_name = notification.content_name
)