Logic Error in the ProcessReceipt Example Code will cause player to get the same paid items with every server join after purchase

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. :wink:

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