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

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

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



## 모델

### EzSerialKey

시리얼 코드<br>

발급된 시리얼 코드는 한 번만 사용할 수 있습니다.<br>
시리얼 코드는 "RPCLP-FP7N-NCDMJ-FLVA-IRI4"와 같은 형식으로 발급되며 데이터 길이는 변경할 수 없습니다.<br>
시리얼 코드 내에는 캠페인 종류에 대한 정보도 포함되어 있으며, 시리얼 코드를 사용할 때는 네임스페이스만 지정하면 사용할 수 있습니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| campaignModelName | string |  | ✓ |  |  ~ 128자 | 캠페인 이름<br>이 시리얼 코드가 속한 캠페인 모델의 이름입니다. 캠페인 정보는 시리얼 코드 자체에 포함되어 있으므로 코드를 사용할 때는 네임스페이스만 지정하면 됩니다. |
| metadata | string |  |  |  |  ~ 2048자 | 메타데이터<br>메타데이터에는 임의의 값을 설정할 수 있습니다.<br>이 값들은 GS2의 동작에는 영향을 주지 않으므로, 게임 내에서 사용하는 정보를 저장하는 용도로 사용할 수 있습니다. |
| code | string |  | ✓ |  |  ~ 48자 | 시리얼 코드<br>"XXXXX-XXXX-XXXXX-XXXX-XXXX" 형식의 시리얼 코드 문자열입니다. 각 코드는 고유하며 캠페인 식별 정보를 포함합니다. 코드의 형식과 데이터 길이는 고정되어 있어 변경할 수 없습니다. |
| status | 문자열 열거형<br>enum {<br>"ACTIVE",<br>"USED",<br>"INACTIVE"<br>}<br> |  |  | "ACTIVE" |  | 상태<br>이 시리얼 코드의 현재 사용 상태입니다. 사용자가 소비하면 ACTIVE에서 USED로 전환됩니다. 이중 사용을 방지하기 위해 낙관적 잠금(optimistic locking)으로 보호됩니다. INACTIVE 상태의 코드는 사용할 수 없습니다.ACTIVE: 사용 가능 / USED: 사용됨 / INACTIVE: 비활성(사용 불가) /  |

**관련 메서드:**
get - 시리얼 코드의 상태 확인
useSerialCode - 시리얼 코드 교환


---

### EzCampaignModel

캠페인 모델<br>

캠페인 모델은 캠페인을 정의하고 시리얼 코드와 연결하여 관리하는 데 사용됩니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| name | string |  | ✓ |  |  ~ 128자 | 캠페인 모델 이름 |
| metadata | string |  |  |  |  ~ 2048자 | 메타데이터<br>메타데이터에는 임의의 값을 설정할 수 있습니다.<br>이 값들은 GS2의 동작에는 영향을 주지 않으므로, 게임 내에서 사용하는 정보를 저장하는 용도로 사용할 수 있습니다. |
| enableCampaignCode | bool |  |  | false |  | 캠페인 코드에 의한 교환을 허용할지 여부<br>활성화하면 개별 시리얼 코드가 아닌 공통 캠페인 코드(캠페인 이름)를 사용하여 보상을 교환할 수 있게 됩니다. 이를 통해 하나의 코드를 여러 사용자가 사용할 수 있습니다. |

**관련 메서드:**
getCampaignModel - 특정 시리얼 코드 캠페인의 상세 정보 조회
get - 시리얼 코드의 상태 확인
useSerialCode - 시리얼 코드 교환


---

## 메서드

### getCampaignModel

특정 시리얼 코드 캠페인의 상세 정보 조회<br>

캠페인 이름을 지정하여 설정을 포함한 상세 정보를 조회합니다.<br>

캠페인 모델은 동일한 목적과 설정을 공유하는 시리얼 코드의 그룹을 정의합니다.<br>
예를 들어 "출시 기념 코드", "잡지 프로모션 코드", "이벤트 배포 코드" 등을 각각 별도의 캠페인으로 관리할 수 있습니다.<br>

응답에는 다음이 포함됩니다:<br>
- 캠페인 이름과 메타데이터<br>
- 캠페인이 현재 활성화되어 코드 교환을 허용하고 있는지 여부<br>

코드 입력 화면을 표시하기 전에 캠페인의 상세 정보를 확인할 때 사용합니다. 예를 들어 플레이어에게 시리얼 코드를 입력하게 하기 전에 캠페인이 활성화되어 있는지 확인할 수 있습니다.

