How to have multiple developer products

Hello! I have a donation leaderboard and a skip stage developer product that won’t work at the same time. The only thing that works is the donation leaderboard if I have the skip stage running as well. I know it’s due in part to the Process Receipt thing and that you’re only allowed to run that once. The only problem is, I’m not sure how to combine my two/three scripts together because they both do completely different things. I’ll put my three scripts down below as reference. Donation leaderboard script in workspace:

local DATA_KEY = "#&(DMD!)A!)@$"
local GUI_FACE = "Right"
local DISPLAY_AMOUNT = 25
local REFRESH_RATE = 60
local DONATION_OPTIONS = {
	{
		Amount = 5,
		Id = 1380706015
	},
	{
		Amount = 10,
		Id = 1380706409
	},
	{
		Amount = 50,
		Id = 1380706407
	},
	{
		Amount = 100,
		Id = 1380706406
	},
	{
		Amount = 250,
		Id = 1380706685
	},
	{
		Amount = 500,
		Id = 1380706684
	},
	{
		Amount = 1000,
		Id = 1380706683
	},
	{
		Amount = 5000,
		Id = 1380706831
	},
}








-----------------------------------------------------------------------------------------------------------
--> Code starts here. Only edit if you're an experienced programmer!

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

local gui = script.Donations
local face = Enum.NormalId[GUI_FACE]

local eve = Instance.new("RemoteEvent")
eve.Name = "DonationEvent"
eve.Parent = ReplicatedStorage

local cache = {}

local tmType = Enum.ThumbnailType.HeadShot
local tmSize = Enum.ThumbnailSize.Size420x420

local donations = DataStoreService:GetOrderedDataStore("DONATIONS_" .. DATA_KEY)

if not face then error("Invalid GUI_FACE: " .. GUI_FACE) else gui.Face = face end

local function getName(id)
	for cachedId, name in pairs (cache) do
		if cachedId == id then
			return name
		end
	end
	local success, result = pcall(function()
		return Players:GetNameFromUserIdAsync(id)
	end)
	if success then
		cache[id] = result
		return result
	else
		warn(result .. "\nId: " .. id)
		return "N/A"
	end
end

local function findAmountById(id)
	for _, donationInfo in pairs (DONATION_OPTIONS) do
		if donationInfo.Id == id then
			print(donationInfo.Amount)
			return donationInfo.Amount
		end
	end
	warn("Couldn't find donation amount for product ID " .. id)
	return 0
end

local function clearList(list)
	for _, v in pairs (list:GetChildren()) do
		if v:IsA("Frame") then v:Destroy() end
	end
end

local function updateAllClients(page)
	eve:FireAllClients("update", page)
end

local function updateInternalBoard(updateClientsAfter)
	local sorted = donations:GetSortedAsync(false, math.clamp(DISPLAY_AMOUNT, 0, 250), 1)
	if sorted then
		local page = sorted:GetCurrentPage()
		local clientDataPacket = {}
		clearList(gui.Main.Leaderboard.List)
		for rank, data in ipairs(page) do
			local userId = data.key
			local username = getName(data.key)
			local icon, isReady = Players:GetUserThumbnailAsync(userId, tmType, tmSize)
			local amountDonated = data.value .. " robux"

			local clone = gui.Main.Leaderboard.Template:Clone()
			clone.Icon.Image = icon
			clone.Rank.Text = "#" .. rank
			clone.Robux.Text = amountDonated
			clone.Username.Text = username
			clone.LayoutOrder = rank
			clone.Visible = true
			clone.Parent = gui.Main.Leaderboard.List
			
			table.insert(clientDataPacket, {
				["name"] = username,
				["icon"] = icon,
				["amount"] = amountDonated,
				["rank"] = rank
			})
		end
		
		if updateClientsAfter then
			updateAllClients(clientDataPacket)
		end
	else
		warn("No data available for leaderboard refresh!")
	end
end

