Best way to use ProcessReceipt

I was just wondering if there was a ‘best way’ to use ProcessReceipt. Whether or not I should be storing purchases through a data store, etc.

This is what I’ve kinda always used

local ReceiptProcessor = {}

local DataStoreService = game:GetService('DataStoreService')
local MarketplaceService = game:GetService('MarketplaceService')
local Players = game:GetService('Players')

local PurchaseHistory = DataStoreService:GetDataStore('PurchaseHistory', 'In-Demo')

local Products = {
	[123456789] = function(receipt, player)
		print('Product purchased!')
		return true
	end
}

function MarketplaceService.ProcessReceipt(receiptInfo)
	local PlayerProductKey = receiptInfo.PlayerId .. ':' .. receiptInfo.PurchaseId
	if PurchaseHistory:GetAsync(PlayerProductKey) then return Enum.ProductPurchaseDecision.PurchaseGranted end
	
	local Player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
	if not Player then return Enum.ProductPurchaseDecision.NotProcessedYet end
	
	local Handler
	
	for productId, v in pairs(Products) do
		if productId == receiptInfo.ProductId then
			Handler = v 
			break
		end
	end
	
	if not Handler then return Enum.ProductPurchaseDecision.PurchaseGranted end
 
	local Success, Error = pcall(Handler, receiptInfo, Player)
	if not Success then
		warn('An error occured while processing a product purchase')
		print('\t ProductId:', receiptInfo.ProductId)
		print('\t Player:', Player)
		print('\t Error message:', Error)
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
 
	if not Error then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
 
	Success, Error = pcall(function()
		PurchaseHistory:SetAsync(PlayerProductKey, true)
	end)
	
	if not Success then
		print('An error occured while saving a product purchase')
		print('\t ProductId:', receiptInfo.ProductId)
		print('\t Player:', Player)
		print('\t Error message:', Error)
		print('\t Handler worked fine, purchase granted')
	end
	
	return Enum.ProductPurchaseDecision.PurchaseGranted
end

return ReceiptProcessor

Is there something here that I don’t need? Or is this fine?

It would probably be best to keep all that, but you could move that code to a ModuleScript so it doesn’t clutter up your scripts too much. If you really needed to, you could maybe remove some of the error handling sections of the script, but that could be harmful to the user experience (and debugging for you).

I recommend moving your final return statement, as you grant the purchase successful even if you fail to save it’s record. This can result in giving a player multiple rewards if datastores error.

Try this instead:

if not Success then
		print('An error occured while saving a product purchase')
		print('\t ProductId:', receiptInfo.ProductId)
		print('\t Player:', Player)
		print('\t Error message:', Error)
		print('\t Handler worked fine, purchase granted')
else
   return Enum.ProductPurchaseDecision.PurchaseGranted
end

Additionally, reward your reward last. That way, if saving goes wrong, you will not give a free reward.

	if not Handler then return Enum.ProductPurchaseDecision.PurchaseGranted end

Returning PurchaseGranted when you are unable to find a handler for a given productId is risky as it could lead to a bug where a purchase fails but you still take a users Robux if you were to forget to implement a handler for a product or something like that.

You also don’t need to loop through the Products table to find the handler, can simply index the table by productId.

local Handler = Products[receiptInfo.ProductId]
if not Handler then return Enum.ProductPurchaseDecision.NotProcessedYet end
4 Likes