In this post MarketplaceService | Documentation - Roblox Creator Hub '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)
end)
-- 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)
end
-- Record transaction in data store so it isn't granted again
local success, errorMessage = pcall(function()
purchaseHistoryStore:SetAsync(playerProductKey, true)
end)
if not success then
error("Cannot save purchase data: " .. errorMessage)
end