# Inworld Product

### 1 판매자의 의무🚩

크리에이터는 자신이 판매한 인월드 상품에 대한 **사용자의 문의에 반드시 대응**해야 하며, 이를 준수하지 않는 경우 🟥**판매 자격이 정지**될 수 있어요

자세한 내용은 디토랜드 정책 센터내 **UGC 정책**의 제 2장 **유료 UGC의 판매** 항목을 참고하세요.

⇒ <https://ditoland.net/policyCenter/studio>

### 2 상품 등록하기🎀

1. 월드 관리 페이지내 **월드 상품 관리** 메뉴에 진입해요.

   <figure><img src="/files/J08am1xTj9D4EY09f5DK" alt=""><figcaption></figcaption></figure>
2. 월드 상품 관리 페이지에서 **등록하기 버튼**을 눌러 상품을 등록할 수 있어요.\ <mark style="color:red;">**(등록한 상품은 삭제할 수 없어요.)**</mark>

   <figure><img src="/files/Lc3exA9RQgPAROEFpmze" alt=""><figcaption></figcaption></figure>
3. 상품 등록 화면에서 **상품 정보**를 입력 후, **등록 버튼**을 눌러요.

   <figure><img src="/files/BjMKDdERnSyW4VJWuegF" alt=""><figcaption></figcaption></figure>

   * 상품 정보
     * **상품명 / 상품설명 / 상품이미지** : 상품의 용도를 기입합니다. 혐오감, 성희롱을 유발하는 등의 내용은 사용이 불가하며, 예고 없이 운영진에 의해 삭제될 수 있어요
     * **판매 금액** : 구매 시 필요한 크레딧(가격)
     * **상품 종류**

       | 종류       | 기능                                                                     | 사용 예시                                                                                         |
       | -------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
       | 이용권(영구성) | <ul><li>일회성 구매 상품<br>(특전, 접근권)</li><li>이용권 상품은 반복 구매할 수 없어요.</li></ul> | <ul><li>스테이지2 해금 상품</li></ul>                                                                 |
       | 소모품(비영구) | <ul><li>반복 구매 상품 (재화, 소모성)</li></ul>                                   | <ul><li>코인 +200 상품</li><li>무기아이템 1번 상품</li><li>스킨 상품</li><li>부활권</li><li>경험치 부스터 상품</li></ul> |

       * **판매상태** : 최초 등록 시 비공개로 등록되며, 등록 후에 상태를 변경할 수 있어요
       * **상품 ID** : 상품 등록 완료 시 자동으로 생성되며, Script에서 기능 연결 시 사용해요

   <mark style="color:red;">**※주의 사항**</mark>

   * 상품 등록 완료 후 **이미지, 판매 상태만 수정**할 수 있어요\
     (상품명, 상품설명, 판매금액 종류는 등록 후에 수정이 불가해요)
   * 등록한 상품 내용의 수정은 제한되니 입력한 정보를 잘 확인 후 저장해 주세요.

### 3 상품 상태 변경하기⚙️

1. 등록한 상품의 상태를 변경하기 위해 **상품 이름 영역을 클릭**해요.

   <figure><img src="/files/UC2Xom3zOrolRvCMuo1g" alt=""><figcaption></figcaption></figure>
2. **상품 상태**를 변경 후 **수정 버튼**을 누르면 상품 상태가 변경돼요.

   <figure><img src="/files/xnhPrYDiYkQfKc7ppf7r" alt=""><figcaption></figcaption></figure>

   <table><thead><tr><th width="143">상태</th><th>기능</th></tr></thead><tbody><tr><td>비공개</td><td>상품을 판매 목록에서 노출하지 않아요. (구매한 상품은 보존됩니다.)</td></tr><tr><td>공개</td><td>상품을 판매 목록에 노출해요.</td></tr></tbody></table>

### 4 스튜디오에서 상품 정보를 불러올 월드 ID 입력하기🏷️

1. 스튜디오에서 **파일 메뉴 - 스튜디오 설정**을 열고, Developer 탭에서 **인월드 상품용 테스트 월드 ID**에 상품이 등록된 **월드의 ID**를 입력해요.

   <figure><img src="/files/RPj2NmNTjRbQOrcAFDLj" alt=""><figcaption></figcaption></figure>

   * 월드의 ID는 **월드 페이지의 주소**에서 **마지막 번호**나 상품 관리 페이지에서 각 **상품 번호의 앞 글자**로 알 수 있어요.
2. **인월드 상품용 테스트 월드 ID**를 입력한 다음, **적용 버튼**을 눌러요.<br>

   <figure><img src="/files/Ua1ZILnXwfAmvP0UQx2P" alt=""><figcaption></figcaption></figure>

<mark style="color:red;">**※주의 사항**</mark>

