# MarketplaceService

MarketplaceService : `Instance`

## Overview

MarketplaceService는 월드 내에서의 상품 구매 및 결제 성공에 따른 지급 처리를 담당하는 서비스입니다.

크리에이터는 상품이 판매되었을 때 지급이 누락되지 않도록 반드시 ProcessReceipt 콜백 함수를 정의해야 하며, DataStore 등을 활용하여 정상적으로 지급이 완료되도록 처리할 책임이 있습니다.

## Properties

## Methods

### GetProductInfo

상품 ID(productId)와 상품 타입(Enum.InfoType)에 해당하는 상품 정보를 반환합니다.

#### Parameters

| `number` ProductId       | 상품의 ID입니다. |
| ------------------------ | ---------- |
| `Enum.InfoType` InfoType | 상품의 타입입니다. |

#### Return

| `Value` | <p>상품 정보로 구성된 딕셔너리입니다.</p><ul><li><code>string</code> Name: 상품 이름</li><li><code>string</code> Description: 상품 설명</li><li><code>number</code> ProductId: 상품 ID</li><li><code>string</code> ProductType: 상품 종류</li><li><code>number</code> PriceInBLUC: 상품 가격</li><li><code>number</code> Created: 상품이 생성된 시간 (UNIX timestamp)</li><li><code>number</code> Updated: 상품이 수정된 시간 (UNIX timestamp)</li></ul> |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

#### Code Samples

```lua
local MarketplaceService = game:GetService("MarketplaceService")

local function Request_GetProductInfo(productId)
    local success, errorOrProductInfo = pcall(function()
        return MarketplaceService:GetProductInfo(productId, Enum.InfoType.Product)
    end)

    if not success then
        print("Error: " .. errorOrProductInfo .. " / ProductId : " .. productId)

    else
        local productInfo = errorOrProductInfo
        print("World Product Name: " .. tostring(productInfo.Name))
        print("ProductId: " .. tostring(productInfo.ProductId))
        print("ProductType: " .. tostring(productInfo.ProductType))
        print("PriceInBLUC: " .. tostring(productInfo.PriceInBLUC))
        print("Description: " .. tostring(productInfo.Description))
        print("Created: " .. productInfo.Created)
        print("Updated: " .. productInfo.Updated)
    end
end
```

### GetWorldProductsAsync

모든 월드 상품에 대한 정보를 포함하는 Pages 객체를 반환합니다.

#### Parameters

#### Return

| `Pages` | 현재 월드의 모든 월드 상품에 대한 정보를 포함하는 객체입니다. |
| ------- | ----------------------------------- |

#### Code Samples

```lua
local MarketplaceService = game:GetService("MarketplaceService")

local function Request_GetWorldProductsAsync()
    local success, errorOrWorldProducts = pcall(function()
        return MarketplaceService:GetWorldProductsAsync()
    end)

    if not success then
        print("Error: " .. errorOrWorldProducts)

    else
        local worldProducts = errorOrWorldProducts

        local pageCount = 1
        local dataList = {}

        while true do
            local currentPage = worldProducts:GetCurrentPage()

            -- 마지막 페이지이면 루프 탈출
            if worldProducts.IsFinished or currentPage == nil then
                print(pageCount .. " page IsFinished : " .. tostring(worldProducts.IsFinished))
                break
            else
                worldProducts:AdvanceToNextPageAsync()
                pageCount = pageCount + 1
            end

            -- 한 페이지에 최대 100개의 상품 정보 구성
            for _, productInfo in pairs(currentPage) do
                local i = #dataList + 1

                print("------ " .. i .. " ------")
                print("World Product Name: " .. tostring(productInfo.Name))
                print("ProductId: " .. tostring(productInfo.ProductId))

                print("ProductType: " .. tostring(productInfo.ProductType))
                print("PriceInBLUC: " .. tostring(productInfo.PriceInBLUC))
                print("Description: " .. tostring(productInfo.Description))
                print("Created: " .. productInfo.Created)
                print("Updated: " .. productInfo.Updated)

                table.insert(dataList, productInfo)
            end
        end
    end
end
```

### PromptProductPurchase

월드 상품 ID(productId)에 해당하는 상품의 구매를 요청합니다. (시스템 UI로 구매창이 출력됩니다.)

#### Parameters

| `Player` Player    | 상품을 구매할 Player입니다. |
| ------------------ | ------------------ |
| `number` ProductId | 월드의 상품 Id입니다.      |

#### Return

| `void` |   |
| ------ | - |

#### Code Samples

```lua
local MarketplaceService = game:GetService("MarketplaceService")

local function Request_PromptProductPurchase(player, productId)
    local success, error = pcall(function()
        MarketplaceService:PromptProductPurchase(player, productId)
    end)

    if not success then
        print("Error: " .. error .. " / ProductId : " .. productId)
    end
end
```

## Events

### PromptProductPurchaseFinished

구매 요청(PromptProductPurchase)을 통해 출력된 구매창이 꺼질 때 이벤트가 호출되며, 구매를 성공하면 isPurchased에 true가 전달되고, 구매를 취소하거나 실패하면 false가 전달됩니다.

이 이벤트는 구매 창을 닫았는지 감지하기 위한 용도로만 사용해야 하며, <mark style="color:red;">**구매한 상품에 대한 지급 처리 용도로는 절대 사용하지 않아야 합니다.**</mark>

#### Parameters

| `string` UserId     | 구매 요청한 Player의 UserId입니다. |
| ------------------- | ------------------------- |
| `number` ProductId  | 구매 요청한 상품의 Id입니다.         |
| `bool` bIsPurchased | 구매 성공 여부입니다.              |

#### Code Samples

