MarketplaceService
MarketplaceService is responsible for in-experience transactions. The most notable methods are PromptProductPurchase and PromptPurchase, as well as the callback ProcessReceipt which must be defined so that developer product transactions do not fail.
MarketplaceService also has methods that fetch information about developer products (GetProductInfo and GetDeveloperProductsAsync), passes (UserOwnsGamePassAsync), and other assets (PlayerOwnsAsset, PlayerOwnsBundle).
Understanding MarketplaceService is the first step towards learning to monetize an experience on Roblox, as well as learning to use DataStoreService, which is responsible for saving and loading all data related to purchases.
Summary
Methods
Returns a Pages object which contains information for all of the current experience's developer products.
Returns the product information of an asset using its asset ID.
Returns the product information of a subscription for the given subscriptionId.
Returns a table that contains the details of the user's subscription for a given subscriptionId.
Returns an Array that contains up to one year of the user's subscription payment history for the given subscriptionId.
Returns a table that contains the subscription status of the user for the given subscriptionId.
Returns whether the given user has the given asset.
Returns whether the given player owns the given bundle.
Prompts a user to purchase multiple avatar items with the given assetId or bundleId.
Prompts a user to purchase a bundle with the given bundleId.
Prompts a user to cancel a subscription for the given subscriptionId.
Prompts a user to purchase a pass with the given gamePassId.
Prompts a user to purchase Roblox Premium.
- PromptProductPurchase(player : Instance,productId : number,equipIfPurchased : boolean,currencyType : Enum.CurrencyType):()
Prompts a user to purchase a developer product with the given productId.
- PromptPurchase(player : Instance,assetId : number,equipIfPurchased : boolean,currencyType : Enum.CurrencyType):()
Prompts a user to purchase an item with the given assetId. Does not work for USD Creator Store purchases.
Prompts a user to purchase a subscription for the given subscriptionId.
Returns true if the player with the given UserId owns the pass with the given gamePassId.
Events
- PromptBulkPurchaseFinished(player : Instance,status : Enum.MarketplaceBulkPurchasePromptStatus,results : Dictionary):RBXScriptSignal
Fires when a purchase prompt for bulk avatar items is closed.
- PromptBundlePurchaseFinished(player : Instance,bundleId : number,wasPurchased : boolean):RBXScriptSignal
- PromptGamePassPurchaseFinished(player : Instance,gamePassId : number,wasPurchased : boolean):RBXScriptSignal
Fires when a purchase prompt for a pass is closed.
Fires when a purchase prompt for Roblox Premium is closed.
- PromptProductPurchaseFinished(userId : number,productId : number,isPurchased : boolean):RBXScriptSignal
Fires when a purchase prompt for a developer product is closed. Do not use this event to process purchases.
Fires when a purchase prompt for an affiliate gear sale or other asset is closed. Does not fire for developer product or pass prompts.
- PromptSubscriptionPurchaseFinished(user : Player,subscriptionId : string,didTryPurchasing : boolean):RBXScriptSignal
Fires when a purchase prompt for a subscription is closed.
Callbacks
A callback to process receipts of developer product purchases.
Properties
Methods
GetDeveloperProductsAsync
Returns
Code Samples
local MarketplaceService = game:GetService("MarketplaceService")
local developerProducts = MarketplaceService:GetDeveloperProductsAsync():GetCurrentPage()
for _, developerProduct in pairs(developerProducts) do
for field, value in pairs(developerProduct) do
print(field .. ": " .. value)
end
print(" ")
end
GetProductInfo
Parameters
Returns
Code Samples
local MarketplaceService = game:GetService("MarketplaceService")
local ASSET_ID = 125378389
local asset = MarketplaceService:GetProductInfo(ASSET_ID)
print(asset.Name .. " :: " .. asset.Description)
GetUserSubscriptionDetailsAsync
Parameters
Returns
GetUserSubscriptionPaymentHistoryAsync
Parameters
Returns
Code Samples
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local SUBSCRIPTION_ID = "EXP-0"
local function checkSubscriptionHistory(player: Player)
local subscriptionHistory = {}
local success, err = pcall(function()
subscriptionHistory = MarketplaceService:GetUserSubscriptionPaymentHistoryAsync(player, SUBSCRIPTION_ID)
end)
if not success then
warn(`Error while checking subscription history: {err}`)
return
end
if next(subscriptionHistory) then
-- User has some subscription history within the past 12 months.
-- Print the details of each payment entry from the subscription history.
print(`Player {player.Name} has subscribed to {SUBSCRIPTION_ID} before:`)
for entryNum, paymentEntry in subscriptionHistory do
local paymentStatus = tostring(paymentEntry.PaymentStatus)
local cycleStartTime = paymentEntry.CycleStartTime:FormatLocalTime("LLL", "en-us")
local cycleEndTime = paymentEntry.CycleEndTime:FormatLocalTime("LLL", "en-us")
print(`{entryNum}: {paymentStatus} ({cycleStartTime} - {cycleEndTime})`)
end
else
print(`Player {player.Name} has never subscribed to {SUBSCRIPTION_ID} before.`)
end
end
-- Call checkSubscriptionHistory for any players already in the game
for _, player in ipairs(Players:GetPlayers()) do
checkSubscriptionHistory(player)
end
-- Call checkSubscriptionHistory for all future players
Players.PlayerAdded:Connect(checkSubscriptionHistory)
GetUserSubscriptionStatusAsync
Parameters
Returns
Code Samples
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local subscriptionID = "EXP-00000"
local function checkSubStatus(player)
local subStatus = {}
local success, message = pcall(function()
-- returns IsRenewing and IsSubscribed
subStatus = MarketplaceService:GetUserSubscriptionStatusAsync(player, subscriptionID)
end)
if not success then
warn("Error while checking if player has subscription: " .. tostring(message))
return
end
if subStatus["IsSubscribed"] then
print(player.Name .. " is subscribed with " .. subscriptionID)
-- Give player permissions associated with the subscription
end
end
Players.PlayerAdded:Connect(checkSubStatus)
PlayerOwnsAsset
Parameters
Returns
Code Samples
local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")
-- The item we're checking for: https://www.roblox.com/catalog/30331986/Midnight-Shades
local ASSET_ID = 30331986
local ASSET_NAME = "Midnight Shades"
local function onPlayerAdded(player)
local success, doesPlayerOwnAsset = pcall(MarketplaceService.PlayerOwnsAsset, MarketplaceService, player, ASSET_ID)
if not success then
local errorMessage = doesPlayerOwnAsset
warn(`Error checking if {player.Name} owns {ASSET_NAME}: {errorMessage}`)
return
end
if doesPlayerOwnAsset then
print(`{player.Name} owns {ASSET_NAME}`)
else
print(`{player.Name} doesn't own {ASSET_NAME}`)
end
end
Players.PlayerAdded:Connect(onPlayerAdded)
PlayerOwnsBundle
Parameters
Returns
Code Samples
local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")
-- The bundle we're checking for: https://www.roblox.com/bundles/589/Junkbot
local BUNDLE_ID = 589
local BUNDLE_NAME = "Junkbot"
Players.PlayerAdded:Connect(function(player)
local success, doesPlayerOwnBundle = pcall(function()
return MarketplaceService:PlayerOwnsBundle(player, BUNDLE_ID)
end)
if success == false then
print("PlayerOwnsBundle call failed: ", doesPlayerOwnBundle)
return
end
if doesPlayerOwnBundle then
print(player.Name .. " owns " .. BUNDLE_NAME)
else
print(player.Name .. " doesn't own " .. BUNDLE_NAME)
end
end)
PromptBulkPurchase
Parameters
Returns
Code Samples
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local promptBulkPurchaseEvent = ReplicatedStorage:WaitForChild("PromptBulkPurchaseEvent")
local part = Instance.new("Part")
part.Parent = workspace
local clickDetector = Instance.new("ClickDetector")
clickDetector.Parent = part
clickDetector.MouseClick:Connect(function()
promptBulkPurchaseEvent:FireServer({
{ Type = Enum.MarketplaceProductType.AvatarAsset, Id = "16630147" },
{ Type = Enum.MarketplaceProductType.AvatarBundle, Id = "182" },
})
end)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MarketplaceService = game:GetService("MarketplaceService")
local promptBulkPurchaseEvent = Instance.new("RemoteEvent")
promptBulkPurchaseEvent.Name = "PromptBulkPurchaseEvent"
promptBulkPurchaseEvent.Parent = ReplicatedStorage
--Listen for the RemoteEvent to fire from a Client and then trigger the bulk purchase prompt
promptBulkPurchaseEvent.OnServerEvent:Connect(function(player, items)
MarketplaceService:PromptBulkPurchase(player, items, {})
end)
PromptPremiumPurchase
Parameters
Returns
Code Samples
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local teleporter = script.Parent
local showModal = true
local TELEPORT_POSITION = Vector3.new(1200, 200, 60)
-- Teleport character to exclusive area
local function teleportPlayer(player)
-- Request streaming around target location
player:RequestStreamAroundAsync(TELEPORT_POSITION)
-- Teleport character
local character = player.Character
if character and character.Parent then
local currentPivot = character:GetPivot()
character:PivotTo(currentPivot * CFrame.new(TELEPORT_POSITION))
end
end
-- Detect character parts touching teleporter
teleporter.Touched:Connect(function(otherPart)
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if not player then
return
end
if not player:GetAttribute("CharacterPartsTouching") then
player:SetAttribute("CharacterPartsTouching", 0)
end
player:SetAttribute("CharacterPartsTouching", player:GetAttribute("CharacterPartsTouching") + 1)
if player.MembershipType == Enum.MembershipType.Premium then
-- User has Premium; teleport character to exclusive area within experience
teleportPlayer(player)
else
-- Show purchase modal, using debounce to show once every few seconds at most
if not showModal then
return
end
showModal = false
task.delay(5, function()
showModal = true
end)
MarketplaceService:PromptPremiumPurchase(player)
end
end)
-- Detect character parts exiting teleporter
teleporter.TouchEnded:Connect(function(otherPart)
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if player and player:GetAttribute("CharacterPartsTouching") then
player:SetAttribute("CharacterPartsTouching", player:GetAttribute("CharacterPartsTouching") - 1)
end
end)
-- Handle membership changed event
Players.PlayerMembershipChanged:Connect(function(player)
warn("User membership changed; new membership is " .. tostring(player.MembershipType))
-- Teleport character if membership type is Premium and character is on teleporter
if player.MembershipType == Enum.MembershipType.Premium and player:GetAttribute("CharacterPartsTouching") > 0 then
teleportPlayer(player)
end
end)
PromptProductPurchase
Parameters
Returns
Code Samples
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local productId = 0000000 -- Change this to your developer product ID
-- Function to prompt purchase of the developer product
local function promptPurchase()
MarketplaceService:PromptProductPurchase(player, productId)
end
promptPurchase()
PromptPurchase
Parameters
Returns
Code Samples
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local promptPurchaseEvent = ReplicatedStorage:WaitForChild("PromptPurchaseEvent")
local part = Instance.new("Part")
part.Parent = workspace
local clickDetector = Instance.new("ClickDetector")
clickDetector.Parent = part
clickDetector.MouseClick:Connect(function()
promptPurchaseEvent:FireServer(16630147)
end)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MarketplaceService = game:GetService("MarketplaceService")
local promptPurchaseEvent = Instance.new("RemoteEvent")
promptPurchaseEvent.Name = "PromptPurchaseEvent"
promptPurchaseEvent.Parent = ReplicatedStorage
-- Listen for the RemoteEvent to fire from a Client and then trigger the purchase prompt
promptPurchaseEvent.OnServerEvent:Connect(function(player, id)
MarketplaceService:PromptPurchase(player, id)
end)
Events
PromptBulkPurchaseFinished
Parameters
PromptGamePassPurchaseFinished
Parameters
Code Samples
local MarketplaceService = game:GetService("MarketplaceService")
local function gamepassPurchaseFinished(...)
-- Print all the details of the prompt, for example:
-- PromptGamePassPurchaseFinished PlayerName 123456 false
print("PromptGamePassPurchaseFinished", ...)
end
MarketplaceService.PromptGamePassPurchaseFinished:Connect(gamepassPurchaseFinished)
PromptPremiumPurchaseFinished
PromptPurchaseFinished
Parameters
Code Samples
local MarketplaceService = game:GetService("MarketplaceService")
local function onPromptPurchaseFinished(player, assetId, isPurchased)
if isPurchased then
print(player.Name, "bought an item with AssetID:", assetId)
else
print(player.Name, "didn't buy an item with AssetID:", assetId)
end
end
MarketplaceService.PromptPurchaseFinished:Connect(onPromptPurchaseFinished)
Callbacks
ProcessReceipt
Parameters
Returns
Code Samples
local MarketplaceService = game:GetService("MarketplaceService")
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
-- Data store setup for tracking purchases that were successfully processed
local purchaseHistoryStore = DataStoreService:GetDataStore("PurchaseHistory")
local productIdByName = {
fullHeal = 123123,
gold100 = 456456,
}
-- A dictionary to look up the handler function to grant a purchase corresponding to a product ID
-- These functions return true if the purchase was granted successfully
-- These functions must never yield since they're called later within an UpdateAsync() callback
local grantPurchaseHandlerByProductId = {
[productIdByName.fullHeal] = function(_receipt, player)
local character = player.Character
local humanoid = character and character:FindFirstChild("Humanoid")
-- Ensure the player has a humanoid to heal
if not humanoid then
return false
end
-- Heal the player to full Health
humanoid.Health = humanoid.MaxHealth
-- Indicate a successful grant
return true
end,
[productIdByName.gold100] = function(_receipt, player)
local leaderstats = player:FindFirstChild("leaderstats")
local goldStat = leaderstats and leaderstats:FindFirstChild("Gold")
if not goldStat then
return false
end
-- Add 100 gold to the player's gold stat
goldStat.Value += 100
-- Indicate a successful grant
return true
end,
}
-- The core ProcessReceipt callback function
-- This implementation handles most failure scenarios but does not completely mitigate cross-server data failure scenarios
local function processReceipt(receiptInfo)
local success, result = pcall(
purchaseHistoryStore.UpdateAsync,
purchaseHistoryStore,
receiptInfo.PurchaseId,
function(isPurchased)
if isPurchased then
-- This purchase was already recorded as granted, so it must have previously been handled
-- Avoid calling the grant purchase handler here to prevent granting the purchase twice
-- While the value in the data store is already true, true is returned again so that the pcall result variable is also true
-- This will later be used to return PurchaseGranted from the receipt processor
return true
end
local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
if not player then
-- Avoids granting the purchase if the player is not in the server
-- When they rejoin, this receipt processor will be called again
return nil
end
local grantPurchaseHandler = grantPurchaseHandlerByProductId[receiptInfo.ProductId]
if not grantPurchaseHandler then
-- If there's no handler defined for this product ID, the purchase cannot be processed
-- This will never happen as long as a handler is set for every product ID sold in the experience
warn(`No purchase handler defined for product ID '{receiptInfo.ProductId}'`)
return nil
end
local handlerSucceeded, handlerResult = pcall(grantPurchaseHandler, receiptInfo, player)
if not handlerSucceeded then
local errorMessage = handlerResult
warn(
`Grant purchase handler errored while processing purchase from '{player.Name}' of product ID '{receiptInfo.ProductId}': {errorMessage}`
)
return nil
end
local didHandlerGrantPurchase = handlerResult == true
if not didHandlerGrantPurchase then
-- The handler did not grant the purchase, so record it as not granted
return nil
end
-- The purchase is now granted to the player, so record it as granted
-- This will later be used to return PurchaseGranted from the receipt processor
return true
end
)
if not success then
local errorMessage = result
warn(`Failed to process receipt due to data store error: {errorMessage}`)
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local didGrantPurchase = result == true
return if didGrantPurchase
then Enum.ProductPurchaseDecision.PurchaseGranted
else Enum.ProductPurchaseDecision.NotProcessedYet
end
-- Set the callback; this can only be done once by one script on the server
MarketplaceService.ProcessReceipt = processReceipt