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

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

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



## 모델

### EzWallet

지갑<br>

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

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

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| slot | int |  | ✓ |  | 0 ~ 100000000 | 슬롯 번호<br>플랫폼이나 컨텍스트별로 지갑 잔액을 분리하기 위한 식별자입니다.<br>슬롯을 달리하면 서로 다른 유료 통화 풀을 관리할 수 있습니다(예: iOS 구매는 슬롯 0, Android는 슬롯 1).<br>무료 통화는 네임스페이스의 shareFree 설정에 따라 모든 슬롯 간에 공유할 수도 있습니다. |
| paid | int |  |  | 0 | 0 ~ 2147483646 | 유료 통화 보유량<br>이 지갑 슬롯 내 유료(구매한) 통화의 합계량입니다.<br>단가가 0이 아닌 모든 WalletDetail 항목의 합계입니다.<br>스토어 구매를 통한 충전으로 증가하고, 소비 우선순위에 따라 소비되어 감소합니다. |
| free | int |  |  | 0 | 0 ~ 2147483646 | 무료 통화 보유량<br>이 지갑 슬롯 내 무료(지급된) 통화의 합계량입니다.<br>단가가 0인 WalletDetail 항목에 해당합니다.<br>네임스페이스에서 shareFree가 활성화된 경우, 이 값은 슬롯 0에서 모든 지갑 슬롯으로 동기화됩니다. |
| shareFree | bool |  |  | false |  | 무상 통화 공유<br>이 지갑의 무상 통화가 모든 슬롯 간에 공유되는지 여부입니다.<br>이 값은 지갑 생성 시 네임스페이스 설정에서 상속됩니다.<br>true인 경우, 무상 통화가 슬롯 0에서 다른 모든 지갑 슬롯으로 동기화됩니다. |
| updatedAt | long |  | ※ | 현재 시각 |  | 최종 갱신일시<br>UNIX 시간・밀리초<br>※ 서버가 자동으로 설정 |

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


---

## 메서드

### get

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

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

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| gameSession | GameSession | | ✓|  |  | GameSession |
| slot | int |  | ✓|  | 0 ~ 100000000 | 슬롯 번호<br>플랫폼이나 컨텍스트별로 지갑 잔액을 분리하기 위한 식별자입니다.<br>슬롯을 달리하면 서로 다른 유료 통화 풀을 관리할 수 있습니다(예: iOS 구매는 슬롯 0, Android는 슬롯 1).<br>무료 통화는 네임스페이스의 shareFree 설정에 따라 모든 슬롯 간에 공유할 수도 있습니다. |

#### Result

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

#### 구현 예제




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

```

**Unity (Vanilla)**
```cs
    var domain = gs2.Money.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->Money->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.money.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.Money.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.Money.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->Money->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->Wallet(
        0 // slot
    );
    
    // 이벤트 핸들링 시작
    const auto CallbackId = Domain->Subscribe(
        [](TSharedPtr<Gs2::Money::Model::FWallet> value) {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

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

```

**Godot**
```gdscript

var domain = ez.money.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의 실행에 의해 변화한 것만이 대상이 됩니다.

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

---

### 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>플랫폼이나 컨텍스트별로 지갑 잔액을 분리하기 위한 식별자입니다.<br>슬롯을 달리하면 서로 다른 유료 통화 풀을 관리할 수 있습니다(예: iOS 구매는 슬롯 0, Android는 슬롯 1).<br>무료 통화는 네임스페이스의 shareFree 설정에 따라 모든 슬롯 간에 공유할 수도 있습니다. |
| count | int |  | ✓|  | 1 ~ 2147483646 | 소비할 유료 통화 수량 |
| paidOnly | bool |  | | false |  | 유료 통화만을 대상으로 할지 여부 |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzWallet](#ezwallet) | 소비 후 지갑|
| price | float | 소비한 통화의 가격|

#### Error

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

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

#### 구현 예제




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

try {
    var domain = gs2.Money.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Wallet(
        slot: 0
    );
    var result = await domain.WithdrawAsync(
        count: 50,
        paidOnly: null
    );
    var item = await result.ModelAsync();
    var price = result.Price;
} catch(Gs2.Gs2Money.Exception.ConflictException e) {
    // The wallet operation process conflicted. Retry required.
} catch(Gs2.Gs2Money.Exception.InsufficientException e) {
    // Wallet balance is insufficient.
}

```

**Unity (Vanilla)**
```cs
    var domain = gs2.Money.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Wallet(
        slot: 0
    );
    var future = domain.WithdrawFuture(
        count: 50,
        paidOnly: null
    );
    yield return future;
    if (future.Error != null)
    {
        if (future.Error is Gs2.Gs2Money.Exception.ConflictException)
        {
            // The wallet operation process conflicted. Retry required.
        }
        if (future.Error is Gs2.Gs2Money.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 price = future.Result.Price;

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->Money->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->Wallet(
        0 // slot
    );
    const auto Future = Domain->Withdraw(
        50 // count
        // paidOnly
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        auto e = Future->GetTask().Error();
        if (e->IsChildOf(Gs2::Money::Error::FConflictError::Class))
        {
            // The wallet operation process conflicted. Retry required.
        }
        if (e->IsChildOf(Gs2::Money::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 Price = Result->Price;

```

**Godot**
```gdscript

var domain = ez.money.namespace_(
        "namespace-0001"
    ).me(game_session).wallet(
        0
    )

var async_result = await domain.withdraw(
    50, # count
    null # paid_only
)
if async_result.error != null:
    if async_result.error is Gs2MoneyConflictException:
        # 지갑 조작 처리가 충돌했습니다. 재시도가 필요합니다
        pass
    if async_result.error is Gs2MoneyInsufficientException:
        # 지갑의 잔액이 부족합니다
        pass
    push_error(str(async_result.error))
    return

var result = async_result.result

```


---



