Roblox DevProduct System – Code Review

Hey everyone,

I’ve created a system for managing all my DevProdcuts in my game. It consists of two main parts:

DevProductManager (Server Module)

This module holds a table where you can easily add new product IDs and define actions for each one. The product ID is the key, and the value is a function that defines what happens when the product is bought. You only need to add the product ID and the corresponding function—everything else happens automatically. It has a Buy Function too, it runs when the player buys something.

DevProductHandler (Server Script)

This script handles all purchases via the ProcessReceipt function. It checks for valid player data, calls the appropriate function from the DevProductManager module, and processes the purchase.



How it works:

  1. Add product IDs: Just add a new entry to the ProductIDs table in the DevProductManager module.

  2. Define the action: For each product ID, write the function that should be executed when the player purchases it (e.g., adding money to the player).

  3. Everything else is automatic: The script will automatically detect the product ID, execute the function, and handle the purchase.



Code Review Request

I would love a review of my code to see if it’s efficient, clean, and secure. Please check for any potential improvements, issues, or best practices that I may have missed:

Video:



DevProductManager (ModuleScript):

-- //: Function for getting the money value in leaderstats
local function getMoneyValue(plr: Player)
	-- Get leaderstats
	local leaderstats = plr:FindFirstChild("leaderstats")
	if not leaderstats then
		warn(plr.Name .. " Has no Leaderstats")
		return false
	end
	
	-- Get Money Value
	local money = leaderstats:FindFirstChild("Money")
	if not money then
		warn(plr.Name .. " Has no MoneyValue in leaderstats")
		return false
	end
	
	return money
end


-- //: MAIN
local DevProductManager = {
	
	-- //: List of ProductIDs with functions that run when a player buys them
	ProductIDs = {
		
		["3224322508"] = function(plr: Player)
			-- Checking Function
			local money = getMoneyValue(plr)
			if not money then
				return false
			end
			
			money.Value += 100
			
			print(plr.Name .. " Bought 100 Money for 5 Robux!")
			return true
		end,

		["3224322948"] = function(plr: Player)
			-- Checking Function
			local money = getMoneyValue(plr)
			if not money then
				return false
			end

			money.Value += 500

			print(plr.Name .. " Bought 500 Money for 10 Robux!")
			return true
		end,

		["3224323154"] = function(plr: Player)
			--- Checking Function
			local money = getMoneyValue(plr)
			if not money then
				return false
			end

			money.Value += 1000

			print(plr.Name .. " Bought 1000 Money for 15 Robux!")
			return true
		end,
		
		["3224323352"] = function(plr: Player)
			-- Checking Function
			local money = getMoneyValue(plr)
			if not money then
				return false
			end

			money.Value += 5000

			print(plr.Name .. " Bought 5000 Money for 20 Robux!")
			return true
		end,
		
		["Type here you Product ID"] = function(plr: Player)
			-- Type here what should happen
		end,
		
	}
	
}


-- //: Function runs when player Buys Something
DevProductManager.Buy = function(plr: Player, productID: number)
	
	-- Check if productID is acutally a number
	if typeof(productID) ~= "number" then
		warn("ProductID Argument has to be a number!")
		return false
	end
	
	-- Check if given ProductID exists in the list
	local productFunction = DevProductManager.ProductIDs[tostring(productID)]
	if productFunction == nil then
		warn("ProductID Argument does not exists in the 'ProdcutIDs' table")
		return false
	end
	
	-- Call the function that belongs to the given ProductID
	local func = productFunction(plr)
	if func then
		return true
	else
		return false
	end
	
end

return DevProductManager



DevProductHandler (ServerScript):

-- //: Variables
local devProductModule = require(game:GetService("ServerScriptService").Modules.DevProductManager)
local marketplaceService = game:GetService("MarketplaceService")


-- //: Main Function
marketplaceService.ProcessReceipt = function(recieptInfo)
	
	-- //: Check if player is valid
	local plr = game.Players:GetPlayerByUserId(recieptInfo.PlayerId)
	if not plr then
		warn("Player with UserID " .. recieptInfo.PlayerId .. " could not be found.")
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
	
	-- //: Run the Buy Function, but in pcall
	local success, returnValue = pcall(function()
		return devProductModule.Buy(plr, recieptInfo.ProductId)
	end)
	
	-- //: Debugging
	if success then
		
		-- Check if the Buy Function returned true or false
		if returnValue then
			print("Buy Function Returned True - Purchase granted for Player: " .. plr.Name .. ", Product ID: " .. recieptInfo.ProductId)
			return Enum.ProductPurchaseDecision.PurchaseGranted
		else
			warn("Buy Function Returned False - Purchase Not Processed for Player: " .. plr.Name .. ", Product ID: " .. recieptInfo.ProductId)
			return Enum.ProductPurchaseDecision.NotProcessedYet
		end
		
	else
		-- If something went wrong with the function, log the error and return NotProcessedYet
		warn("Error during purchase processing for Player: " .. plr.Name .. ", Product ID: " .. recieptInfo.ProductId)
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
	
end

I would love to hear some feedback!

  1. You don’t need to pcall something that will never error (You error handled it)
  2. Could be clear

How I would rewrite it:

local marketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")

local devProductModule = require(game:GetService("ServerScriptService").Modules.DevProductManager)

local PurchaseDecision = {
	Granted = Enum.ProductPurchaseDecision.PurchaseGranted,
	Cancelled = Enum.ProductPurchaseDecision.NotProcessedYet
}

marketplaceService.ProcessReceipt = function(recieptInfo)
	local Player = Players:GetPlayerByUserId(recieptInfo.PlayerId)
	if not Player then warn(`Player with UserID {recieptInfo.PlayerId} could not be found.`) return PurchaseDecision.Cancelled end
	
	local Success = devProductModule.Buy(Player, recieptInfo.PlayerId)
	
	if Success then
		print(`Buy Function Returned True - Purchase granted for Player: {Player.Name} , Product ID: {recieptInfo.ProductId}`)
		return PurchaseDecision.Granted
	else
		warn(`Buy Function Returned False - Purchase Not Processed for Player: {Player.Name}, Product ID: {recieptInfo.ProductId}`)
		return PurchaseDecision.Cancelled
	end
end

With this, you could just immediately return what the function returns without evaluating like return productFunction(plr)

Overall I think your code is great though. I pretty much gave readability suggestions

1 Like

Bro thanks a lot,
Thanks for reading my code.

These Tips were great.
It improves the readability a lot.

I will start making these nice changes now!

Thanks for making my code cleaner!

Btw for your product ID’s, why are they inside the table and the function is outside? You should move your product ID’s outside the table tbh

For the devoroductmanager module, it can be better. Instead of adding a key and a function for every pass even though the functions are identical. We can just use one fn and store the money amount in the array:

ProductIDs = {
    ["1234"] = 100,
    ["5678"] = 500,
--etc your badge ids
}

And then instead of calling a buy fn we get the money amount and just use that:

--other code inside .buy
local money = ProductIDs[tostring(productId)]
local value = getMoneyValue(plr)

if not value or not money then return false end
value.Value += money

return true

Also you don’t need to do if true false return etc you can just return the boolean


if x then
  return true
else
  return false
end
--equivalent to

return x--with x a boolean

Because I have the main table that I return,

and it that mainTable I have a table where all product ids are,
and I have a Buy funnction,

I dont want the buy function to be in the productIDs table

The last tip was good with the returning, but I can’t use the rest.

because not all my functions are for money,
I will add funnctions for different things, not just buying Money.

that’s why I can’t do it like you said.



Thanks for taking time and giving me feedback!

1 Like