Getting Dev Product Error (Transform function error Callbacks cannot yield)

Every time I buy a dev product unless it’s Plus Wins, it gives me this error: “Transform function error: Callbacks cannot yield.” Then, when I buy another product that’s Plus Wins, it gives me the error again. What did I do wrong?

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 1634262175 for Kill Everyone
productFunctions[1634262175] = function(_receipt, player)
	game.ReplicatedStorage.Events.DevProductPurchase:FireClient(player)
	game.ReplicatedStorage.Events.KillEveryoneMessageEvent:FireAllClients({Text = player.DisplayName .. " (@" .. player.Name ..")" .. " has bought Kill Everyone", Color = Color3.new(1, 0, 0), Font = Enum.Font.SourceSansBold, FontSize = Enum.FontSize.Size24})
	for i,v in pairs(game.Players:GetPlayers()) do
		if (v.UserId ~= player.UserId and v.Character and v.Character:FindFirstChild("Humanoid")) then
			v.Character:FindFirstChild("Humanoid").Health = 0
		end
	end
	return true
end
-- ProductId 1634262146 for Reset Winners
productFunctions[1634262146] = function(_receipt, player)
	game.ReplicatedStorage.Events.DevProductPurchase:FireClient(player)
	game.ReplicatedStorage.Events.ResetWinnersMessageEvent:FireAllClients({Text = player.DisplayName .. " (@" .. player.Name ..")" .. " has bought Reset Winners", Color = Color3.new(0, 0.682353, 1), Font = Enum.Font.SourceSansBold, FontSize = Enum.FontSize.Size24})
	game.Workspace.End.AllowTeamChangeOnTouch = false
	for i,v in pairs(game.Players:GetPlayers()) do
		if (v.Character and v.Character:FindFirstChild("Humanoid")) then
			if v.Team.Name == "Winners" then
				v.Character:FindFirstChild("Humanoid").Health = 0
				for i = 20, 0, -1 do
					v.Team = game.Teams:FindFirstChild("Climbers")
					wait(0.1)
				end
				game.ReplicatedStorage.ResetConfettiEvent:FireClient(v)
				v.debounces.CanWin.Value = true
			end
		end
	end
	game.Workspace.End.AllowTeamChangeOnTouch = true
	return true
end
-- ProductId 1634262176 for Skip To End
productFunctions[1634262176] = function(_receipt, player)
	game.ReplicatedStorage.Events.DevProductPurchase:FireClient(player)
	game.ReplicatedStorage.Events.SkipToEndMessageEvent:FireAllClients({Text = player.DisplayName .. " (@" .. player.Name ..")" .. " has bought Skip To End", Color = Color3.new(1, 0.588235, 0), Font = Enum.Font.SourceSansBold, FontSize = Enum.FontSize.Size24})
	if player.Character and player.Character:FindFirstChild("Humanoid") then
			player.Character:FindFirstChild("Humanoid").Health = 0
			for i = 20, 0, -1 do
				player.Team = game.Teams:FindFirstChild("Winners")
				wait(0.1)
			end
		end
	return true
end

-- ProductId 1635129013 for 1 Win
productFunctions[1635129013] = function(_receipt, player)
	game.ReplicatedStorage.Events.DevProductPurchase:FireClient(player)
	local stats = player:FindFirstChild("leaderstats")
	local Wins = stats and stats:FindFirstChild("Wins")
	if Wins then
		Wins.Value = Wins.Value + 1
		-- Indicate a successful purchase
		return true
	end
end

-- ProductId 1635129010 for 5 Win
productFunctions[1635129010] = function(_receipt, player)
	game.ReplicatedStorage.Events.DevProductPurchase:FireClient(player)
	local stats = player:FindFirstChild("leaderstats")
	local Wins = stats and stats:FindFirstChild("Wins")
	if Wins then
		Wins.Value = Wins.Value + 5
		-- Indicate a successful purchase
		return true
	end
end

-- ProductId 1635129009 for 10 Win
productFunctions[1635129009] = function(_receipt, player)
	game.ReplicatedStorage.Events.DevProductPurchase:FireClient(player)
	local stats = player:FindFirstChild("leaderstats")
	local Wins = stats and stats:FindFirstChild("Wins")
	if Wins then
		Wins.Value = Wins.Value + 10
		-- Indicate a successful purchase
		return true
	end
end

-- ProductId 1635129011 for 25 Win
productFunctions[1635129011] = function(_receipt, player)
	game.ReplicatedStorage.Events.DevProductPurchase:FireClient(player)
	local stats = player:FindFirstChild("leaderstats")
	local Wins = stats and stats:FindFirstChild("Wins")
	if Wins then
		Wins.Value = Wins.Value + 25
		-- Indicate a successful purchase
		return true
	end
end

-- ProductId 1635129008 for 50 Win
productFunctions[1635129008] = function(_receipt, player)
	game.ReplicatedStorage.Events.DevProductPurchase:FireClient(player)
	local stats = player:FindFirstChild("leaderstats")
	local Wins = stats and stats:FindFirstChild("Wins")
	if Wins then
		Wins.Value = Wins.Value + 50
		-- Indicate a successful purchase
		return true
	end
end




-- ProductId 1635129581 for +3 RoundsUntilReset
productFunctions[1635129581] = function(_receipt, player)
	game.ReplicatedStorage.Events.DevProductPurchase:FireClient(player)
	game.ReplicatedStorage.Events.Rounds3MessageEvent:FireAllClients({Text = player.DisplayName .. " (@" .. player.Name ..")" .. " has bought +3 Rounds", Color = Color3.new(0.0901961, 0.898039, 0), Font = Enum.Font.SourceSansBold, FontSize = Enum.FontSize.Size24})
	local ReplicatedStorage = game.ReplicatedStorage
	local RoundsUntilReset = ReplicatedStorage.RoundsUntilReset
	if RoundsUntilReset then
		RoundsUntilReset.Value = RoundsUntilReset.Value + 3
		-- Indicate a successful purchase
		return true
	end
end

-- ProductId 1635129581 for +10 RoundsUntilReset
productFunctions[1635129583] = function(_receipt, player)
	game.ReplicatedStorage.Events.DevProductPurchase:FireClient(player)
	game.ReplicatedStorage.Events.Rounds3MessageEvent:FireAllClients({Text = player.DisplayName .. " (@" .. player.Name ..")" .. " has bought +10 Rounds", Color = Color3.new(0.0901961, 0.898039, 0), Font = Enum.Font.SourceSansBold, FontSize = Enum.FontSize.Size24})
	local ReplicatedStorage = game.ReplicatedStorage
	local RoundsUntilReset = ReplicatedStorage.RoundsUntilReset
	if RoundsUntilReset then
		RoundsUntilReset.Value = RoundsUntilReset.Value + 10
		-- 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 purchased = false
	local success, result, errorMessage

	success, errorMessage = pcall(function()
		purchased = purchaseHistoryStore:GetAsync(playerProductKey)
	end)
	-- If purchase was 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

	-- 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: " .. tostring(receiptInfo.ProductId) .. " Player: " .. tostring(player) .. " Error: " .. tostring(result))
				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

Products are not designed to yield. You have wait() functions inside, which cause it to yield.

Think of it this way, you want transactional code to happen as fast as possible, so you shouldn’t be running any code other than to check if the product goes through or not.

If you want something to happen after a transaction, it needs to run on a separate thread. In your case you need to listen to an event under MarketplaceService called PromptProductPurchaseFinished.

Take a look at the parameters of that event, fit your code to what it says, and you shouldn’t run into that issue anymore.

1 Like

So if I remove the wait() functions, it should work?