# MarketplaceService

MarketplaceService : `Instance`

## Overview

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

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

## Properties

## Methods

### GetProductInfo

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

#### Parameters

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

#### Return

| `Dictionary` | <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="../../../manual/monetization/marketplace" %}
[marketplace](https://docs.overdare.com/korean/manual/monetization/marketplace)
{% endcontent-ref %}