* **설정한 인월드 상품용 테스트 월드 ID 정보**는 맵(RMO)에 저장되는 정보가 아니기 때문에, 스튜디오를 끄면 **다시 입력해야 해요**

### 5 스크립트 기능 구현🛠️

#### 5-1 판매할 상품 정보 가져오기🎁

**공개 상태의 상품**을 스크립트에서 가져오는 방법이에요.

```lua
--클라이언트 스크립트에서

ProductList = nil -- 상품 정보를 가져올 변수

----------------------------------------------------------------------------------------
--특정 상품 정보 출력
local function PrintProductData(productData)    
    local productID       = productData.ID           --상품 ID
    local productName     = productData.Name         --상품 이름
    local productType     = productData.ProductType  --상품 종류
    local productDesc     = productData.Comment      --상품 설명
    local priceType       = productData.PriceType    --상품 가격 타입
    local productPrice    = productData.Price        --상품 가격  
    local productImageURL = productData.ImageURL     --상품 이미지    
     
    print("productID", productID, "productName", productName, "productDesc", productDesc)
      
    if     productType == Enum.ProductType.Ticket      then 
        print("productType", "이용권")
           
    elseif productType == Enum.ProductType.Expendables then 
        print("productType", "소모품")
    end  
      
    if priceType == Enum.PriceType.DitoCredit then 
        print("priceType", "크레딧", "productPrice", productPrice)  
    end      
      
     print("productImageURL", productImageURL)
     print("--------------------------------------------")
end

----------------------------------------------------------------------------------------
--모든 상품 정보 가져오기 
--(GetProductList 함수는 OnEnterPlayer나 OnSpawnCharacter가 아닌 UI 활성화시 같은 특정 시점에서 호출해야 해요.)
--(활성화된 상품 수가 많으면 반환이 늦을 수 있어요.)
local function RefreshProductList(self)
    ProductList = Marketplace:GetProductList()  
    print("공개 상태의 상품 수 : ", #ProductList)
    
    for i = 1, #ProductList do       
        local productData = ProductList[i]
       
        PrintProductData(productData)
    end
end
Workspace.ShopUI.ShopOpenButton.OnPressEvent:Connect(RefreshProductList)
```

#### 5-2 상품 구매 요청🛒

특정 상품(상품 ID)에 대한 **구매 시도를 요청**하는 방법이에요.

```lua
--클라이언트 스크립트에서

----------------------------------------------------------------------------------------
--상품 구매 버튼 클릭시 상품 ID로 구매를 요청해요.
local function ClickPurchaseButton(self)
    local productID = productData.ID
	
    --OpenConfirmPopup 함수로 특정 상품의 구매 창을 출력해요.
    Marketplace:OpenConfirmPopup(productID)
end
productButton.OnPressEvent:Connect(ClickPurchaseButton)        
```

#### 5-3 상품 구매 결과 처리📢

**구매가 발생**했을 때, 어떻게 **결과를 판단**하는지에 대한 방법이에요.

```lua
--클라이언트 스크립트에서

----------------------------------------------------------------------------------------
--상품 구매 처리 후
local function BindDoneEvent(buyEventData, flag)
    local player = LocalPlayer:GetRemotePlayer()
    local productID = buyEventData.ID --구매 시도한 상품의 ID
    
    --구매 성공시
    if flag == 1 then      
        print(player.Name .. " " .. productID .. " 구매 성공!")
        
        --실제 지급 처리는 Server에서 
    
    --충전 필요시
    elseif flag == 2 then  
        print(player.Name .. " " .. productID .. " 충전 필요!")
    
    --중복 구매 또는 기타 이유
    elseif flag == 3 then  
        print(player.Name .. " " .. productID .. " 중복 또는 기타 사유로 구매 실패!")
    end
end
Marketplace.OnBuyDoneEvent:Connect(BindDoneEvent)
```

```lua
--서버 스크립트에서

----------------------------------------------------------------------------------------
--상품 구매 처리 후
local function BindDoneEvent(player, buyEventData, flag)    
    local productID = buyEventData.ID --구매 시도한 상품의 ID

    --구매 성공시
    if flag == 1 then      
        print(player.Name .. " " .. productID .. " 구매 성공!")
        
    --충전 필요시
    elseif flag == 2 then  
        print(player.Name .. " " .. productID .. " 충전 필요!")
    
    --중복 구매 또는 기타 이유
    elseif flag == 3 then  
        print(player.Name .. " " .. productID .. " 중복 또는 기타 사유로 구매 실패!")
    end
end
Marketplace.OnBuyDoneEvent:Connect(BindDoneEvent)
```

#### 5-4 구매 성공한 상품의 지급 처리📩

구매 결과가 성공(1)일 때, **상품을 지급**하는 방법이에요.

