---
title: Product API 활용 예제
slug: world-sdk-guide-kr/product-api
docTags: 
createdAt: 2024-09-02T08:34:13.134Z
---

다음은 Product API를 사용하여 월드 통화 및 월드 상품을 관리하는 예입니다.

이 예제는 제품 API에 대한 기본적인 이해를 돕고 서버와 클라이언트 간의 메시지를 송수신하는 구현을 가능하게 하여 월드 통화 및 월드 상품의 획득와 차감을 쉽게 테스트할 수 있도록 합니다.

이 예제에는 월드 통화 및 월드 상품 클라이언트 스크립트에 대한 설명이 포함되어 있으며, 모든 서버 측 처리를 처리하는 통합 서버 스크립트가 뒤따르며 다음 기능이 포함됩니다:

- 월드 통화 획득
- 월드 통화 차감
- 통화 차감 없이 월드 상품 획득
- 월드 상품 차감

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-LVomjGT16SVteQlTOo7ug-20240904-102419.gif)



## 월드 통화 획득 및 차감

아래는 월드 통화 획득 및 차감을 관리하는 전체 클라이언트 코드와 이를 구현하는 방법입니다.

### 통화 클라이언트 스크립트

:::CodeblockTabs
CurrencyManagerSample

```typescript
import { Button, Text } from 'UnityEngine.UI';
import { ZepetoScriptBehaviour } from 'ZEPETO.Script'
import { LocalPlayer, ZepetoPlayers } from 'ZEPETO.Character.Controller';
import { Room, RoomData } from 'ZEPETO.Multiplay';
import { ZepetoWorldMultiplay } from 'ZEPETO.World';
 
export default class CurrencyManagerSample extends ZepetoScriptBehaviour {
 
    public currencyId: string;
    public increaseQuantity: number;
    public decreaseQuantity: number;
    public increaseCurrencyBtn: Button;
    public decreaseCurrencyBtn: Button;
    public balanceText: Text;
    public multiplay: ZepetoWorldMultiplay;
 
    private _localPlayer: LocalPlayer;
 
    Start() {
 
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            this._localPlayer = ZepetoPlayers.instance.LocalPlayer;
            this.loadCurrencyBalance(this.currencyId);
        });
 
        // 통화 UI 업데이트를 위한 방 참여 이벤트 처리
        this.multiplay.RoomJoined += (room: Room) => {
            room.AddMessageHandler("SyncCurrencyInfo", (message: CurrencyMessage) => {
                const currentCurrencyId = message.currencyId;
                const currentQuantity = message.quantity;
                this.balanceUIUpdate(currentCurrencyId, currentQuantity);
            });
        };
 
        // 통화 증가 버튼 이벤트 리스너 : 크레딧 요청
        this.increaseCurrencyBtn.onClick.AddListener(() => {
            const data = new RoomData();
            data.Add("currencyId", this.currencyId);
            data.Add("quantity", this.increaseQuantity);
            this.multiplay.Room?.Send("CreditCurrency", data.GetObject());
        })
 
        // 통화 감소 버튼 이벤트 리스너 : 차감 요청
        this.decreaseCurrencyBtn.onClick.AddListener(() => {
            const data = new RoomData();
            data.Add("currencyId", this.currencyId);
            data.Add("quantity", this.decreaseQuantity);
            this.multiplay.Room?.Send("DebitCurrency", data.GetObject());
        })
 
    }
 
    // 지정된 통화의 현재 금액 요청
    private loadCurrencyBalance(currentCurrencyId: string) {
        this.multiplay.Room?.Send("LoadBalance", currentCurrencyId);
    }
 
    // 현재 통화의 수량을 반영하도록 UI 업데이트
    private balanceUIUpdate(currentCurrencyId: string, currentQuantity: number) {
        this.balanceText.text = currentQuantity.toString();
    }
 
}
 
// 통화 동기화 메시지 인터페이스
interface CurrencyMessage {
    currencyId: string,
    quantity: number,
}
```
:::



### 통화 클라이언트 스크립트 설명

- 캐릭터가 로드되면, 서버에 기존 통화 잔액을 로드하기 위해 방 메시지가 전송됩니다. 그런 다음 서버에서 수신한 잔액 정보를 기반으로 UI가 업데이트됩니다.

