Process receipt stopped working (12am)

My PROCESS RECEIPT just suddenly stopped working and I need this to work in order to continue to fund this game, it was working perfectly fine a few hours ago.

-- SAFE banana peel (runs outside ProcessReceipt)
local function bananaPeel()
	for _, v in pairs(workspace:GetChildren()) do
		if v:IsA("Model") and v:FindFirstChildOfClass("Humanoid") then
			if v:FindFirstChild("LoopJump") then
				task.spawn(function()
					local hrp = v:FindFirstChild("HumanoidRootPart")
					if not hrp then return end

					local slipSound = Instance.new("Sound", hrp)
					slipSound.SoundId = "rbxassetid://129432532096499"

					local landSound = Instance.new("Sound", hrp)
					landSound.SoundId = "rbxassetid://113557077269331"

					slipSound:Play()
					v.Humanoid.Jump = true
					task.wait(.5)
					v.Humanoid.Sit = true
					landSound:Play()

					landSound.Ended:Connect(function()
						slipSound:Destroy()
						landSound:Destroy()
					end)
				end)
			end
		end
	end
end

-- NON-YIELDING HANDLERS
local grantPurchaseHandlerByProductId = {
	[productIdByName.JUMPSCARE_ID] = function(_receipt, player)
		JumpscareEvent:FireClient(player)

		local reduce = player:FindFirstChild("ReducePrompts")
		if reduce then reduce.Value = true end

		return true
	end,

	[productIdByName.CONCERT_ID] = function(_receipt, player)
		concertEvent:FireClient(player)

		local reduce = player:FindFirstChild("ReducePrompts")
		if reduce then reduce.Value = true end

		return true
	end,

	[productIdByName.BANANAPEEL_ID] = function(_receipt, player)
		task.spawn(bananaPeel)

		local reduce = player:FindFirstChild("ReducePrompts")
		if reduce then reduce.Value = true end

		return true
	end,

	[productIdByName.P_1] = function(_receipt, player)
		local leaderstats = player:FindFirstChild("leaderstats")
		if leaderstats then
			leaderstats.Points.Value += 45

			analyticsService:LogEconomyEvent(
				player,
				Enum.AnalyticsEconomyFlowType.Source,
				"Points",
				45,
				leaderstats.Points.Value,
				Enum.AnalyticsEconomyTransactionType.IAP.Name,
				"45 Points"
			)
		end

		return true
	end,

	[productIdByName.P_2] = function(_receipt, player)
		local leaderstats = player:FindFirstChild("leaderstats")
		if leaderstats then
			leaderstats.Points.Value += 99

			analyticsService:LogEconomyEvent(
				player,
				Enum.AnalyticsEconomyFlowType.Source,
				"Points",
				99,
				leaderstats.Points.Value,
				Enum.AnalyticsEconomyTransactionType.IAP.Name,
				"99 Points"
			)
		end

		return true
	end,
}

-- The core ProcessReceipt callback function
-- This implementation handles most failure scenarios but does not completely mitigate cross-server data failure scenarios
local function processReceipt(receiptInfo)
	local success, result = pcall(
		purchaseHistoryStore.UpdateAsync,
		purchaseHistoryStore,
		receiptInfo.PurchaseId,
		function(isPurchased)
			if isPurchased then
				-- This purchase was already recorded as granted, so it must have previously been handled
				-- Avoid calling the grant purchase handler here to prevent granting the purchase twice

				-- While the value in the data store is already true, true is returned again so that the pcall result variable is also true
				-- This will later be used to return PurchaseGranted from the receipt processor
				return true
			end

			local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
			if not player then
				-- Avoids granting the purchase if the player is not in the server
				-- When they rejoin, this receipt processor will be called again
				return nil
			end

			local grantPurchaseHandler = grantPurchaseHandlerByProductId[receiptInfo.ProductId]
			if not grantPurchaseHandler then
				-- If there's no handler defined for this product ID, the purchase cannot be processed
				-- This will never happen as long as a handler is set for every product ID sold in the experience
				warn(`No purchase handler defined for product ID '{receiptInfo.ProductId}'`)
				return nil
			end

			local handlerSucceeded, handlerResult = pcall(grantPurchaseHandler, receiptInfo, player)
			if not handlerSucceeded then
				local errorMessage = handlerResult
				warn(
					`Grant purchase handler errored while processing purchase from '{player.Name}' of product ID '{receiptInfo.ProductId}': {errorMessage}`
				)
				return nil
			end

			local didHandlerGrantPurchase = handlerResult == true
			if not didHandlerGrantPurchase then
				-- The handler did not grant the purchase, so record it as not granted
				return nil
			end

			-- The purchase is now granted to the player, so record it as granted
			-- This will later be used to return PurchaseGranted from the receipt processor
			return true
		end
	)

	if not success then
		local errorMessage = result
		warn(`Failed to process receipt due to data store error: {errorMessage}`)

		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	local didGrantPurchase = result == true
	return if didGrantPurchase
		then Enum.ProductPurchaseDecision.PurchaseGranted
		else Enum.ProductPurchaseDecision.NotProcessedYet
end

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

Hey! Your implementation is actually really solid, but there’s one critical issue that can cause ProcessReceipt to “stop working” randomly

You’re doing this:

local success, result = pcall(
purchaseHistoryStore.UpdateAsync,
purchaseHistoryStore,
receiptInfo.PurchaseId,
function(isPurchased)

This is not the safe way to call UpdateAsync

If anything inside that callback errors (even indirectly), the entire receipt processing silently fails and Roblox will keep retrying or just stall.

Wrap UpdateAsync properly

Use a function wrapper instead:

local success, result = pcall(function()
	return purchaseHistoryStore:UpdateAsync(receiptInfo.PurchaseId, function(isPurchased)
		if isPurchased then
			return true
		end

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

		local grantPurchaseHandler = grantPurchaseHandlerByProductId[receiptInfo.ProductId]
		if not grantPurchaseHandler then
			warn(`No handler for product ID {receiptInfo.ProductId}`)
			return nil
		end

		local handlerSucceeded, handlerResult = pcall(grantPurchaseHandler, receiptInfo, player)
		if not handlerSucceeded then
			warn(`Handler error: {handlerResult}`)
			return nil
		end

		if handlerResult ~= true then
			return nil
		end

		return true
	end)
end)

Another issue is that you rely on player inside ProcessReceipt

local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)

If the player leaves during purchase:

  • You return nil
  • Roblox retries later

You can improve it’s safety by adding a retry protection

if not player then
	return Enum.ProductPurchaseDecision.NotProcessedYet
end

(You already kinda do this, but make sure it’s consistent)

You can refer more here : https://create.roblox.com/docs/production/monetization/developer-products

3 Likes

Okay, I was just using what they had on devforums but I’ll check this out!

It is still not working and it seems like process receipt is not firing for some odd reason?

Donate board was interfering with the process receipt and had its own.

1 Like