> For the complete documentation index, see [llms.txt](/llms.txt)

# GS2-Money2 SDK for Game Engine API 레퍼런스

게임 엔진용 GS2-Money2 SDK의 모델 사양과 API 레퍼런스



## 모델

### EzWallet

지갑<br>

지갑 내 통화는 유료로 구매한 통화와 무료로 얻은 통화로 크게 나누어 관리됩니다.<br>
유료로 구매한 통화는 다시 구매 시점의 단가별로 관리되어, 서비스가 종료되었을 때의 환불이나 자금결제법에 해당하는 잔액이 존재하는지를 집계할 수 있습니다.<br>

지갑에는 슬롯이 있으며, 슬롯마다 서로 다른 잔액을 관리할 수 있습니다.<br>
플랫폼 간에 잔액을 공유할 수 없는 경우에는 플랫폼마다 다른 슬롯을 사용함으로써 나누어 관리할 수 있습니다.<br>
이때 무료로 얻은 통화는 모든 플랫폼에서 공통된 값을 사용할 수도 있습니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| slot | int |  | ✓ |  | 0 ~ 100000000 | 슬롯 번호<br>지갑 슬롯을 식별합니다. 플랫폼 간 잔액 공유가 허용되지 않는 경우, 서로 다른 슬롯을 사용하여 플랫폼별로 통화를 나누어 관리할 수 있습니다(예: iOS용과 Android용). |
| summary | [EzWalletSummary](#ezwalletsummary) |  | ✓ |  |  | 지갑 상태<br>지갑의 현재 잔액 요약으로, 유료 통화, 무료 통화, 합계 금액으로 나누어 표시됩니다. 입금 트랜잭션으로부터 산출됩니다. |
| sharedFreeCurrency | bool |  | ✓ |  |  | 무료 통화 공유 여부<br>이 지갑의 무료 통화가 모든 슬롯 간에 공유되는지 여부를 나타냅니다. 지갑 생성 시 네임스페이스 설정으로부터 상속됩니다. |
| updatedAt | long |  | ※ | 현재 시각 |  | 최종 갱신일시<br>UNIX 시간・밀리초<br>※ 서버가 자동으로 설정 |

**관련 메서드:**
get - 플레이어의 유료 통화 지갑 잔액을 취득한다
list - 플레이어의 과금 통화 지갑 목록 조회
withdraw - 플레이어의 지갑에서 유료 통화 소비하기


---

### EzSubscribeTransaction

구독 구매 정보<br>

스토어 플랫폼으로부터의 구독 구매 레코드를 나타냅니다. 유효, 트라이얼, 초회 할인, 유예 기간, 해지, 기한 만료, 취소 등 라이프사이클을 통한 상세한 구독 상태를 추적합니다. 각 트랜잭션은 특정 스토어 플랫폼과 사용자에 연결됩니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| contentName | string |  | ✓ |  |  ~ 128자 | 스토어 구독 콘텐츠 모델 이름 |
| store | 문자열 열거형<br>enum {<br>"AppleAppStore",<br>"GooglePlay",<br>"fake"<br>}<br> |  | ✓ |  |  | 스토어<br>구매가 이루어진 스토어 플랫폼입니다. 영수증 검증에 사용되는 검증 방법을 결정합니다.AppleAppStore: Apple App Store / GooglePlay: Google Play / fake: Fake /  |
| transactionId | string |  | ✓ |  |  ~ 1024자 | 트랜잭션 ID<br>스토어 플랫폼에 의해 할당된 고유한 트랜잭션 식별자입니다. 동일 구매의 중복 처리를 방지하는 데 사용됩니다. |
| statusDetail | 문자열 열거형<br>enum {<br>"active@active",<br>"active@converted_from_trial",<br>"active@in_trial",<br>"active@in_intro_offer",<br>"grace@canceled",<br>"grace@grace_period",<br>"grace@on_hold",<br>"inactive@expired",<br>"inactive@revoked"<br>}<br> |  | ✓ |  |  | 상태<br>상세한 구독 상태입니다. 간략 카테고리(active/grace/inactive) 뒤에 구체적인 상태가 이어집니다. active 상태는 구독을 사용할 수 있음을, grace 상태는 결제에 문제가 있지만 일시적으로 이용 가능함을, inactive 상태는 구독이 더 이상 유효하지 않음을 나타냅니다.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: 환불 등으로 강제로 취소됨 /  |
| expiresAt | long |  | ✓ |  |  | 유효기간<br>이 구독 트랜잭션이 만료되는 일시입니다. 스토어 플랫폼에 의해 구독이 갱신되면 함께 업데이트됩니다. |


**관련 모델:**
EzSubscriptionStatus - 구독 계약 상태



---

### EzSubscriptionStatus

구독 계약 상태<br>

특정 구독 콘텐츠에 대한 사용자의 구독 계약 상태를 추적합니다. 상세한 구독 트랜잭션 상태로부터 도출된 간이 유효/무효 상태와 유효기간, 관련 구독 트랜잭션 목록을 제공합니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| contentName | string |  | ✓ |  |  ~ 128자 | 스토어 구독 콘텐츠 모델 이름 |
| userId | string |  |  |  |  ~ 128자 | 사용자ID |
| status | 문자열 열거형<br>enum {<br>"active",<br>"inactive"<br>}<br> |  | ✓ |  |  | 상태<br>간이 구독 상태입니다. "active"는 유효, 트라이얼, 초회 할인, 유예 기간 상태를 포함합니다. "inactive"는 기한 만료와 취소 상태를 포함합니다.active: 유효 / inactive: 비활성 /  |
| expiresAt | long |  | ✓ |  |  | 유효기간<br>구독이 만료되는 일시입니다. 구독이 갱신되거나 상태가 변경될 때 업데이트됩니다. |
| detail | [List&lt;EzSubscribeTransaction&gt;](#ezsubscribetransaction) |  |  | [] | 0 ~ 100 items | 계약 상태 상세 정보<br>이 구독과 관련된 구독 트랜잭션 목록입니다. 각 트랜잭션은 스토어 플랫폼의 구매 레코드로, 상세한 상태 정보(유효, 트라이얼, 유예 기간, 기한 만료, 취소 등)를 가집니다. |

**관련 메서드:**
allocateSubscriptionStatus - 스토어 영수증을 사용하여 구독 등록
getSubscriptionStatus - 특정 구독 계약 상태 조회
listSubscriptionStatuses - 플레이어의 구독 계약 상태 목록 조회
takeOverSubscriptionStatus - 다른 계정에서 이 플레이어에게 구독 이전


---

### EzStoreContentModel

스토어 콘텐츠 모델<br>

다양한 스토어 플랫폼의 콘텐츠를 저장하는 모델입니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| name | string |  | ✓ |  |  ~ 128자 | 스토어 콘텐츠 모델 이름 |
| metadata | string |  |  |  |  ~ 1024자 | 메타데이터<br>메타데이터에는 임의의 값을 설정할 수 있습니다.<br>이 값들은 GS2의 동작에는 영향을 주지 않으므로, 게임 내에서 사용하는 정보를 저장하는 용도로 사용할 수 있습니다. |
| appleAppStore | [EzAppleAppStoreContent](#ezappleappstorecontent) |  |  |  |  | Apple App Store의 콘텐츠<br>이 스토어 콘텐츠의 Apple App Store 상품 정보(프로덕트 ID)입니다. 영수증 검증 시 구매한 상품과의 대조에 사용됩니다. |
| googlePlay | [EzGooglePlayContent](#ezgoogleplaycontent) |  |  |  |  | Google Play의 콘텐츠<br>이 스토어 콘텐츠의 Google Play 상품 정보(프로덕트 ID)입니다. 영수증 검증 시 구매한 상품과의 대조에 사용됩니다. |


---

### EzWalletSummary

지갑 상태<br>

지갑의 통화 잔액을 요약해서 보여주며, 유상 금액과 무상 금액을 구분합니다. 지갑 내 모든 입금 트랜잭션을 가격을 기준으로 집계하여 산출됩니다(가격 > 0은 유상, 가격 = 0은 무상).

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| paid | int |  |  | 0 | 0 ~ 2147483646 | 유상 통화<br>실제 돈으로 구매된 통화의 합계량입니다(가격 > 0인 입금 트랜잭션). |
| free | int |  |  | 0 | 0 ~ 2147483646 | 무상 통화<br>무상으로 획득한 통화의 합계량입니다(가격 = 0인 입금 트랜잭션). 로그인 보너스나 이벤트 보상 등이 포함됩니다. |
| total | int |  |  | 0 | 0 ~ 2147483646 | 총수<br>통화 잔액의 합계(유상 + 무상)입니다. 지갑에서 이용 가능한 전체 수량을 나타냅니다. |


**관련 모델:**
EzWallet - 지갑



---

### EzDepositTransaction

입금 트랜잭션<br>

지갑 내 단일 입금 레코드를 나타냅니다. 유상 입금(가격 > 0)은 정확한 환불 계산과 자금결제법 준수를 위해 단가별로 추적됩니다. 무상 입금(가격 = 0)은 별도로 추적됩니다. 출금 시에는 네임스페이스의 재화 소비 우선순위에 기반하여 입금 트랜잭션이 소비됩니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| price | double |  | ✓ |  | 0.0 ~ 100000000.0 | 구매 가격<br>이 입금에 대해 현지 통화로 지급된 실제 금액입니다. 0은 무료 통화를 나타냅니다. 환불 목적의 단가 계산에 사용됩니다. |
| currency | string | {price} > 0 | ✓※ |  |  ~ 8자 | 통화 코드<br>실제 결제의 ISO 통화 코드(예: "JPY", "USD")입니다. 유료 입금(가격 > 0)의 경우에만 적용됩니다.<br><br>※ price이(가) 0 보다 크면 필수 |
| count | int |  | ✓ |  | 0 ~ 2147483646 | 과금 통화 수량<br>이 입금에서의 가상 통화 단위 수입니다. 지갑에서 출금되면 감소합니다. |

**관련 메서드:**
withdraw - 플레이어의 지갑에서 유료 통화 소비하기


---

### EzAppleAppStoreContent

Apple App Store의 콘텐츠<br>

인앱 결제 상품에 대응하는 Apple App Store의 프로덕트 ID를 포함합니다. 영수증 검증 시 대조에 사용됩니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| productId | string |  |  |  |  ~ 1024자 | 프로덕트 ID<br>이 인앱 결제 아이템에 대해 App Store Connect에 등록된 Apple App Store의 프로덕트 식별자입니다. |


**관련 모델:**
EzStoreContentModel - 스토어 콘텐츠 모델



---

### EzGooglePlayContent

Google Play의 콘텐츠<br>

인앱 결제 상품에 대응하는 Google Play의 프로덕트 ID를 포함합니다. 영수증 검증 시 대조에 사용됩니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| productId | string |  |  |  |  ~ 1024자 | 프로덕트 ID<br>이 인앱 결제 아이템에 대해 Google Play Console에 등록되어 있는 Google Play의 프로덕트 식별자입니다. |


**관련 모델:**
EzStoreContentModel - 스토어 콘텐츠 모델



---

### EzAppleAppStoreSubscriptionContent

Apple App Store의 기간 결제 콘텐츠<br>

구독 기반 상품의 Apple App Store 구독 그룹 식별자를 포함합니다. 자동 갱신 구독의 관리와 검증에 사용됩니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| subscriptionGroupIdentifier | string |  |  |  |  ~ 64자 | 구독 그룹 ID<br>App Store Connect에 등록된 구독 그룹 식별자입니다. 동일 그룹 내의 구독은 상호 배타적이며, 사용자는 동시에 하나만 계약할 수 있습니다. |


---

### EzGooglePlaySubscriptionContent

Google Play 구독 콘텐츠<br>

구독 기반 상품의 Google Play 프로덕트 ID를 포함합니다. Google Play에서 자동 갱신 구독을 관리하고 검증하는 데 사용됩니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| productId | string |  |  |  |  ~ 1024자 | 프로덕트 ID |


---

## 메서드

### get

플레이어의 유료 통화 지갑 잔액을 취득한다<br>

지정한 슬롯의 플레이어 지갑을 취득하여 유료 통화와 무료 통화 각각의 현재 잔액을 확인할 수 있습니다.<br>
"유료" 통화는 플레이어가 실제 돈으로 구매한 것(예: 100 젬을 ¥120에 구매)이고, "무료" 통화는 게임 플레이를 통해 획득한 것(예: 이벤트 보상, 로그인 보너스)입니다.<br>
일부 기능에서는 유료 통화만을 요구하는 경우가 있기 때문에(예: 특정 가챠나 특별 오퍼) 별도로 관리됩니다.<br>
플레이어의 통화 잔액을 표시하는 데 사용합니다. 예를 들어 상점 화면이나 헤더 UI에서 "젬: 350(유료: 100, 무료: 250)"과 같이 표시하는 데 유용합니다.

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| gameSession | GameSession | | ✓|  |  | GameSession |
| slot | int |  | ✓|  | 0 ~ 100000000 | 슬롯 번호<br>지갑 슬롯을 식별합니다. 플랫폼 간 잔액 공유가 허용되지 않는 경우, 서로 다른 슬롯을 사용하여 플랫폼별로 통화를 나누어 관리할 수 있습니다(예: iOS용과 Android용). |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzWallet](#ezwallet) | 지갑|

#### 구현 예제




**Unity (UniTask)**
```csharp
    var domain = gs2.Money2.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Wallet(
        slot: 0
    );
    var item = await domain.ModelAsync();

```

**Unity (Vanilla)**
```cs
    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;

```

**Unreal Engine 5**
```cpp
    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;
    }

```

**Godot**
```gdscript

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

```


##### 값 변경 이벤트 핸들링




**Unity (UniTask)**
```csharp
    var domain = gs2.Money2.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Wallet(
        slot: 0
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    domain.Unsubscribe(callbackId);

```

**Unity (Vanilla)**
```cs
    var domain = gs2.Money2.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Wallet(
        slot: 0
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    domain.Unsubscribe(callbackId);

```

**Unreal Engine 5**
```cpp
    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);

```

**Godot**
```gdscript

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)

```


**⚠️ Warning**

이 이벤트는 SDK가 가진 로컬 캐시의 값이 변경되었을 때 호출됩니다.

로컬 캐시는 SDK가 가진 API의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-Distributor를 통한 스탬프 시트의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-JobQueue의 실행에 의해 변화한 것만이 대상이 됩니다.

따라서 이 방법 이외로 값이 변경된 경우 콜백은 호출되지 않습니다.

---

### list

플레이어의 과금 통화 지갑 목록 조회<br>

플레이어가 현재 보유한 모든 지갑을 조회합니다. 각 지갑은 독립적인 잔액을 가지며, 유상 통화와 무상 통화가 별도로 관리됩니다.<br>
"유상" 통화는 플레이어가 실제 화폐로 구매한 것(예: 100젬을 120엔에 구매)이며, "무상" 통화는 게임 플레이를 통해 획득한 것(예: 이벤트 보상, 로그인 보너스)입니다.<br>
플레이어의 통화 전체 현황을 표시할 때 사용합니다. 예를 들어, 통화 관리 화면에서 모든 지갑 슬롯과 잔액을 목록으로 표시하는 경우에 유용합니다.

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| gameSession | GameSession | | ✓|  |  | GameSession |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| items | [List&lt;EzWallet&gt;](#ezwallet) | 지갑 목록|
| nextPageToken | string | 목록의 나머지를 취득하기 위한 페이지 토큰|

#### 구현 예제




**Unity (UniTask)**
```csharp
    var domain = gs2.Money2.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    );
    var items = await domain.WalletsAsync(
    ).ToListAsync();

```

**Unity (Vanilla)**
```cs
    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;
        }
    }

```

**Unreal Engine 5**
```cpp
    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());
    }

```


##### 값 변경 이벤트 핸들링




**Unity (UniTask)**
```csharp
    var domain = gs2.Money2.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.SubscribeWallets(
        () => {
            // 리스트의 요소가 변화했을 때 호출됨
        }
    );

    // 이벤트 핸들링 정지
    domain.UnsubscribeWallets(callbackId);

```

**Unity (Vanilla)**
```cs
    var domain = gs2.Money2.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.SubscribeWallets(
        () => {
            // 리스트의 요소가 변화했을 때 호출됨
        }
    );

    // 이벤트 핸들링 정지
    domain.UnsubscribeWallets(callbackId);

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->Money2->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    );
    
    // 이벤트 핸들링 시작
    const auto CallbackId = Domain->SubscribeWallets(
        []() {
            // 리스트의 요소가 변화했을 때 호출됨
        }
    );

    // 이벤트 핸들링 정지
    Domain->UnsubscribeWallets(CallbackId);

```


**⚠️ Warning**

이 이벤트는 SDK가 가진 로컬 캐시의 값이 변경되었을 때 호출됩니다.

로컬 캐시는 SDK가 가진 API의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-Distributor를 통한 스탬프 시트의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-JobQueue의 실행에 의해 변화한 것만이 대상이 됩니다.

따라서 이 방법 이외로 값이 변경된 경우 콜백은 호출되지 않습니다.

---

### withdraw

플레이어의 지갑에서 유료 통화 소비하기<br>

플레이어의 지갑에서 지정된 수량의 유료 통화를 차감합니다.<br>
기본값(paidOnly = false)에서는 무료 통화가 먼저 소비되고, 부족한 만큼 유료 통화가 소비됩니다. 이를 통해 플레이어는 구매한 통화를 사용하기 전에 무료 통화를 모두 소진할 수 있습니다.<br>
paidOnly를 true로 설정하면 유료 통화만 소비됩니다. 법률상 유료 통화만 사용해야 하는 기능(예: 일부 지역의 유료 전용 가챠)에 필요합니다.<br>
플레이어가 유료 통화로 구매할 때 사용합니다. 예를 들어 특별한 아이템을 구매하거나 가챠를 뽑기 위해 젬 100개를 소비하는 경우입니다.<br>
참고: GS2-Showcase를 통한 상품 구매의 대가로 통화를 소비하는 경우에는 자동으로 처리되므로, 이 API를 호출할 필요가 없습니다.

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| gameSession | GameSession | | ✓|  |  | GameSession |
| slot | int |  | ✓|  | 0 ~ 100000000 | 슬롯 번호<br>지갑 슬롯을 식별합니다. 플랫폼 간 잔액 공유가 허용되지 않는 경우, 서로 다른 슬롯을 사용하여 플랫폼별로 통화를 나누어 관리할 수 있습니다(예: iOS용과 Android용). |
| withdrawCount | int |  | ✓|  | 1 ~ 2147483646 | 소비할 유료 통화 수량 |
| paidOnly | bool |  | | false |  | 유료 통화만을 대상으로 할지 여부 |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzWallet](#ezwallet) | 소비 후 지갑|
| withdrawTransactions | [List&lt;EzDepositTransaction&gt;](#ezdeposittransaction) | 소비한 입금 트랜잭션 목록|

#### Error

이 API에는 특별한 예외가 정의되어 있습니다.<br>
GS2-SDK for Game Engine에서는 게임 내에서 핸들링이 필요할 것으로 예상되는 에러를, 일반적인 예외에서 파생된 특수화된 예외로 제공하여 다루기 쉽게 하고 있습니다.<br>
일반적인 에러의 종류와 핸들링 방법은 [여기]() 문서를 참고해 주세요.

| 타입 | 베이스 클래스 | 설명 |
| --- | --- | --- |
| ConflictException | ConflictException | 지갑 조작 처리가 충돌했습니다. 재시도가 필요합니다 |
| InsufficientException | BadRequestException | 지갑의 잔액이 부족합니다 |

#### 구현 예제




**Unity (UniTask)**
```csharp

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.
}

```

**Unity (Vanilla)**
```cs
    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;

```

**Unreal Engine 5**
```cpp
    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;

```

**Godot**
```gdscript

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.result

```


---

### allocateSubscriptionStatus

스토어 영수증을 사용하여 구독 등록<br>

App Store나 Google Play의 영수증을 검증하여 구독 구매를 플레이어에게 연결합니다.<br>
플레이어가 단말기에서 구독(예: "월정액 패스")을 구매한 후, 스토어 영수증을 이 API에 전달하여 서버 측에서 구독을 활성화합니다.<br>
스토어 구매를 플레이어의 계정에 연결하기 위한 필수 단계입니다. 이 호출이 없으면 서버는 플레이어가 계약했음을 인식할 수 없습니다.<br>
구매 흐름에서 사용합니다. 예를 들어, 인앱 결제 대화상자에서 플레이어가 "월정액 패스"를 구매한 후 영수증을 전송하여 VIP 혜택을 활성화하는 경우입니다.

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| gameSession | GameSession | | |  |  | GameSession |
| receipt | string |  | ✓|  |  ~ 1024자 | 영수증 |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzSubscriptionStatus](#ezsubscriptionstatus) | 구독 계약 상태|

#### Error

이 API에는 특별한 예외가 정의되어 있습니다.<br>
GS2-SDK for Game Engine에서는 게임 내에서 핸들링이 필요할 것으로 예상되는 에러를, 일반적인 예외에서 파생된 특수화된 예외로 제공하여 다루기 쉽게 하고 있습니다.<br>
일반적인 에러의 종류와 핸들링 방법은 [여기]() 문서를 참고해 주세요.

| 타입 | 베이스 클래스 | 설명 |
| --- | --- | --- |
| AlreadyUsedException | BadRequestException | 이미 해당 구독 계약은 다른 사용자가 사용하고 있습니다 |

#### 구현 예제




**Unity (UniTask)**
```csharp

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.
}

```

**Unity (Vanilla)**
```cs
    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;

```

**Unreal Engine 5**
```cpp
    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();

```

**Godot**
```gdscript

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.result

```


---

### getSubscriptionStatus

특정 구독 계약 상태 조회<br>

콘텐츠 이름을 지정하여 특정 구독의 계약 상태를 조회합니다.<br>
상태에는 플레이어가 현재 계약 중인지 여부와 유효기간 등의 관련 정보가 포함됩니다.<br>
플레이어가 특정 구독을 보유하고 있는지 확인할 때 사용합니다. 예를 들어, 데일리 보너스 보상을 지급하기 전에 "월정액 패스"가 유효한지 확인하거나, 플레이어 프로필에 "VIP 멤버십: 유효"라고 표시하는 경우에 유용합니다.

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| gameSession | GameSession | | |  |  | GameSession |
| contentName | string |  | ✓|  |  ~ 128자 | 스토어 구독 콘텐츠 모델 이름 |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzSubscriptionStatus](#ezsubscriptionstatus) | 구독 계약 상태|

#### 구현 예제




**Unity (UniTask)**
```csharp
    var domain = gs2.Money2.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SubscriptionStatus(
        contentName: "content-0001"
    );
    var item = await domain.ModelAsync();

```

**Unity (Vanilla)**
```cs
    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;

```

**Unreal Engine 5**
```cpp
    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;
    }

```

**Godot**
```gdscript

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

```


##### 값 변경 이벤트 핸들링




**Unity (UniTask)**
```csharp
    var domain = gs2.Money2.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SubscriptionStatus(
        contentName: "content-0001"
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    domain.Unsubscribe(callbackId);

```

**Unity (Vanilla)**
```cs
    var domain = gs2.Money2.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SubscriptionStatus(
        contentName: "content-0001"
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    domain.Unsubscribe(callbackId);

```

**Unreal Engine 5**
```cpp
    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);

```

**Godot**
```gdscript

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)

```


**⚠️ Warning**

이 이벤트는 SDK가 가진 로컬 캐시의 값이 변경되었을 때 호출됩니다.

로컬 캐시는 SDK가 가진 API의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-Distributor를 통한 스탬프 시트의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-JobQueue의 실행에 의해 변화한 것만이 대상이 됩니다.

따라서 이 방법 이외로 값이 변경된 경우 콜백은 호출되지 않습니다.

---

### listSubscriptionStatuses

플레이어의 구독 계약 상태 목록 조회<br>

플레이어가 계약했을 가능성이 있는 모든 구독 콘텐츠의 계약 상태를 조회합니다.<br>
구독이란 App Store나 Google Play에서 구매하는 "월정액 패스"나 "VIP 멤버십"과 같은 정기 결제를 의미합니다.<br>
각 상태는 플레이어가 해당 콘텐츠를 현재 계약하고 있는지 여부를 나타냅니다.<br>
구독 개요를 표시할 때 사용합니다. 예를 들어, 멤버십 화면에서 "월정액 패스: 유효(3월 15일까지)", "VIP 멤버십: 미계약"과 같이 표시하는 경우에 유용합니다.

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| gameSession | GameSession | | |  |  | GameSession |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| items | [List&lt;EzSubscriptionStatus&gt;](#ezsubscriptionstatus) | 구독 계약 상태 목록|

#### 구현 예제




**Unity (UniTask)**
```csharp
    var domain = gs2.Money2.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    );
    var items = await domain.SubscriptionStatusesAsync(
    ).ToListAsync();

```

**Unity (Vanilla)**
```cs
    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;
        }
    }

```

**Unreal Engine 5**
```cpp
    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());
    }

```


##### 값 변경 이벤트 핸들링




**Unity (UniTask)**
```csharp
    var domain = gs2.Money2.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.SubscribeSubscriptionStatuses(
        () => {
            // 리스트의 요소가 변화했을 때 호출됨
        }
    );

    // 이벤트 핸들링 정지
    domain.UnsubscribeSubscriptionStatuses(callbackId);

```

**Unity (Vanilla)**
```cs
    var domain = gs2.Money2.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.SubscribeSubscriptionStatuses(
        () => {
            // 리스트의 요소가 변화했을 때 호출됨
        }
    );

    // 이벤트 핸들링 정지
    domain.UnsubscribeSubscriptionStatuses(callbackId);

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->Money2->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    );
    
    // 이벤트 핸들링 시작
    const auto CallbackId = Domain->SubscribeSubscriptionStatuses(
        []() {
            // 리스트의 요소가 변화했을 때 호출됨
        }
    );

    // 이벤트 핸들링 정지
    Domain->UnsubscribeSubscriptionStatuses(CallbackId);

```


**⚠️ Warning**

이 이벤트는 SDK가 가진 로컬 캐시의 값이 변경되었을 때 호출됩니다.

로컬 캐시는 SDK가 가진 API의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-Distributor를 통한 스탬프 시트의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-JobQueue의 실행에 의해 변화한 것만이 대상이 됩니다.

따라서 이 방법 이외로 값이 변경된 경우 콜백은 호출되지 않습니다.

---

### takeOverSubscriptionStatus

다른 계정에서 이 플레이어에게 구독 이전<br>

현재 다른 플레이어 계정에 연결되어 있는 구독을 이 플레이어의 계정으로 이동합니다.<br>
플레이어가 계정 이전을 수행하는 경우에 사용합니다. 예를 들어, 기기 변경으로 새 계정을 생성한 경우, 스토어 영수증을 사용하여 기존 계정의 "월정액 패스" 구독을 새 계정으로 이전할 수 있습니다.<br>
구독은 기존 계정에서 해제되어 새 계정에 연결되므로, 기존 계정에서는 더 이상 구독 혜택을 받을 수 없게 됩니다.<br>
계정 이전 흐름에서 사용합니다. 예를 들어, 플레이어가 새 계정으로 로그인하여 이전을 확인한 후 호출합니다.

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| gameSession | GameSession | | |  |  | GameSession |
| receipt | string |  | ✓|  |  ~ 1024자 | 영수증 |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzSubscriptionStatus](#ezsubscriptionstatus) | 구독 계약 상태|

#### Error

이 API에는 특별한 예외가 정의되어 있습니다.<br>
GS2-SDK for Game Engine에서는 게임 내에서 핸들링이 필요할 것으로 예상되는 에러를, 일반적인 예외에서 파생된 특수화된 예외로 제공하여 다루기 쉽게 하고 있습니다.<br>
일반적인 에러의 종류와 핸들링 방법은 [여기]() 문서를 참고해 주세요.

| 타입 | 베이스 클래스 | 설명 |
| --- | --- | --- |
| LockPeriodNotElapsedException | BadRequestException | 마지막 사용자 전환 이후 잠금 기간이 경과하지 않았습니다 |

#### 구현 예제




**Unity (UniTask)**
```csharp

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.
}

```

**Unity (Vanilla)**
```cs
    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;

```

**Unreal Engine 5**
```cpp
    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();

```

**Godot**
```gdscript

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 |네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다.|
| userId | string |사용자ID|
| contentName | string |스토어 구독 콘텐츠 모델 이름|

#### 구현 예제





**Unity (UniTask)**
```csharp

    gs2.Money2.OnChangeSubscriptionStatus += notification =>
    {
        var namespaceName = notification.NamespaceName;
        var userId = notification.UserId;
        var contentName = notification.ContentName;
    };
```

**Unity (Vanilla)**
```cs

    gs2.Money2.OnChangeSubscriptionStatus += notification =>
    {
        var namespaceName = notification.NamespaceName;
        var userId = notification.UserId;
        var contentName = notification.ContentName;
    };
```

**Unreal Engine 5**
```cpp

    Gs2->Money2->OnChangeSubscriptionStatus().AddLambda([](const auto Notification)
    {
        const auto NamespaceName = Notification->NamespaceNameValue;
        const auto UserId = Notification->UserIdValue;
        const auto ContentName = Notification->ContentNameValue;
    });
```

**Godot**
```gdscript

    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
    )
```


---