```typescript
this.multiplay.Room?.Send("LoadBalance", currentCurrencyId);
```

- 메시지 교환을 용이하게 하기 위해, `CurrencyMessage` 인터페이스가 정의됩니다.

```typescript
interface CurrencyMessage {
    currencyId: string,
    quantity: number,
}
```

- 사용자가 통화를 증가시키거나 감소시킬 때, 그들은 서버에 Room 메시지로 요청을 보냅니다.
  어떤 통화를 얼마나 증가시키거나 감소시킬지를 포함한 데이터가 포함됩니다.

```typescript
this.multiplay.Room?.Send("CreditCurrency", data.GetObject());
this.multiplay.Room?.Send("DebitCurrency", data.GetObject());
```

- 서버는 통화의 증가 또는 감소를 처리한 후, 최종 잔액 정보를 클라이언트에 보냅니다. 클라이언트는 이 정보를 수신하고 UI를 업데이트합니다.

[전체 서버 스크립트 보기](./#-)&#x20;


```typescript
this.multiplay.RoomJoined += (room: Room) => {
    room.AddMessageHandler("SyncCurrencyInfo", (message: CurrencyMessage) => {
        const currentCurrencyId = message.currencyId;
        const currentQuantity = message.quantity;
        this.updateBalanceUI(currentCurrencyId, currentQuantity);
    });
};
```



## 월드 상품의 획득 및 차감

아래는 월드 제품 획득 및 차감을 관리하는 전체 클라이언트 코드와 이를 구현하는 방법입니다.

### 제품 클라이언트 스크립트

:::CodeblockTabs
ProductManagerSample

```typescript
import { Button, Text } from 'UnityEngine.UI';
import { ZepetoScriptBehaviour } from 'ZEPETO.Script'
import { LocalPlayer, ZepetoPlayers } from 'ZEPETO.Character.Controller';
import { Room, RoomData } from 'ZEPETO.Multiplay';
import { ZepetoWorldMultiplay } from 'ZEPETO.World';
import { InventoryRecord, InventoryService } from 'ZEPETO.Inventory';
import { WaitUntil } from 'UnityEngine';
 
export default class ProductManagerSample extends ZepetoScriptBehaviour {
 
    public productId: string;
    public productAddQuantity: number;
    public acquireItemBtn: Button;
    public useItemBtn: Button;
    public itemCountText: Text;
    public multiplay: ZepetoWorldMultiplay;
 
    private _localPlayer: LocalPlayer;
 
    Start() {
 
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            this._localPlayer = ZepetoPlayers.instance.LocalPlayer;
            this.StartCoroutine(this.refreshProductUI());
        });
 
        // 제품 UI 업데이트를 위한 방 참여 이벤트 처리
        this.multiplay.RoomJoined += (room: Room) => {
            room.AddMessageHandler("SyncProductInfo", (message: ProductMessage) => {
                this.StartCoroutine(this.refreshProductUI());
            });
        };
 
        // 아이템 획득 버튼 이벤트 리스너 : 제품 추가
        this.acquireItemBtn.onClick.AddListener(() => {
            const data = new RoomData();
            data.Add("productId", this.productId);
            data.Add("quantity", this.productAddQuantity);
            this.multiplay.Room?.Send("AddProduct", data.GetObject());
        })
 
        // 아이템 사용 버튼 이벤트 리스너 : 제품 사용
        this.useItemBtn.onClick.AddListener(() => {
            const data = new RoomData();
            data.Add("productId", this.productId);
            data.Add("quantity", 1);
            this.multiplay.Room?.Send("UseProduct", data.GetObject());
        })
 
    }
     
    private * refreshProductUI() {
        const request = InventoryService.GetAsync(this.productId);
        yield new WaitUntil(() => request.keepWaiting == false);
        // 요청이 성공하면 제품 UI를 업데이트합니다.
        if (request.responseData.isSuccess) {
            this.updateItemCountText(request.responseData.product);
        }
        else {
            console.log("제품 아이템 로드 실패.");
        }
    }
 
    private updateItemCountText(item: InventoryRecord) {
        if (item != null) {
            this.itemCountText.text = item.quantity.toString();
        }
        else {
            this.itemCountText.text = "0";
        }
    }
 
}
 
// 제품 메시지 인터페이스.
interface ProductMessage {
    productId: string,
    productAction: ProductAction,
}
 
// 제품 행동 유형을 정의하는 열거형.
export enum ProductAction {
    Use,
    Add,
}
```
:::



### 제품 클라이언트 스크립트 설명

- 캐릭터가 로드될 때, `InventoryService`를 사용하여 [ZEPETO.Inventory](https://developer.zepeto.me/docs/product/namespaces/ZEPETO_Inventory/)의 초기 제품 인벤토리를 로드합니다. 그런 다음 UI를 업데이트합니다.

```typescript
private * refreshProductUI() {
        const request = InventoryService.GetAsync(this.productId);
        yield new WaitUntil(() => request.keepWaiting == false);
        if (request.responseData.isSuccess) {
            this.updateItemCountText(request.responseData.product);
        }
        else {
            console.log("제품 아이템 로드 실패.");
        }
    }
```

- 메시지를 보내고 받으려면, `ProductMessage` 인터페이스를 정의합니다. `ProductAction` 열거형을 예제 구현을 위해 정의합니다.

```typescript
interface ProductMessage {
    productId: string,
    productAction: ProductAction,
}
 
export enum ProductAction {
    사용,
    추가,
}
```

- 제품을 증가시키거나 감소시킬 때, 해당 데이터를 서버에 룸 메시지로 보냅니다.
  어떤 제품을 얼마나 증가시키거나 감소시킬지에 대한 데이터가 포함됩니다.

```typescript
this.multiplay.Room?.Send("AddProduct", data.GetObject());
this.multiplay.Room?.Send("UseProduct", data.GetObject());
```

- 제품 변경을 처리한 후, 서버는 최종 재고 정보를 클라이언트에 전송하여 UI를 업데이트합니다.

[전체 서버 스크립트 보기](./#-)&#x20;

```typescript
this.multiplay.RoomJoined += (room: Room) => {
    room.AddMessageHandler("SyncProductInfo", (message: ProductMessage) => {
        this.StartCoroutine(this.refreshProductUI());
    });
};
```

:::hint{type="success"}
- 일반적으로, 월드 제품은 구매를 위해 화폐가 필요합니다.
- 이 경우, 가이드를 참조하여 **제품 구매 버튼**으로 쉽게 판매할 수 있습니다.[내 월드로 돈벌기! 아이템과 재화 세팅하기](docId\:bi1uNMNWHaPlvxG21YTSO)&#x20;
:::



## 서버 스크립트

아래는 월드 통화와 제품을 관리하는 전체 서버 코드와 이를 구현하는 방법입니다.

### 전체 서버 스크립트

```typescript
import { Sandbox, SandboxOptions, SandboxPlayer } from "ZEPETO.Multiplay";
import { loadCurrency } from "ZEPETO.Multiplay.Currency";
import { loadInventory } from "ZEPETO.Multiplay.Inventory";

export default class extends Sandbox {

    async onCreate(options: SandboxOptions) {

        // 통화
        //통화 로딩 요청
        this.onMessage("LoadBalance", (client, message: string) => {
            this.loadBalance(client, message);
        });
        //크레딧 추가 요청
        this.onMessage("CreditCurrency", (client, message: CurrencyMessage ) => {
            const currencyId = message.currencyId;
            const quantity = message.quantity;
            this.addCredit(client, currencyId, quantity);
        });
        //크레딧 차감 요청
        this.onMessage("DebitCurrency", (client, message: CurrencyMessage ) => {
            const currencyId = message.currencyId;
            const quantity = message.quantity;
            this.onDebit(client, currencyId, quantity);
        });

        // 제품
        //아이템 추가 요청
        this.onMessage("AddProduct", (client, message: any) => {
            const productId = message.productId;
            const quantity = message.quantity;
            this.addProduct(client, productId, quantity);
        });
        //아이템 사용 요청
        this.onMessage("UseProduct", (client, message: any) => {
            const productId = message.productId;
            const quantity = message.quantity;
            this.useProduct(client, productId, quantity);
        });

    }

    async addCredit(client: SandboxPlayer, currencyId: string, quantity: number) {
        try {
            const currency = await loadCurrency(client.userId);
            await currency.credit(currencyId, quantity);
            this.loadBalance(client, currencyId);
        } catch (e) {
            console.error(`AddCredit Error: ${e}`);
        }
    }

    async onDebit(client: SandboxPlayer, currencyId: string, quantity: number) {
        try {
            const currency = await loadCurrency(client.userId);
            if (await currency.debit(currencyId, quantity) === true) {
                this.loadBalance(client, currencyId);
            } else {
                console.error("DebitCredit Error: 통화 부족");
            }
        } catch (e) {
            console.error(`DebitCredit Error: ${e}`);
        }
    }

    async loadBalance(client: SandboxPlayer, currencyId: string) {
        try {
            const currency = await loadCurrency(client.userId);
            let balancesObject = await currency.getBalances();
            let balancesMap: Map<string, number> = new Map(Object.entries(balancesObject));
            const specificCurrencyID = currencyId;
            const specificBalance = balancesMap.get(specificCurrencyID) ?? 0;
            const currencySync: CurrencyMessage = {
                currencyId: specificCurrencyID,
                quantity: specificBalance
            }
            client.send("SyncCurrencyInfo", currencySync);
        } catch (e) {
            console.error(`loadBalance Error: ${e}`);
        }
    }

    async addProduct(client: SandboxPlayer, productId: string, quantity: number) {
        try {
            const inventory = await loadInventory(client.userId);
            await inventory.add(productId, quantity);
            const productMessage: ProductMessage = {
                productId: productId,
                productAction: ProductAction.Add
            }
            client.send("SyncProductInfo", productMessage);
        } catch (e) {
            console.error(`addProduct Error: ${e}`);
        }
    }

    async useProduct(client: SandboxPlayer, productId: string, quantity: number) {
        try {
            const inventory = await loadInventory(client.userId);
            await inventory.use(productId, quantity);
            const productMessage: ProductMessage = {
                productId: productId,
                productAction: ProductAction.Use
            }
            client.send("SyncProductInfo", productMessage);
        } catch (e) {
            console.error(`useProduct Error: ${e}`);
        }
    }

    onJoin(client: SandboxPlayer) {
        // 입장 시 로직
    }

    onTick(deltaTime: number): void {
        // 틱 로직
    }

    onLeave(client: SandboxPlayer, consented?: boolean) {
        // 퇴장 처리 로직
    }

}

// 통화 메시지 인터페이스
interface CurrencyMessage {
    currencyId: string,
    quantity: number,
}

// 제품 메시지 인터페이스.
interface ProductMessage {
    productId: string,
    productAction: ProductAction,
}

// 제품 액션 유형 정의를 위한 열거형.
export enum ProductAction {
    Use,
    Add,
}
```



### 서버 스크립트 설명

- 월드 통화를 관리하려면 [ZEPETO.Multiplay.Currency](https://developer.zepeto.me/docs/multiplay-server/namespaces/ZEPETO_Multiplay_Currency/).
- 사용하려면 `currency.credit()` 및 `currency.debit()`를 사용하여 원하는 통화의 잔액을 증가시키거나 감소시킵니다. 이후, `currency.getBalances()`를 호출하여 각 통화의 현재 잔액을 가져옵니다.

```typescript
currency.credit(currencyId, quantity);
currency.debit(currencyId, quantity);
currency.getBalances();
```

- 월드 상품을 관리하려면 [ZEPETO.Multiplay.Inventory](https://developer.zepeto.me/docs/multiplay-server/namespaces/ZEPETO_Multiplay_Inventory).
- 사용하려면 `inventory.add()` 및 `inventory.use()`를 사용하여 원하는 통화의 잔액을 증가시키거나 감소시킵니다.

```typescript
inventory.add(productId, quantity);
inventory.use(productId, quantity);
```

- 클라이언트의 통화 및 제품 증가 또는 감소 요청을 처리합니다.
- 사용 `loadBalance()`를 사용하여 ZEPETO Studio에 등록된 통화 정보를 가져옵니다.
- 여러 통화가 있는 경우 특정 통화 ID 값에 조건을 걸어 해당 통화의 잔액만 가져올 수 있습니다.
- 최종 잔액 값을 클라이언트에게 방 메시지로 전달합니다.