* OnBuyDoneEvent 함수에서 **결과가 성공**으로 반환되었을 때, **SaveUserProductData 함수로 반드시 지급을 처리**해야 해요.
* 구매는 성공했지만 통신 장애 등으로 인해 **지급이 처리되지 않은 경우**, 구매 상품에 대해서 OnBuyDoneEvent 함수가 다시 호출돼요.
* 즉, 구매한 상품들은 SaveUserProductData 함수가 반드시 호출되어야 하고, **호출되지 않으면** 플레이어가 접속할 때마다 **미지급 상품에 대해 OnBuyDoneEvent가 다시 호출돼요.**

```lua
--서버 스크립트에서

----------------------------------------------------------------------------------------
--상품 구매 처리 후
local function BindDoneEvent(player, buyEventData, flag)    
    local productID = buyEventData.ID --구매 시도한 상품의 ID
    local playerID = player:GetPlayerID()

    --구매 성공시
    if flag == 1 then      
        print(player.Name .. " " .. productID .. " 구매 성공!")
        
        -- 상품 번호 1234_1가 Coin 200개 구매 상품일 때
        if productID = "1234_1" then
            --'Coin' 상품에 대한 구매 처리
            local saveKey = "Coin"
            local addValue = 200
			      
            local saveValue = player.Coin + addValue 
	        
            --지급 처리와 함께 유저데이터 저장을 처리해요.
            Marketplace:SaveUserProductData(playerID, saveKey, saveValue, buyEventData)
            player.Coin = saveValue 
			      
            print(player.Name .. " " .. productID .. " 지급 처리 완료!")
        end
    end
end
Marketplace.OnBuyDoneEvent:Connect(BindDoneEvent)

----------------------------------------------------------------------------------------
--'Coin' 재화 불러오기
local function LoadPlayerData(player)
    local playerID = player:GetPlayerID()    
    
    --유저 데이터 불러오기
    local saveKey = "Coin"
    local loadValue = Game:GetSavedUserGameData(playerID, saveKey)    
    
    --불러올 데이터가 없으면
    if loadValue == nil then 
        loadValue = 0
        Game:SaveUserGameData(playerID, saveKey, loadValue) --초기값 저장
    end    
    player.Coin = loadValue
end
Game.OnEnterPlayer:Connect(LoadPlayerData)
```

### 6 미지급건에 대한 확인 방법🔍

구매자가 **미지급건에 대한 문의**를 했을 때, **지급 상태를 확인**하는 방법이에요.

1. **판매 관리** 페이지에서 **판매 내역 조회 항목**을 찾은 다음, 상태를 **미지급**으로 변경하고 검색을 클릭해요.

   <figure><img src="/files/ztoWEDopcrQf0Sfb1oK5" alt=""><figcaption></figcaption></figure>
2. **미지급된 상품**만 따로 확인할 수 있어요.

   <figure><img src="/files/wVk2O8m9vK22on69o9Jc" alt=""><figcaption></figcaption></figure>

### 7 판매 내역 확인💰

1. 월드 관리 페이지 - **판매 관리** 탭에서 판매한 상품의 내역을 확인할 수 있어요.

   <figure><img src="/files/Nd1M3ttdN3HOGmtqV1ZT" alt=""><figcaption></figcaption></figure>
2. 판매 내역은 **CSV 다운로드 버튼**을 눌러 웹이 아닌 환경에서도 확인할 수 있어요.

   <figure><img src="/files/bLyGZVGN1D9vkdvAcvRf" alt=""><figcaption></figcaption></figure>

### 8 샘플맵🥰

상품 목록 가져오기, 상품 지급 처리 등 **인월드 상품과 관련된 기능과 UI 처리**가 모두 구현된 샘플 맵이에요.

[MarketPlace.RMO](https://huy6eprx677.edge.naverncp.com/manualsample/MarketPlace.RMO)

* ServerProductManager 스크립트 : 상품 관련 유저데이터 처리, 지급 함수가 구현되어 있어요.
* ServerMarketplaceSystem 스크립트 : 상품 구매 성공시 지급 함수를 호출해요.
* ClientMarketplaceSystem 스크립트 : 상품 목록을 가져와서 UI에 출력하고, 구매 요청을 처리해요.
* NotiSystem 오브젝트 : 지급 처리된 상품에 대한 알림을 출력해요.
* Test 오브젝트 : 디버깅을 위해 상품 관련 유저데이터 정보를 출력해요.

### 9 판매 수익 정산하기🎉

**판매관리 페이지**에서 **페이아웃 버튼**을 눌러 판매 수익을 정산할 수 있어요.

(판매 수익 정산은 보유 CPR이 있을때만 가능해요!)

<figure><img src="/files/4Rgxo4RiLJzwIh9BSUWE" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ditoland-utplus.gitbook.io/ditoland/manual/inworld-product.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
