Wondering about the reliability of my devproduct scripts

Hi people, i recently made a tycoon that has devproducts for players to buy game currency (coins),
although in play test the purchases have never failed me (even when simulating a full server) a player added me as friends to tell me that they haven’t received the coins, i checked in transactions and fair enough there’s their purchase, i manually added their coins with a datastore editor and went here to ask you guys for help

  1. What do you want to achieve? Keep it simple and clear!
    -A very reliable devproduct purchase system

  2. What is the issue? Include screenshots / videos if possible!
    -Someone told me that the purchase was successful but the coin delivery failed

  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    -These two scripts have been revamped once, and asked ai for fine tuning

my devproduct system works with 2 scripts, one local script that triggers when the user presses a screengui textbutton, one for each coin tier, this is an example of the 10k one, the maximum tier, the only difference between scripts would be the ID

This is the LocalScript at ScreenGui

local MarketPlaceService = game:GetService("MarketplaceService")
local Chat = game:GetService("Chat")

local player = game.Players.LocalPlayer

local function sendNotification(message)
	Chat:Chat(player.Character.Head, message, Enum.ChatColor.Blue)  -- You can choose the color
end

script.Parent.Activated:Connect(function()
	if player.HasTycoon.Value == true then
		if player.leaderstats.Coins.Value > 6000 then
			sendNotification("Usually 15,000 coins completes the tycoon, don't buy so much!")
		else
			MarketPlaceService:PromptProductPurchase(player, 2155628281)
		end
		
	else
		sendNotification("Get a tycoon first!")
	end

end)

That script then triggers the this normal Script located at ServerScriptService

local MarketplaceService = game:GetService("MarketplaceService")

local function grantCoins(player, amount)
	if player and player:FindFirstChild("leaderstats") and player.leaderstats:FindFirstChild("Coins") then
		player.leaderstats.Coins.Value += amount
		return true
	end
	return false
end

MarketplaceService.ProcessReceipt = function(receiptInfo)
	local player = game.Players:GetPlayerByUserId(receiptInfo.PlayerId)
	if not player then
		warn("Player not found for purchase! Receipt ID:", receiptInfo.ReceiptId)
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	-- Product processing logic
	local productIdToCoins = {
		[2155628281] = 10000,
		[2700160371] = 850,
		[2155642891] = 1000,
		[2155642964] = 2000,
		[2155643679] = 5000,
	}

	local coinsToGrant = productIdToCoins[receiptInfo.ProductId]
	if coinsToGrant then
		local success = grantCoins(player, coinsToGrant)
		if success then
			-- Granting succeeded; approve the purchase
			return Enum.ProductPurchaseDecision.PurchaseGranted
		else
			-- Granting failed; don't approve the purchase yet
			warn("Failed to grant coins to player:", player.Name, "for ProductId:", receiptInfo.ProductId)
			return Enum.ProductPurchaseDecision.NotProcessedYet
		end
	else
		warn("Invalid ProductId:", receiptInfo.ProductId)
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
end

Those are the scripts, if you guys have ideas to bulletproof this system or have any suggestions, those are very welcome, thanks in advance

i see 2 potential issues. one in an easy fix the other is a little more difficult.

First some background.
In this image you can see when a processed receipt callback is called.


Now imagine the following scenario.
A player purchases coins and immediately leaves.
In your code this causes your processed receipt to return NotProcessedYet because the player is not in the game anymore.
Then the player joins again your leaderstats script will make a new folder and your callback will also try to process the purchase again. If your callback fires before the leaderstats folder is made your grantCoins() function will return false and your process receipt will return NotProcessedYet again.
This can go on and on every time the player joins causing the purchase to never complete.

To fix this you should use WaitForChild() instead of FindFirstChild() to find the leaderstats folder.

The other issue is that you should only return PurchaseGranted once the coins you’ve given have successfully saved to the player’s datastore.
This is needed because if you give the player coins and return PurchaseGranted and then the server crashes or fails to save the user’s data the coins you’ve given are lost.

If you want to know more about how to properly grant purchases you should give this a read.
There is a section specifically about developer products but everything is worth reading.
It’s quite advanced though.

1 Like

i will implement those changes and check out the info, thanks a lot for your answer and time :pray::pray::pray: