CyberMax
CyberMax › Answers

How to use developer products in Roblox (ProcessReceipt that never double-grants)

Updated 2026-10-17 · Roblox and game developers · by CyberMax

Short answer

Create the product in Creator Hub > Monetization, prompt it with MarketplaceService:PromptProductPurchase, and grant it only in MarketplaceService.ProcessReceipt. Record each PurchaseId in a DataStore, grant and save the item, and only then return PurchaseGranted; return NotProcessedYet if anything fails so Roblox retries. Roblox warns not to grant from PromptProductPurchaseFinished.

Copy-paste code

ServerScriptService/Purchases.server.lua

local MarketplaceService = game:GetService("MarketplaceService")
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local receipts = DataStoreService:GetDataStore("Receipts_v1")

local GRANTS = {
	[123456789] = function(player) -- your developer product id
		player.leaderstats.Coins.Value += 500
		return true
	end,
}

MarketplaceService.ProcessReceipt = function(info)
	local player = Players:GetPlayerByUserId(info.PlayerId)
	local grant = GRANTS[info.ProductId]
	if not player or not grant then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
	local granted = false
	local ok = pcall(receipts.UpdateAsync, receipts, info.PlayerId .. "_" .. info.PurchaseId, function(already)
		if already then granted = true return nil end -- granted on an earlier call
		if not grant(player) then return nil end
		granted = true
		return true
	end)
	if ok and granted then
		return Enum.ProductPurchaseDecision.PurchaseGranted
	end
	return Enum.ProductPurchaseDecision.NotProcessedYet
end

-- client: MarketplaceService:PromptProductPurchase(player, 123456789)

Good to know

ProcessReceipt can be called again for the same purchase, for example after a server crash or when the player rejoins. The PurchaseId record is what stops a second grant.

Only one script may set ProcessReceipt. Put every product in that one GRANTS table.

Roblox checks Listed products' ProcessReceipt and stops showing a product outside your game if it detects a problem.

Studwright: Roblox systems that never lose saves

from $12

Studwright's Receipts module grants developer products exactly once and saves the grant with the player's session-locked data. 16 tested modules in one kit.

Secure checkout · 14-day fix-or-refund if it doesn't work as described (terms)

Questions

Developer product or game pass?

Developer products can be bought many times (coins, boosts); game passes are bought once (VIP, a permanent perk).

Why not use PromptProductPurchaseFinished?

It only tells you the prompt closed; Roblox's docs say to grant purchases in ProcessReceipt.

How much do I keep?

Roblox keeps 30% of each sale; you keep 70% as Earned Robux.

Free: the Roblox Starter Kit

7 free Luau modules (damage, pets, shop formatting, admin audit log, tycoon guards and more) plus 3 free calculators, then 5 short emails with the scripting fixes we see go wrong most often.

You get one email to confirm first. Then the kit, and 5 short emails over two weeks. One-click unsubscribe in every email; we never share your address.

Related answers

How to make a Roblox game pass (and give the perk in a script)Create a pass in Creator Hub, then check ownership with UserOwnsGamePassAsync and grant passes bought mid-gameA Roblox DataStore save script that never wipes player dataCopy-paste Luau for loading and saving player data with DataStoreService: retries, UpdateAsync, BindToClose anRoblox DevEx requirements: who can cash out Robux, and at what rateDevEx eligibility from Roblox's docs: 13+, 30,000 Earned Robux, verified email, DevEx portal account and tax f