local function createButtonsInternal()
	for pos, donationInfo in pairs (DONATION_OPTIONS) do
		local clone = gui.Main.Donate.Template:Clone()

		clone.Id.Value = donationInfo.Id
		clone.Info.Text = "<b>" .. donationInfo.Amount .. "</b> robux"

		clone.Visible = true
		clone.LayoutOrder = pos

		clone.Parent = gui.Main.Donate.List
	end
end

local function processReceipt(receiptInfo) 
	local donatedAmount = findAmountById(receiptInfo.ProductId)
	local id = receiptInfo.PlayerId

	local success, err = pcall(function()
		donations:UpdateAsync(id, function(previousData)
			if previousData then
				return previousData + donatedAmount
			else
				return donatedAmount
			end
		end)
	end)

	local player = Players:GetPlayerByUserId(id)

	if not success then
		if player then
			eve:FireClient(player, "Error", "There was an error processing your purchase. You have not been charged. Error: " .. err)
		end
		warn("Error handling " .. id .. "'s purchase: " .. err)
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	if player then
		eve:FireClient(player, "Success", "Thanks for your generous donation!")
	end

	return Enum.ProductPurchaseDecision.PurchaseGranted
end

local function onPlayerAdded(plr)
	local pGui = plr:WaitForChild("PlayerGui", 5)
	if pGui then
		for _, board in pairs (script.Parent:GetChildren()) do
			if board.Name == "Board" then
				local clone = gui:Clone()
				clone.Adornee = board
				clone.Parent = pGui
			end
		end 
		return true
	end
	warn("Couldn't find PlayerGui for " .. plr.Name .. ":" .. plr.UserId)
end



createButtonsInternal()
updateInternalBoard(false)
MarketplaceService.ProcessReceipt = processReceipt

for _, plr in pairs (Players:GetPlayers()) do
	onPlayerAdded(plr)
end
Players.PlayerAdded:Connect(onPlayerAdded)

while true do
	wait(REFRESH_RATE)
	updateInternalBoard(true)
end

Skip Stage in ServerScriptService:

local mps = game:GetService("MarketplaceService")
local devProductID = 1388352883

mps.ProcessReceipt = function(purchaseInfo)
	local plrPurchased = game.Players:GetPlayerByUserId(purchaseInfo.PlayerId)
	if not plrPurchased then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
	
	if purchaseInfo.ProductId == devProductID then
		if tonumber(plrPurchased.leaderstats.Stage.Value) < #workspace.Checkpoints:GetChildren() - 1 then
			plrPurchased.leaderstats.Stage.Value = plrPurchased.leaderstats.Stage.Value + 1
			
				
		else
			
			plrPurchased.leaderstats.Stage.Value = "End"
			
		end
		
		plrPurchased:LoadCharacter()
		
		return Enum.ProductPurchaseDecision.PurchaseGranted
	end
end

And then I also have another one inside the skip stage button in starter gui:

local mps = game:GetService("MarketplaceService")

local devProductID = 1388352883

local plr = game.Players.LocalPlayer

local button = script.Parent

button.MouseButton1Click:Connect(function()
	if plr.leaderstats.Stage.Value == "End" then return end
	
	
	mps:PromptProductPurchase(plr, devProductID)
	
end)

Any help is appreciated because I’m not sure what to do at this point.

3 Likes

The first two scripts need to be combined since there could only be one function for MarketplaceService.ProcessReceipt. You can leave the third script alone. Let me see if this works:

local DATA_KEY = "#&(DMD!)A!)@$"
local GUI_FACE = "Right"
local DISPLAY_AMOUNT = 25
local REFRESH_RATE = 60
local DONATION_OPTIONS = {
	{
		Amount = 5,
		Id = 1380706015
	},
	{
		Amount = 10,
		Id = 1380706409
	},
	{
		Amount = 50,
		Id = 1380706407
	},
	{
		Amount = 100,
		Id = 1380706406
	},
	{
		Amount = 250,
		Id = 1380706685
	},
	{
		Amount = 500,
		Id = 1380706684
	},
	{
		Amount = 1000,
		Id = 1380706683
	},
	{
		Amount = 5000,
		Id = 1380706831
	},
}

