> For the complete documentation index, see [llms.txt](https://ditoland-utplus.gitbook.io/ditoland/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ditoland-utplus.gitbook.io/ditoland/manual/inworld-product.md).

# 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>
