MarketplaceService ProcessReceipt code sample robs players of money

Issue Description

The error() call on line 71 has too many parameters. It is using error() the same way you would use print() or warn() where you can pass as many parameters as you want and they will all be added to the message. error() only takes two parameters and so this causes the code to break, which causes the PurchaseHistoryStore to not be updated, but the player’s money was still taken.

The problematic line:

error("Failed to process a product purchase for ProductId:", receiptInfo.ProductId, " Player:", player)

Corrected:

error("Failed to process a product purchase for ProductId: ".. tostring(receiptInfo.ProductId).. " Player: ".. tostring(player))

This line is also a lot less useful than it could be, as it does not show any details about the error it caught. The following would be more ideal:

error("Failed to process a product purchase for ProductId: ".. tostring(receiptInfo.ProductId).. " Player: ".. tostring(player).. " Error: " .. tostring(result))

Issue Area: Documentation Content
Page URL: MarketplaceService | Roblox Creator Documentation

5 Likes

I had the exact same issue a while ago luckily I got around to the problem and fixed it before anyone lost anything but still.

Woah. I didn’t notice that, since I used the callback example from the old developer.roblox.com documention.

This should be fixed asap.

Good catch, Noobot9k! I hope this will be resolved soon. It is likely that a number of users would have used the available sample code from the official documentation.

Because it errors, shouldn’t success be set to nil, causing

if not success then
	error("Failed to process receipt due to data store error.")
	return Enum.ProductPurchaseDecision.NotProcessedYet

(why is there an error call here)

to run, and therefore not returning PurchaseGranted, which leads to the purchase not actually finalizing?

[…] this callback is called multiple times until it returns ProductPurchaseDecision.PurchaseGranted

My problem was that it did not try again after failing. Maybe because it was a Studio test purchase?

I believe your line suggestion has been implemented. I just checked the link, and the line you suggestion is exactly what is now shown on the documentation:

local MarketplaceService = game:GetService("MarketplaceService")
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")


local purchaseHistoryStore = DataStoreService:GetDataStore("PurchaseHistory")


local productFunctions = {}

productFunctions[123123] = function(_receipt, player)
	
	if player.Character and player.Character:FindFirstChild("Humanoid") then
		
		player.Character.Humanoid.Health = player.Character.Humanoid.MaxHealth
		
		return true
	end
end

productFunctions[456456] = function(_receipt, player)
	
	local stats = player:FindFirstChild("leaderstats")
	local gold = stats and stats:FindFirstChild("Gold")
	if gold then
		gold.Value = gold.Value + 100
		
		return true
	end
end

-- The core 'ProcessReceipt' callback function
local function processReceipt(receiptInfo)
	
	local playerProductKey = receiptInfo.PlayerId .. "_" .. receiptInfo.PurchaseId
	local purchased = false
	local success, result, errorMessage

	success, errorMessage = pcall(function()
		purchased = purchaseHistoryStore:GetAsync(playerProductKey)
	end)
	
	if success and purchased then
		return Enum.ProductPurchaseDecision.PurchaseGranted
	elseif not success then
		error("Data store error:" .. errorMessage)
	end

	  
	local playerProductKey = receiptInfo.PlayerId .. "_" .. receiptInfo.PurchaseId

	local success, isPurchaseRecorded = pcall(function()
		return purchaseHistoryStore:UpdateAsync(playerProductKey, function(alreadyPurchased)
			if alreadyPurchased then
				return true
			end

			
			local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
			if not player then
				
				return nil
			end

			local handler = productFunctions[receiptInfo.ProductId]

			local success, result = pcall(handler, receiptInfo, player)

			if not success or not result then -- THIS RIGHT HERE
				error("Failed to process a product purchase for ProductId: " .. tostring(receiptInfo.ProductId) .. " Player: " .. tostring(player) .. " Error: " .. tostring(result))
				return nil
			end

			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
		
		return Enum.ProductPurchaseDecision.NotProcessedYet
	else	
		
		return Enum.ProductPurchaseDecision.PurchaseGranted
	end
end

-- Set the callback; this can only be done once by one script on the server!
MarketplaceService.ProcessReceipt = processReceipt

1 Like

Thanks for the report! The line was replaced with you suggestion. Please let us know if you encounter any more discrepancies.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.