MarketplaceService Local or not?

Hello, so i’ve been wondering about 2 things:

  1. which parts of a transaction have to be executed locally and which have to be executed on the server. (especially the part arround the receipt)

  2. Are there any good tutorials explaining the entire thing, so that i could adapt it on my own and do my own thing with it

thanks in advance!

1 Like

Prompting the purchase prompt to the player should be done locally.
Everything else should be done on the server or else it could be exploited.

Here’s an example:

This is a sample local script for a button that, when clicked, would prompt a purchase for the player:

local MarketplaceService = game:GetService("MarketplaceService")
local LocalPlayer = game:GetService("Players").Localplayer
local cooldown = false

local productId = 000000 -- Developer Product ID goes here

script.Parent.MouseButton1Click:Connect(function() 

	if cooldown == false then

		cooldown = true

		MarketplaceService:PromptProductPurchase(LocalPlayer, productId)

		task.wait(1.5)

		cooldown = false

	end

end)

The script above is ran on the client.

The part of the transaction where the player receives the award (if they choose to buy it) should be done on the server or else players could exploit the amount of awards they receive and get an infinite amount of rewards.

Below is a sample server script I found in the Developer Hub that shows how the rest of the transaction should be held:

local MarketplaceService = game:GetService("MarketplaceService")
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
 
-- Data store for tracking purchases that were successfully processed
local purchaseHistoryStore = DataStoreService:GetDataStore("PurchaseHistory")
 
-- Table setup containing product IDs and functions for handling purchases
local productFunctions = {}
-- ProductId 123123 for a full heal
productFunctions[123123] = function(receipt, player)
	-- Logic/code for player buying a full heal (may vary)
	if player.Character and player.Character:FindFirstChild("Humanoid") then
		-- Heal the player to full health
		player.Character.Humanoid.Health = player.Character.Humanoid.MaxHealth
		-- Indicate a successful purchase
		return true
	end
end
-- ProductId 456456 for 100 gold
productFunctions[456456] = function(receipt, player)
	-- Logic/code for player buying 100 gold (may vary)
	local stats = player:FindFirstChild("leaderstats")
	local gold = stats and stats:FindFirstChild("Gold")
	if gold then
		gold.Value = gold.Value + 100
		-- Indicate a successful purchase
		return true
	end
end
 
-- The core 'ProcessReceipt' callback function
local function processReceipt(receiptInfo)
 
	-- Determine if the product was already granted by checking the data store  
	local playerProductKey = receiptInfo.PlayerId .. "_" .. receiptInfo.PurchaseId
 
	local success, isPurchaseRecorded = pcall(function()
		return purchaseHistoryStore:UpdateAsync(playerProductKey, function(alreadyPurchased)
			if alreadyPurchased then
				return true
			end
 
			-- Find the player who made the purchase in the server
			local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
			if not player then
				-- The player probably left the game
				-- If they come back, the callback will be called again
				return nil
			end
 
			local handler = productFunctions[receiptInfo.ProductId]
 
			local success, result = pcall(handler, receiptInfo, player)
			-- If granting the product failed, do NOT record the purchase in datastores.
			if not success or not result then
				error("Failed to process a product purchase for ProductId:", receiptInfo.ProductId, " Player:", player)
				return nil
			end
 
			-- Record the transcation in purchaseHistoryStore.
			return true
		end)
	end)
 
	if not success then
		error("Failed to process receipt due to data store error.")
		return Enum.ProductPurchaseDecision.NotProcessedYet
	elseif isPurchaseRecorded == nil then
		-- Didn't update the value in data store.
		return Enum.ProductPurchaseDecision.NotProcessedYet
	else	
		-- IMPORTANT: Tell Roblox that the game successfully handled the purchase
		return Enum.ProductPurchaseDecision.PurchaseGranted
	end
end
 
-- Set the callback; this can only be done once by one script on the server! 
MarketplaceService.ProcessReceipt = processReceipt
5 Likes

one thing i dont really understand:

How does the serversided script actually find out if the player pressed “Buy” and not “Cancel”

The Server handles and automatically detects that for you in the server script above. You won’t have to worry about detecting whether the player receives the award or not. If they choose to press “buy”, and if scripted correctly, the player will automatically receive the award when they press buy. If they press cancel, then nothing happens.

hmm, interesting i’ll look into it

1 Like

If you want more information on this, here’s a really useful article I found on the Developer Hub for you:

just a quick question:

what exactly does the pcall return here

local handler = productFunctions[receiptInfo.ProductId]

		local success, result = pcall(handler, receiptInfo, player)
		-- If granting the product failed, do NOT record the purchase in datastores.
		if not success or not result then
			error("Failed to process a product purchase for ProductId:", receiptInfo.ProductId, " Player:", player)
			return nil
		end

In the example, the function connected to the product id returns true if the function was handled successfully. So, if it was handled correctly, it returns true, but returns nil if not

1 Like

What if i prompt it on the server using a remote from a client?