Why save the history of purchased developer products?

In this post https://developer.roblox.com/en-us/api-reference/callback/MarketplaceService/ProcessReceipt 's code example, the history of developer product purchases is saved in a datastore.

Why is this needed? In the code sample of the post, the first thing which is checked whenever the ProcessReceipt callback function is called, is whether the PurchaseId is already saved in the datastore, meaning that the purchase has already been handled succesfully and will not be granted again.

Why should that be checked in the first place? In which scenario is it possible that the same PurchaseId is called again even when it has been handled succesfully in the past?

Could it be because Enum.ProductPurchaseDecision.NotProcessedYet calls the callback function multiple times, even after the purchase already has been granted?

Little snipped of the code example:

-- The core 'ProcessReceipt' callback function
local function processReceipt(receiptInfo)
	-- Determine if the product was already granted by checking the data store  
	local playerProductKey = receiptInfo.PlayerId .. "_" .. receiptInfo.PurchaseId
	local purchased = false
	local success, errorMessage = pcall(function()
		purchased = purchaseHistoryStore:GetAsync(playerProductKey)
	-- If purchase was recorded, the product was already granted
	if success and purchased then
		return Enum.ProductPurchaseDecision.PurchaseGranted
	elseif not success then
		error("Data store error:" .. errorMessage)
	-- Record transaction in data store so it isn't granted again
	local success, errorMessage = pcall(function()
		purchaseHistoryStore:SetAsync(playerProductKey, true)
	if not success then
		error("Cannot save purchase data: " .. errorMessage)
1 Like

I think someone asked this question before.

1 Like

This is to guarantee the purchased goods are delivered to the buyer. It could happen that the server shutdowns or has network issues at the moment of the purchase and the player could have bought the product without actually receiving it.
Using this DataStore, the game can later check if the purchase has already been granted or not. I think it will keep calling it until it is granted.