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))
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.
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