local skipStageID = 1388352883

-----------------------------------------------------------------------------------------------------------
--> Code starts here. Only edit if you're an experienced programmer!

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

local gui = script.Donations
local face = Enum.NormalId[GUI_FACE]

local eve = Instance.new("RemoteEvent")
eve.Name = "DonationEvent"
eve.Parent = ReplicatedStorage

local cache = {}

local tmType = Enum.ThumbnailType.HeadShot
local tmSize = Enum.ThumbnailSize.Size420x420

local donations = DataStoreService:GetOrderedDataStore("DONATIONS_" .. DATA_KEY)

if not face then error("Invalid GUI_FACE: " .. GUI_FACE) else gui.Face = face end

local function getName(id)
	for cachedId, name in pairs (cache) do
		if cachedId == id then
			return name
		end
	end
	local success, result = pcall(function()
		return Players:GetNameFromUserIdAsync(id)
	end)
	if success then
		cache[id] = result
		return result
	else
		warn(result .. "\nId: " .. id)
		return "N/A"
	end
end

local function findAmountById(id)
	for _, donationInfo in pairs (DONATION_OPTIONS) do
		if donationInfo.Id == id then
			print(donationInfo.Amount)
			return donationInfo.Amount
		end
	end
	warn("Couldn't find donation amount for product ID " .. id)
	return 0
end

local function clearList(list)
	for _, v in pairs (list:GetChildren()) do
		if v:IsA("Frame") then v:Destroy() end
	end
end

local function updateAllClients(page)
	eve:FireAllClients("update", page)
end

local function updateInternalBoard(updateClientsAfter)
	local sorted = donations:GetSortedAsync(false, math.clamp(DISPLAY_AMOUNT, 0, 250), 1)
	if sorted then
		local page = sorted:GetCurrentPage()
		local clientDataPacket = {}
		clearList(gui.Main.Leaderboard.List)
		for rank, data in ipairs(page) do
			local userId = data.key
			local username = getName(data.key)
			local icon, isReady = Players:GetUserThumbnailAsync(userId, tmType, tmSize)
			local amountDonated = data.value .. " robux"

			local clone = gui.Main.Leaderboard.Template:Clone()
			clone.Icon.Image = icon
			clone.Rank.Text = "#" .. rank
			clone.Robux.Text = amountDonated
			clone.Username.Text = username
			clone.LayoutOrder = rank
			clone.Visible = true
			clone.Parent = gui.Main.Leaderboard.List
			
			table.insert(clientDataPacket, {
				["name"] = username,
				["icon"] = icon,
				["amount"] = amountDonated,
				["rank"] = rank
			})
		end
		
		if updateClientsAfter then
			updateAllClients(clientDataPacket)
		end
	else
		warn("No data available for leaderboard refresh!")
	end
end

local function createButtonsInternal()
	for pos, donationInfo in pairs (DONATION_OPTIONS) do
		local clone = gui.Main.Donate.Template:Clone()

		clone.Id.Value = donationInfo.Id
		clone.Info.Text = "<b>" .. donationInfo.Amount .. "</b> robux"

		clone.Visible = true
		clone.LayoutOrder = pos

		clone.Parent = gui.Main.Donate.List
	end
end

local function processReceipt(receiptInfo)
	local plrPurchased = game.Players:GetPlayerByUserId(receiptInfo.PlayerId)
	if not plrPurchased then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	if purchaseInfo.ProductId == skipStageID then
		if tonumber(plrPurchased.leaderstats.Stage.Value) < #workspace.Checkpoints:GetChildren() - 1 then
			plrPurchased.leaderstats.Stage.Value = plrPurchased.leaderstats.Stage.Value + 1	
		else
			plrPurchased.leaderstats.Stage.Value = "End"
		end
		
		plrPurchased:LoadCharacter()
		
		return Enum.ProductPurchaseDecision.PurchaseGranted
	end

	local donatedAmount = findAmountById(receiptInfo.ProductId)
	local id = receiptInfo.PlayerId

	local success, err = pcall(function()
		donations:UpdateAsync(id, function(previousData)
			if previousData then
				return previousData + donatedAmount
			else
				return donatedAmount
			end
		end)
	end)

	local player = Players:GetPlayerByUserId(id)

	if not success then
		if player then
			eve:FireClient(player, "Error", "There was an error processing your purchase. You have not been charged. Error: " .. err)
		end
		warn("Error handling " .. id .. "'s purchase: " .. err)
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	if player then
		eve:FireClient(player, "Success", "Thanks for your generous donation!")
	end

	return Enum.ProductPurchaseDecision.PurchaseGranted
