Can somebody please explain how this code implemented datastores in developer products?

I have been trying to learn how to use developer products, and from the DevHub’s sample code, it implements datastores in order to keep track of purchases?
After looking at the code, I have some questions.

  1. In the code, it checks to see if the player has already brought the item using Datastores, and if the item was brought, it returned Enum.ProductPurchaseDecision.PurchaseGranted. Does this mean the player can only purchase this product once and if they try to buy it again, it returns Enum.ProductPurchaseDecision.PurchaseGranted and takes their Robux without giving anything?
  2. What exactly does Enum.ProductPurchaseDecision.PurchaseGranted do? Does this mean the purchase was successful and Robux will be deducted from the player’s account?

Blockquote

local MarketplaceService = game:GetService("MarketplaceService")
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
 
-- Data store for tracking purchases that were successfully processed
local purchaseHistoryStore = DataStoreService:GetDataStore("PurchaseHistory")
 
-- Table setup containing product IDs and functions for handling purchases
local productFunctions = {}
-- ProductId 123123 for a full heal
productFunctions[123123] = function(receipt, player)
	-- Logic/code for player buying a full heal (may vary)
	if player.Character and player.Character:FindFirstChild("Humanoid") then
		-- Heal the player to full health
		player.Character.Humanoid.Health = player.Character.Humanoid.MaxHealth
		-- Indicate a successful purchase
		return true
	end
end
-- ProductId 456456 for 100 gold
productFunctions[456456] = function(receipt, player)
	-- Logic/code for player buying 100 gold (may vary)
	local stats = player:FindFirstChild("leaderstats")
	local gold = stats and stats:FindFirstChild("Gold")
	if gold then
		gold.Value = gold.Value + 100
		-- Indicate a successful purchase
		return true
	end
end
 
-- 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
 
	-- Find the player who made the purchase in the server
	local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
	if not player then
		-- The player probably left the game
		-- If they come back, the callback will be called again
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
	
	-- Look up handler function from 'productFunctions' table above
	local handler = productFunctions[receiptInfo.ProductId]
 
	-- Call the handler function and catch any errors
	local success, result = pcall(handler, receiptInfo, player)
	if not success or not result then
		warn("Error occurred while processing a product purchase")
		print("\nProductId:", receiptInfo.ProductId)
		print("\nPlayer:", player)
		return Enum.ProductPurchaseDecision.NotProcessedYet
	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
 
	-- IMPORTANT: Tell Roblox that the game successfully handled the purchase
	return Enum.ProductPurchaseDecision.PurchaseGranted
end
 
-- Set the callback; this can only be done once by one script on the server! 
MarketplaceService.ProcessReceipt = processReceipt

Enum.ProductPurchaseDecision.PurchaseGranted is an indication to Roblox that the game successfully granted the player the product.

I would suggest for you to refer to MarketplaceService.ProcessReceipt

Nope, the way it works is that it checks the History datastore if something had already been done via check if a key in a datastore has true stored in it. The key in question consists of the UserId of who bought the Developer Product, an underscore, and the PurchaseId, which is a unique string identifier that is made for that specific purchase, meaning no 2 identifiers can be the same I believe. It doesn’t prevent you form buying a Developer Product more than once, it basically ensures you cannot get the same benefits from a single purchase more than once

It’s just a way to show the game that a purchase was made successfully

Even if it wasn’t successful, I.E, NotProcessedYet was returned, it’ll still deduce the Robux. They’re just ways to show if a purchase was granted and no further action needs to be taken or if the purchase was not processed yet, such as if the player leaves during the purchase or something else happened, it’ll try again at other times, which I am unsure as to when that happens, think it can happen when the player rejoins, and when purchasing another Developer Product I think?

1 Like

Thanks, that was very well explained!

1 Like

Glad to have helped you understand what the code is doing! If you have anymore issues don’t be afraid to make another post!

1 Like