DeveloperProduct receipt checking

I modified the process receipt callback function from Roblox’ documentation and am wondering if it can be improved at all. I can see things getting a bit messy down the line with this when more things get added.

local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")
local ServerScriptService = game:GetService("ServerScriptService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Manager = require(ServerScriptService.PlayerData.Manager) --data saving using ProfileService

local TemporaryBoosts = require(ReplicatedStorage.Databases.TemporaryBoosts) --dictionary
local PermanentBoosts = require(ReplicatedStorage.Databases.PermanentBoosts) --dictionary
local remotes = ReplicatedStorage.Remotes

local permanentBoostIDs = {}
local temporaryBoostIDs = {}

for _, permanentBoost in PermanentBoosts do
	table.insert(permanentBoostIDs, permanentBoost.ID)
end
for _, temporaryBoost in TemporaryBoosts do
	table.insert(temporaryBoostIDs, temporaryBoost.ID)
end

local productFunctions = {}

productFunctions["ID"] = function(receipt, player) --left this here incase something comes up that needs it later in development
	local profile = Manager.Profiles[player]
	if not profile then return end
end

local function processReceipt(receiptInfo)
	local userId = receiptInfo.PlayerId
	local productId = receiptInfo.ProductId
	local info = MarketplaceService:GetProductInfo(productId, Enum.InfoType.Product)
	local player = Players:GetPlayerByUserId(userId)
	
	if player then
		if table.find(permanentBoostIDs, productId) then
			local profile = Manager.Profiles[player]
			if not profile then return end

			local success, result = pcall(function()
				for _, permanentBoost in PermanentBoosts do
					if permanentBoost.ID == productId then
						Manager.SetPermanentBoost(player, permanentBoost.boostAmount)
						remotes.permanentBoostEvent:Fire(player)
					end
				end
			end)

			if success then
				print("[".. player.Name.. "] has bought [".. info.Name.. "]")
				return Enum.ProductPurchaseDecision.PurchaseGranted
			else
				warn("Failed to process receipt: ", receiptInfo, result)
			end
		elseif table.find(temporaryBoostIDs, productId) then
			local profile = Manager.Profiles[player]
			if not profile then return end

			local success, result = pcall(function()
				for _, temporaryBoost in TemporaryBoosts do
					if temporaryBoost.ID == productId then
						Manager.GiveTemporaryBoost(player, temporaryBoost)
					end
				end
			end)

			if success then
				print("[".. player.Name.. "] has bought [".. info.Name.. "]")
				return Enum.ProductPurchaseDecision.PurchaseGranted
			else
				warn("Failed to process receipt: ", receiptInfo, result)
			end
		else
			local handler = productFunctions[productId]
			local success, result = pcall(handler, receiptInfo, player)

			if success then
				print(player.Name.. " has bought ".. info.Name)
				return Enum.ProductPurchaseDecision.PurchaseGranted
			else
				warn("Failed to process receipt: ", receiptInfo, result)
			end
		end

		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
end

MarketplaceService.ProcessReceipt = processReceipt
2 Likes

Hi there!

Your modified process receipt function looks quite solid, but I can see how it might become messy as you add more features. Here are a few suggestions to improve the clarity and maintainability of your code:

  1. Modularize Product Handlers: Instead of handling all product types in the processReceipt function, consider separating the logic into individual functions for permanent and temporary boosts. This way, your main function remains clean, and each handler can focus on its specific logic.

  2. Use a Lookup Table for Boosts: Instead of looping through the PermanentBoosts and TemporaryBoosts each time, you could create a lookup table when the game starts. This would allow you to quickly access boosts by their IDs without needing to iterate through the lists.

  3. Centralized Logging: You have some print and warn statements. It might be helpful to create a centralized logging function to handle all logging, making it easier to manage output in the future.

  4. Error Handling: While you’re already using pcall, consider defining custom error messages for different failure points, so you can pinpoint issues more effectively during debugging.

  5. Commenting and Documentation: Adding comments to explain the purpose of different sections of the code can help future maintainers (or yourself) understand the logic at a glance.

Here’s a brief refactor suggestion for clarity:

local function handlePermanentBoost(productId, player)
    -- Handle permanent boost logic
end

local function handleTemporaryBoost(productId, player)
    -- Handle temporary boost logic
end

local function processReceipt(receiptInfo)
    -- Main logic, with calls to handlePermanentBoost and handleTemporaryBoost
end