end

local function onPlayerAdded(plr)
	local pGui = plr:WaitForChild("PlayerGui", 5)
	if pGui then
		for _, board in pairs (script.Parent:GetChildren()) do
			if board.Name == "Board" then
				local clone = gui:Clone()
				clone.Adornee = board
				clone.Parent = pGui
			end
		end 
		return true
	end
	warn("Couldn't find PlayerGui for " .. plr.Name .. ":" .. plr.UserId)
end



createButtonsInternal()
updateInternalBoard(false)
MarketplaceService.ProcessReceipt = processReceipt

for _, plr in pairs (Players:GetPlayers()) do
	onPlayerAdded(plr)
end
Players.PlayerAdded:Connect(onPlayerAdded)

while true do
	wait(REFRESH_RATE)
	updateInternalBoard(true)
end

Also, by how different the styles are for the two scripts, I’m assuming that you copied this code off somewhere? Anyway hope this helps!

2 Likes

This for some reason is saying that it is an unknown global.
And yeah the donation script came with a donation board model and the other script is something I learned from a youtube video. I’m still trying to learn scripting but there are certain aspects that are harder than others haha. I do appreciate the help!

My bad, I forgot to change the name of the variable from purchaseInfo to receiptInfo. So change it to this:

local DATA_KEY = "#&(DMD!)A!)@$"
local GUI_FACE = "Right"
local DISPLAY_AMOUNT = 25
local REFRESH_RATE = 60
local DONATION_OPTIONS = {
	{
		Amount = 5,
		Id = 1380706015
	},
	{
		Amount = 10,
		Id = 1380706409
	},
	{
		Amount = 50,
		Id = 1380706407
	},
	{
		Amount = 100,
		Id = 1380706406
	},
	{
		Amount = 250,
		Id = 1380706685
	},
	{
		Amount = 500,
		Id = 1380706684
	},
	{
		Amount = 1000,
		Id = 1380706683
	},
	{
		Amount = 5000,
		Id = 1380706831
	},
}

local skipStageID = 1388352883

-----------------------------------------------------------------------------------------------------------
--> Code starts here. Only edit if you're an experienced programmer!

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

local gui = script.Donations
local face = Enum.NormalId[GUI_FACE]

local eve = Instance.new("RemoteEvent")
eve.Name = "DonationEvent"
eve.Parent = ReplicatedStorage

local cache = {}

local tmType = Enum.ThumbnailType.HeadShot
local tmSize = Enum.ThumbnailSize.Size420x420

local donations = DataStoreService:GetOrderedDataStore("DONATIONS_" .. DATA_KEY)

if not face then error("Invalid GUI_FACE: " .. GUI_FACE) else gui.Face = face end

local function getName(id)
	for cachedId, name in pairs (cache) do
		if cachedId == id then
			return name
		end
	end
	local success, result = pcall(function()
		return Players:GetNameFromUserIdAsync(id)
	end)
	if success then
		cache[id] = result
		return result
	else
		warn(result .. "\nId: " .. id)
		return "N/A"
	end
end

local function findAmountById(id)
	for _, donationInfo in pairs (DONATION_OPTIONS) do
		if donationInfo.Id == id then
			print(donationInfo.Amount)
			return donationInfo.Amount
		end
	end
	warn("Couldn't find donation amount for product ID " .. id)
	return 0
end

local function clearList(list)
	for _, v in pairs (list:GetChildren()) do
		if v:IsA("Frame") then v:Destroy() end
	end
