This sample code below has a logic error starting on this line:
local success, isPurchaseRecorded = pcall(function()
The isPurchaseRecorded will always return nil instead of true upon successful granting of the item to the player if the playerProductKey sent is not in the purchaseHistoryStore. Usually, this just means the player would get a double-grant once because then this would trigger the Enum.ProductPurchaseDecision.PurchaseGranted, record the purchase and not be an issue anymore. But because isPurchaseRecorded always returns nil no matter if the product is granted or not, it creates a loop where the player gets granted the products every server visit instead of one time.
This is also causing errors “Transform function error Callbacks cannot yield”
The original documentation sample code did not have this issue of infinite granting products to the player every server visit.
Current Sample Documentations Code:
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")
-- Table setup containing product IDs and functions for handling purchases
local productFunctions = {}
-- ProductId 123123 for a full heal
productFunctions[123123] = function(_receipt, player)
-- Logic for the player buying a full heal
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 for player buying 100 gold
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)
-- Check the data store to determine if the product was already granted
local playerProductKey = receiptInfo.PurchaseId
local purchased = false
local success, result, errorMessage
success, errorMessage = pcall(function()
purchased = purchaseHistoryStore:GetAsync(playerProductKey)
end)
-- If the purchase is recorded, the product was already granted
if success and purchased then
return Enum.ProductPurchaseDecision.PurchaseGranted
elseif not success then
error("Data store error:" .. errorMessage)
end
-- Update the purchase record
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 the player returns, the callback is called again
return nil
end
local handler = productFunctions[receiptInfo.ProductId]
local success, result = pcall(handler, receiptInfo, player)
-- Do not record the purchase if granting the product failed
if not success or not result then
error("Failed to process a product purchase for ProductId: " .. tostring(receiptInfo.ProductId) .. " Player: " .. tostring(player) .. " Error: " .. tostring(result))
return nil
end
-- Record the transaction 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
-- Did not update the value in the 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
If you put in some print statements to follow along in the script, you can see towards the end after the product was granted, it still returned nil and thus triggers:
return Enum.ProductPurchaseDecision.NotProcessedYet instead that starts over again if the player leaves and joins the server again.
Expected behavior
Not grant bought products (such as Developer items) to the player every server visit, but only the one time at purchase.
Page URL: https://create.roblox.com/docs/reference/engine/classes/MarketplaceService#ProcessReceipt