```lua
local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")

local function OnPromptPurchaseFinished(userId, productId, isPurchased)
    local player = Players:GetPlayerByUserId(userId)

    print(player.Name .. " / ProductID : " .. productId .. " / isPurchased : " .. tostring(isPurchased))
end
MarketplaceService.PromptProductPurchaseFinished:Connect(OnPromptPurchaseFinished)
```

## Callback

### ProcessReceipt

구매 성공한 상품 중에서 아직 **지급 처리가 되지 않은 영수증** 정보를 반환하는 이벤트가 호출됩니다.

**호출 조건**

* 월드 상품을 성공적으로 구매했을 때(구매 성공 팝업이 사용자에게 표시되었을 때)
  * 미처리 상품이 있는 상태에서 새로운 상품 구매시, **이전 미처리건도 함께 호출**됩니다.
* 사용자가 서버에 **접속(재접속)**&#xD588;을 때

**지급 상태 변경 방법**

* 상품 지급 처리 후에, Enum.ProductPurchaseDecision.**PurchaseGranted**를 반환합니다.

**주의사항**

* ProcessReceipt 이벤트 연결은 **서버측 Script에서 한 번만** 설정해야 합니다.
* 이 콜백은 시간 제한 없이 **yield 가능**하며, 서버가 실행 중인 한 응답이 돌아올 때까지 유효합니다.
* 미지급된 영수증이 여러개인 경우 **각각 호출**되며, 콜백 호출 순서는 비결정적(non-deterministic)입니다.
* 사용자가 **서버에 있어야** 콜백이 호출됩니다.
  * 단, 콜백의 결과는 사용자가 서버에 없어도 백엔드에 기록될 수 있습니다.
* 콜백에서 **PurchaseGranted**를 반환해도 백엔드 기록이 실패할 수 있으며, 이런 경우 영수증의 상태는 변경되지 않습니다. (미지급 상태 유지)
* **미지급 상태**의 상품은 자금이 **지급 보류 상태(Escrow)**&#xB85C; 보관됩니다.

#### Parameters

| `Dictionary` Receipt | <p>구매 성공한 상품의 영수증 정보로 구성된 딕셔너리입니다.</p><ul><li><code>string</code> PurchaseId: 영수증 Id</li><li><code>string</code> PlayerId: Player의 UserId</li><li><code>number</code> ProductId: 상품 Id</li><li><code>number</code> CurrencySpent: 거래에 사용된 화폐의 양</li><li><code>number</code> PurchaseDateTime: 상품을 결제한시간 (UNIX timestamp)</li></ul> |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

#### Return

| `Enum.ProductPurchaseDecision` | <p>월드 상품의 지급 상태입니다.</p><ul><li>PurchaseGranted: 상품이 Player에게 성공적으로 지급된 상태</li><li>NotProcessedYet: 상품이 지급되지 않은 상태</li></ul> |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |

#### Code Samples

<pre class="language-lua"><code class="lang-lua">local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")

local ProductDeliverer = {}

-----------------------------------------------------------------------------
-- 아래와 같이 테이블에 상품 번호 별로 함수를 구성하여 상품마다 지급 로직을 구현할 수 있습니다.
ProductDeliverer[Enter the product number here.] = function(player)
    local success, resultOrError = pcall(function()
        -- player에게 상품 지급 및 DataStore를 이용한 저장 처리 
        
        -- Tip.
        -- DataStore로 상품 정보를 저장할 때는 
        -- 네트워크 충돌이나 경쟁 상태(race condition)를 방지하기 위해
        -- IncrementAsync 또는 UpdateAsync로 처리하는 것을 권장합니다.
                
        -- 지급 및 저장 처리가 성공적으로 완료된 경우 true 반환
        return true
    end)
    
    if success and resultOrError then
        return true
        
    else
        return false, resultOrError
    end
end

-----------------------------------------------------------------------------
-- 상품 구매 성공시 호출되는 영수증 처리용 콜백
local function OnProcessReceipt(receiptInfo)	
    -- Receipt information
    local success, error = pcall(function()	
        print("PurchaseId: " .. receiptInfo.PurchaseId)
        print("UserId: " .. receiptInfo.PlayerId)
        print("ProductId: " .. receiptInfo.ProductId)
        print("CurrencySpent: " .. receiptInfo.CurrencySpent)
        print("PurchaseDateTime: " .. receiptInfo.PurchaseDateTime)
    end)
    
    if not success then
        print("Error: " .. tostring(error))
        return Enum.ProductPurchaseDecision.NotProcessedYet
    end
    
<strong>    -- 플레이어가 유효하면 
</strong>    local productId = receiptInfo.ProductId        
    local userId = receiptInfo.PlayerId
    
    local player = Players:GetPlayerByUserId(userId)  
    if player == nil then
        print("Error: player is nil")
        return Enum.ProductPurchaseDecision.NotProcessedYet	
    end  
    
    -- 상품 지급 함수 호출
    local delivererFunc = ProductDeliverer[productId]
    local success, error = delivererFunc(player)
    
    -- 상품 지급 성공시
    if success then
        -- 지급 완료 상태 반환
        print("Item delivery successful / ProductId : " .. productId)
        return Enum.ProductPurchaseDecision.PurchaseGranted
        
    -- 상품 지급 실패시
    else
        print("Error: " .. tostring(error))
        return Enum.ProductPurchaseDecision.NotProcessedYet
    end
end
MarketplaceService.ProcessReceipt = OnProcessReceipt
</code></pre>

## See also

{% content-ref url="/pages/IhO1fNmVOpWXNlpj9Sy7" %}
[월드 상품 판매](/korean/manual/monetization/marketplace.md)
{% endcontent-ref %}


---

# 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://docs.overdare.com/korean/development/api-reference/classes/marketplaceservice.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.