#### Request

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

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzCampaignModel](#ezcampaignmodel) | 캠페인 모델|

#### 구현 예제




**Unity (UniTask)**
```csharp
    var domain = gs2.SerialKey.Namespace(
        namespaceName: "namespace-0001"
    ).CampaignModel(
        campaignModelName: "campaign-0001"
    );
    var item = await domain.ModelAsync();

```

**Unity (Vanilla)**
```cs
    var domain = gs2.SerialKey.Namespace(
        namespaceName: "namespace-0001"
    ).CampaignModel(
        campaignModelName: "campaign-0001"
    );
    var future = domain.ModelFuture();
    yield return future;
    var item = future.Result;

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->SerialKey->Namespace(
        "namespace-0001" // namespaceName
    )->CampaignModel(
        "campaign-0001" // campaignModelName
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

```

**Godot**
```gdscript

var domain = ez.serial_key.namespace_(
        "namespace-0001"
    ).campaign_model(
        "campaign-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.SerialKey.Namespace(
        namespaceName: "namespace-0001"
    ).CampaignModel(
        campaignModelName: "campaign-0001"
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

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

```

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

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

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->SerialKey->Namespace(
        "namespace-0001" // namespaceName
    )->CampaignModel(
        "campaign-0001" // campaignModelName
    );
    
    // 이벤트 핸들링 시작
    const auto CallbackId = Domain->Subscribe(
        [](TSharedPtr<Gs2::SerialKey::Model::FCampaignModel> value) {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

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

```

**Godot**
```gdscript

var domain = ez.serial_key.namespace_(
        "namespace-0001"
    ).campaign_model(
        "campaign-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의 실행에 의해 변화한 것만이 대상이 됩니다.

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

---

### get

시리얼 코드의 상태 확인<br>

특정 시리얼 코드의 상세 정보를 조회합니다. 사용 여부, 어떤 캠페인에 속하는지 등의 정보가 포함됩니다.<br>

코드를 교환하기 전후에 상태를 확인할 때 사용합니다. 예를 들어:<br>
- 플레이어가 이미 사용된 코드를 입력했을 때 "이 코드는 이미 사용되었습니다"라고 표시<br>
- 플레이어가 교환을 확정하기 전에 캠페인 정보(코드로 얻을 수 있는 보상)를 표시<br>
- 고객 지원 목적으로 코드를 조회<br>

코드는 "XXXXX-XXXX-XXXXX-XXXX-XXXXX"와 같은 형식입니다.<br>
응답에는 시리얼 코드의 상세 정보와 관련된 캠페인 모델이 포함됩니다.

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| code | string |  | ✓|  |  ~ 48자 | 시리얼 코드<br>"XXXXX-XXXX-XXXXX-XXXX-XXXX" 형식의 시리얼 코드 문자열입니다. 각 코드는 고유하며 캠페인 식별 정보를 포함합니다. 코드의 형식과 데이터 길이는 고정되어 있어 변경할 수 없습니다. |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzSerialKey](#ezserialkey) | 시리얼 코드|
| campaignModel | [EzCampaignModel](#ezcampaignmodel) | 캠페인 모델|

#### 구현 예제




**Unity (UniTask)**
```csharp
    var domain = gs2.SerialKey.Namespace(
        namespaceName: "namespace-0001"
    ).User(
        userId: "user-0001"
    ).SerialKey(
        serialKeyCode: "code-0001"
    );
    var item = await domain.ModelAsync();

```

**Unity (Vanilla)**
```cs
    var domain = gs2.SerialKey.Namespace(
        namespaceName: "namespace-0001"
    ).User(
        userId: "user-0001"
    ).SerialKey(
        serialKeyCode: "code-0001"
    );
    var future = domain.ModelFuture();
    yield return future;
    var item = future.Result;

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->SerialKey->Namespace(
        "namespace-0001" // namespaceName
    )->User(
        "user-0001" // userId
    )->SerialKey(
        "code-0001" // serialKeyCode
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

```

**Godot**
```gdscript

var domain = ez.serial_key.namespace_(
        "namespace-0001"
    ).user(
        "user-0001"
    ).serial_key(
        "code-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.SerialKey.Namespace(
        namespaceName: "namespace-0001"
    ).User(
        userId: "user-0001"
    ).SerialKey(
        serialKeyCode: "code-0001"
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

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

```

**Unity (Vanilla)**
```cs
    var domain = gs2.SerialKey.Namespace(
        namespaceName: "namespace-0001"
    ).User(
        userId: "user-0001"
    ).SerialKey(
        serialKeyCode: "code-0001"
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

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

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->SerialKey->Namespace(
        "namespace-0001" // namespaceName
    )->User(
        "user-0001" // userId
    )->SerialKey(
        "code-0001" // serialKeyCode
    );
    
    // 이벤트 핸들링 시작
    const auto CallbackId = Domain->Subscribe(
        [](TSharedPtr<Gs2::SerialKey::Model::FSerialKey> value) {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

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

```

**Godot**
```gdscript

var domain = ez.serial_key.namespace_(
        "namespace-0001"
    ).user(
        "user-0001"
    ).serial_key(
        "code-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의 실행에 의해 변화한 것만이 대상이 됩니다.

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

---

### useSerialCode

시리얼 코드 교환<br>

시리얼 코드를 사용(소비)하여 현재 플레이어가 교환한 것으로 표시합니다.<br>
한 번 사용된 코드는 누구도 다시 사용할 수 없습니다.<br>

게임 내 "시리얼 코드 입력" 기능에서 사용하는 메인 API입니다. 일반적인 흐름은 다음과 같습니다:<br>
1. 플레이어가 게임 내 "코드 교환" 화면을 연다<br>
2. 플레이어가 시리얼 코드를 입력한다(프로모션 카드, 이메일, 웹사이트 등에서 얻은 것)<br>
3. 게임이 입력된 코드로 UseSerialCode를 호출한다<br>
4. 성공하면 코드가 사용됨 상태가 된다 — 보상 지급 시스템과 조합하여 플레이어에게 보상을 지급한다<br>
5. 코드가 이미 사용된 경우 "사용됨" 오류가 반환된다<br>
6. 코드가 존재하지 않는 경우 "코드를 찾을 수 없음" 오류가 반환된다<br>

주요 사용 사례:<br>
- 상품이나 잡지에 동봉되는 프로모션 코드<br>
- 이벤트나 SNS를 통해 배포되는 기프트 코드<br>
- 사전 등록 특전 코드<br>
- 콜라보레이션 캠페인 코드

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| gameSession | GameSession | | ✓|  |  | GameSession |
| code | string |  | ✓|  |  ~ 48자 | 시리얼 코드<br>"XXXXX-XXXX-XXXXX-XXXX-XXXX" 형식의 시리얼 코드 문자열입니다. 각 코드는 고유하며 캠페인 식별 정보를 포함합니다. 코드의 형식과 데이터 길이는 고정되어 있어 변경할 수 없습니다. |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzSerialKey](#ezserialkey) | 시리얼 코드|
| campaignModel | [EzCampaignModel](#ezcampaignmodel) | 캠페인 모델|

#### Error

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

| 타입 | 베이스 클래스 | 설명 |
| --- | --- | --- |
| AlreadyUsedException | BadRequestException | 지정된 시리얼 코드는 이미 사용되었습니다 |
| CodeNotFoundException | NotFoundException | 지정된 시리얼 코드는 존재하지 않습니다 |

#### 구현 예제




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

try {
    var domain = gs2.SerialKey.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SerialKey(
        serialKeyCode: "code-0001"
    );
    var result = await domain.UseSerialCodeAsync(
        code: "code-0001"
    );
    var item = await result.ModelAsync();
} catch(Gs2.Gs2SerialKey.Exception.AlreadyUsedException e) {
    // The specified serial code has already been used.
} catch(Gs2.Gs2SerialKey.Exception.CodeNotFoundException e) {
    // The specified serial code does not exist.
}

```

**Unity (Vanilla)**
```cs
    var domain = gs2.SerialKey.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SerialKey(
        serialKeyCode: "code-0001"
    );
    var future = domain.UseSerialCodeFuture(
        code: "code-0001"
    );
    yield return future;
    if (future.Error != null)
    {
        if (future.Error is Gs2.Gs2SerialKey.Exception.AlreadyUsedException)
        {
            // The specified serial code has already been used.
        }
        if (future.Error is Gs2.Gs2SerialKey.Exception.CodeNotFoundException)
        {
            // The specified serial code does not exist.
        }
        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->SerialKey->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->SerialKey(
        "code-0001" // serialKeyCode
    );
    const auto Future = Domain->UseSerialCode(
        "code-0001" // code
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        auto e = Future->GetTask().Error();
        if (e->IsChildOf(Gs2::SerialKey::Error::FAlreadyUsedError::Class))
        {
            // The specified serial code has already been used.
        }
        if (e->IsChildOf(Gs2::SerialKey::Error::FCodeNotFoundError::Class))
        {
            // The specified serial code does not exist.
        }
        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.serial_key.namespace_(
        "namespace-0001"
    ).me(game_session).serial_key(
        "code-0001"
    )

var async_result = await domain.use_serial_code(
    "code-0001" # code
)
if async_result.error != null:
    if async_result.error is Gs2SerialKeyAlreadyUsedException:
        # 지정된 시리얼 코드는 이미 사용되었습니다
        pass
    if async_result.error is Gs2SerialKeyCodeNotFoundException:
        # 지정된 시리얼 코드는 존재하지 않습니다
        pass
    push_error(str(async_result.error))
    return

var result = async_result.result

```


---