end

local function updateAllClients(page)
	eve:FireAllClients("update", page)
end

local function updateInternalBoard(updateClientsAfter)
	local sorted = donations:GetSortedAsync(false, math.clamp(DISPLAY_AMOUNT, 0, 250), 1)
	if sorted then
		local page = sorted:GetCurrentPage()
		local clientDataPacket = {}
		clearList(gui.Main.Leaderboard.List)
		for rank, data in ipairs(page) do
			local userId = data.key
			local username = getName(data.key)
			local icon, isReady = Players:GetUserThumbnailAsync(userId, tmType, tmSize)
			local amountDonated = data.value .. " robux"

			local clone = gui.Main.Leaderboard.Template:Clone()
			clone.Icon.Image = icon
			clone.Rank.Text = "#" .. rank
			clone.Robux.Text = amountDonated
			clone.Username.Text = username
			clone.LayoutOrder = rank
			clone.Visible = true
			clone.Parent = gui.Main.Leaderboard.List
			
			table.insert(clientDataPacket, {
				["name"] = username,
				["icon"] = icon,
				["amount"] = amountDonated,
				["rank"] = rank
			})
		end
		
		if updateClientsAfter then
			updateAllClients(clientDataPacket)
		end
	else
		warn("No data available for leaderboard refresh!")
	end
end

local function createButtonsInternal()
	for pos, donationInfo in pairs (DONATION_OPTIONS) do
		local clone = gui.Main.Donate.Template:Clone()

		clone.Id.Value = donationInfo.Id
		clone.Info.Text = "<b>" .. donationInfo.Amount .. "</b> robux"

		clone.Visible = true
		clone.LayoutOrder = pos

		clone.Parent = gui.Main.Donate.List
	end
end

local function processReceipt(receiptInfo)
	local plrPurchased = game.Players:GetPlayerByUserId(receiptInfo.PlayerId)
	if not plrPurchased then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	if receiptInfo.ProductId == skipStageID then
		if tonumber(plrPurchased.leaderstats.Stage.Value) < #workspace.Checkpoints:GetChildren() - 1 then
			plrPurchased.leaderstats.Stage.Value = plrPurchased.leaderstats.Stage.Value + 1	
		else
			plrPurchased.leaderstats.Stage.Value = "End"
		end
		
		plrPurchased:LoadCharacter()
		
		return Enum.ProductPurchaseDecision.PurchaseGranted
	end

	local donatedAmount = findAmountById(receiptInfo.ProductId)
	local id = receiptInfo.PlayerId

	local success, err = pcall(function()
		donations:UpdateAsync(id, function(previousData)
			if previousData then
				return previousData + donatedAmount
			else
				return donatedAmount
			end
		end)
	end)

	local player = Players:GetPlayerByUserId(id)

	if not success then
		if player then
			eve:FireClient(player, "Error", "There was an error processing your purchase. You have not been charged. Error: " .. err)
		end
		warn("Error handling " .. id .. "'s purchase: " .. err)
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	if player then
		eve:FireClient(player, "Success", "Thanks for your generous donation!")
	end

	return Enum.ProductPurchaseDecision.PurchaseGranted
end

local function onPlayerAdded(plr)
	local pGui = plr:WaitForChild("PlayerGui", 5)
	if pGui then
		for _, board in pairs (script.Parent:GetChildren()) do
			if board.Name == "Board" then
				local clone = gui:Clone()
				clone.Adornee = board
				clone.Parent = pGui
			end
		end 
		return true
	end
	warn("Couldn't find PlayerGui for " .. plr.Name .. ":" .. plr.UserId)
end



createButtonsInternal()
updateInternalBoard(false)
MarketplaceService.ProcessReceipt = processReceipt

for _, plr in pairs (Players:GetPlayers()) do
	onPlayerAdded(plr)
end
Players.PlayerAdded:Connect(onPlayerAdded)

while true do
	wait(REFRESH_RATE)
	updateInternalBoard(true)
end
2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.