Why is it increasing?

Hi Guys, so I basically made a script where if a player buys a specific product, there stats named Skips increase. Basically I have 5 types of skips -
+1 Skip
+5 Skip
+10 skip
+25 skip
+100 skip

Basically whenever someone buys any product, it changes the value in table.

Script -

MS.ProcessReceipt = function(receipt)
		if receipt.PlayerId == player.UserId then
			if receipt.ProductId == 1358881369 then
				data.Skips1 += 1
				print(data.Skips1)
			end
			if receipt.ProductId == 1361212065 then
				data.Skips5 += 1
				print(data.Skips5)
			end
			if receipt.ProductId == 1361212655 then
				data.Skips10 += 1
				print(data.Skips10)
			end
			if receipt.ProductId == 1361268917 then
				data.Skips100 += 1
				print(data.Skips100)
			end
		end
	end

(Data is just the table)
But for some reason it is really kind of multiplying. Lets say you purchase a product first time, it increments normally as 1. But slowly skips increase by 2, 3, 4,5 etc. I don’t really know the reason behind. Any help is appreciated! :slight_smile:

2 Likes

I suspect this is being caused by a memory leak, but I’ll need to see the whole script to confirm this theory if possible please

2 Likes

You are probably creating a different event everytime the player buys it. Only create the event once in the beginning

2 Likes

Ok, the whole script is long but you have asked for it so I’ll show you.

local BS = game:GetService("BadgeService")
local DS = game:GetService("DataStoreService"):GetDataStore("Stage")
local MS = game:GetService("MarketplaceService")

local module = require(game.ReplicatedStorage.Modules.DataModule)
local bans = {}
local function awardBadge(player, id)
	if not BS:UserHasBadgeAsync(player.UserId, id) then
		BS:AwardBadge(player.UserId, id)
	end
end

local function duplicateGui(player, amount, value)
	local clone = game.ReplicatedStorage.SkipButton
	clone.TextLabel = "+"..clone.Value.." Skips ".."(x"..clone.Amount..")"
	clone.Parent = player.PlayerGui.ScreenGui1.Inventory.Scroll
end

local checkpoints = workspace.Checkpoints

game.Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder") 
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player
	
	local Stage = Instance.new("IntValue")
	Stage.Name = "Stage"
	Stage.Value = 0
	Stage.Parent = leaderstats
	
	data = module:Load(player)
	
	Stage.Value = data.Stage
	
	
	
	player.CharacterAdded:Connect(function(char)
		print("Loaded")
		local hum = char:WaitForChild("Humanoid")
		wait()
		char:MoveTo(checkpoints[Stage.Value].Checkpoint.Position)
		hum.Touched:Connect(function(hit)
			if hit.Name == "Checkpoint" and hit.Parent.Parent == checkpoints then
				if tonumber(hit.Parent.Name) == Stage.Value + 1 then
					Stage.Value += 1
					data.Stage = Stage.Value
				end
			end
		end)
	end)
	awardBadge(player, 2130439793)
	if table.find(bans, player.Name) then
		print(bans)
		player:Kick()
	end
	
	MS.ProcessReceipt = function(receipt)
		if receipt.PlayerId == player.UserId then
			if receipt.ProductId == 1358881369 then
				print(data.Skips1 + 1)
			elseif receipt.ProductId == 1361212065 then
				data.Skips5 += 1
				print(data.Skips5)
			elseif receipt.ProductId == 1361212655 then
				data.Skips10 += 1
				print(data.Skips10)
			elseif receipt.ProductId == 1361268917 then
				data.Skips100 += 1
				print(data.Skips10)
			end
		end
	end
end)

game.Players.PlayerRemoving:Connect(function(player)
	module:Save(player, data)
end)

game:BindToClose(function()
	for i, plr in pairs(game.Players:GetPlayers()) do
		module:Save(plr, data)
	end
end)


If you want me to share the module as well, let me know.

2 Likes

Create the process receipt in a server script inside server script service. You can already get the player data with the function. Theres no need to create a different event for every player that joins

2 Likes

It’s inside ServerScriptService though. Also, I’ll do what you say.

1 Like

What i meant by that is only create the function at the top of the script

1 Like

It’s true that you’re creating the callback repeatedly and this is how to solve it:

local BS = game:GetService("BadgeService")
local DS = game:GetService("DataStoreService"):GetDataStore("Stage")
local MS = game:GetService("MarketplaceService")

local module = require(game.ReplicatedStorage.Modules.DataModule)
local bans = {}
local function awardBadge(player, id)
	if not BS:UserHasBadgeAsync(player.UserId, id) then
		BS:AwardBadge(player.UserId, id)
	end
end

local function duplicateGui(player, amount, value)
	local clone = game.ReplicatedStorage.SkipButton
	clone.TextLabel = "+"..clone.Value.." Skips ".."(x"..clone.Amount..")"
	clone.Parent = player.PlayerGui.ScreenGui1.Inventory.Scroll
end

local checkpoints = workspace.Checkpoints

game.Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder") 
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player
	
	local Stage = Instance.new("IntValue")
	Stage.Name = "Stage"
	Stage.Value = 0
	Stage.Parent = leaderstats
	
	data = module:Load(player)
	
	Stage.Value = data.Stage
	
	player.CharacterAdded:Connect(function(char)
		print("Loaded")
		local hum = char:WaitForChild("Humanoid")
		wait()
		char:MoveTo(checkpoints[Stage.Value].Checkpoint.Position)
		hum.Touched:Connect(function(hit)
			if hit.Name == "Checkpoint" and hit.Parent.Parent == checkpoints then
				if tonumber(hit.Parent.Name) == Stage.Value + 1 then
					Stage.Value += 1
					data.Stage = Stage.Value
				end
			end
		end)
	end)
	awardBadge(player, 2130439793)
	if table.find(bans, player.Name) then
		print(bans)
		player:Kick()
	end
end)

game.Players.PlayerRemoving:Connect(function(player)
	module:Save(player, data)
end)

MS.ProcessReceipt = function(receipt)
	if receipt.PlayerId == player.UserId then
		if receipt.ProductId == 1358881369 then
			print(data.Skips1 + 1)
		elseif receipt.ProductId == 1361212065 then
			data.Skips5 += 1
			print(data.Skips5)
		elseif receipt.ProductId == 1361212655 then
			data.Skips10 += 1
			print(data.Skips10)
		elseif receipt.ProductId == 1361268917 then
			data.Skips100 += 1
			print(data.Skips10)
		end
	end
end

game:BindToClose(function()
	for i, plr in pairs(game.Players:GetPlayers()) do
		module:Save(plr, data)
	end
end)
2 Likes

Your script is incorrect. You can’t check the player userid inside the process receipt because theres no player value. Instead get the player value and increase the skips data when player makes a purchase

3 Likes

My mistake, thanks for letting me know :sweat_smile:

I’ll work on a fix now

3 Likes

I don’t know but this is again repeatedly adding. I have removed Player.UserId line btw

1 Like

I found a way to get the correct player and their data now:

local BS = game:GetService("BadgeService")
local DS = game:GetService("DataStoreService"):GetDataStore("Stage")
local MS = game:GetService("MarketplaceService")

local module = require(game.ReplicatedStorage.Modules.DataModule)
local bans = {}
local function awardBadge(player, id)
	if not BS:UserHasBadgeAsync(player.UserId, id) then
		BS:AwardBadge(player.UserId, id)
	end
end

local function duplicateGui(player, amount, value)
	local clone = game.ReplicatedStorage.SkipButton
	clone.TextLabel = "+"..clone.Value.." Skips ".."(x"..clone.Amount..")"
	clone.Parent = player.PlayerGui.ScreenGui1.Inventory.Scroll
end

local checkpoints = workspace.Checkpoints

game.Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder") 
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local Stage = Instance.new("IntValue")
	Stage.Name = "Stage"
	Stage.Value = 0
	Stage.Parent = leaderstats

	data = module:Load(player)

	Stage.Value = data.Stage

	player.CharacterAdded:Connect(function(char)
		print("Loaded")
		local hum = char:WaitForChild("Humanoid")
		wait()
		char:MoveTo(checkpoints[Stage.Value].Checkpoint.Position)
		hum.Touched:Connect(function(hit)
			if hit.Name == "Checkpoint" and hit.Parent.Parent == checkpoints then
				if tonumber(hit.Parent.Name) == Stage.Value + 1 then
					Stage.Value += 1
					data.Stage = Stage.Value
				end
			end
		end)
	end)
	awardBadge(player, 2130439793)
	if table.find(bans, player.Name) then
		print(bans)
		player:Kick()
	end
end)

game.Players.PlayerRemoving:Connect(function(player)
	module:Save(player, data)
end)

MS.ProcessReceipt = function(receipt)
	local data = module:Load(game.Players:GetPlayerByUserId(receipt.PlayerId))

	if receipt.ProductId == 1358881369 then
		print(data.Skips1 + 1)
	elseif receipt.ProductId == 1361212065 then
		data.Skips5 += 1
		print(data.Skips5)
	elseif receipt.ProductId == 1361212655 then
		data.Skips10 += 1
		print(data.Skips10)
	elseif receipt.ProductId == 1361268917 then
		data.Skips100 += 1
		print(data.Skips10)
	end
end

game:BindToClose(function()
	for i, plr in pairs(game.Players:GetPlayers()) do
		module:Save(plr, data)
	end
end)

It’s strange that the issue is still happening for you even when removing that line though, I’ll see if I can find the cause

Edit: @Moneypro456789 Can I see the module as well please? I can’t seem to find something in the code that might cause the problem to happen now so I suspect it’s being caused by the module

1 Like

There are three problems with your code:

  1. You should only register a callback for ProcessReceipt once.
  2. ProcessReceipt must return a Enum.ProductPurchaseDecision.
  3. You don’t handle retrying properly so it could give the user the same purchase twice.
1 Like

Alr, there you go -

local DataModule = {}
local DS = game:GetService("DataStoreService"):GetDataStore("Stage")

local defaultData = {
	Stage = 0,
	Skips1 = 0,
	Skips5 = 0,
	Skips10 = 0,
	Skips25 = 0,
	Skips100 = 0,
	Robux = 0,
}


function DataModule:Load(plr)
	local data
	
	local success, err = pcall(function()
		data = DS:GetAsync(plr.UserId)
		if typeof(data) ~= "table" then
			local stage = data
			local newData = {
				Stage = stage,
				Skips1 = 0,
				Skips5 = 0,
				Skips10 = 0,
				Skips25 = 0,
				Skips100 = 0,
				Robux = 0,
			}
			DS:SetAsync(plr.UserId, newData)
			data = newData
			
		else
			return true
		end
	end)
	if success then
		return data
	end
end

function DataModule:Save(plr, data)
	local success, err = pcall(function()
		DS:SetAsync(plr.UserId, data)
	end)
	
	if success then
		print("Saved Data")
	end
end

function DataModule:UpdateRobuxSpent(plr, amount, data)
	data.Robux += amount
	local success, err = pcall(function()
		DS:SetAsync(plr.UserId, data)
	end)
end



return DataModule

The DataModule:Load is quite unusual, I don’t know why you’re setting the Skips back to 0 and resaving the data retrieved by it. All the function should need to do really is to attempt to retrieve the player’s data in an error-protected way and return the data result

Basically the game is quite old and I decided to shift on table saving recently. But the problem with that is if I directly set tables, it will overwrite and reset the old data. So I’m firstly seeing if it was a table or not, if it’s not, then it’s old, and I’m putting inside the tables.

1 Like

So how should I check the ProductPurchaseDecision though? Sorry but I’m not very experienced with MarketplaceService.

You should be more explicit with your data check. (talking about DataModule:Load)

The following code would pass your check, resulting in the stage being set to nil if the player is new to the game.

print(typeof(nil) ~= "table") -- true
1 Like

I think this line:

Needs to be changed like so:

if receipt.ProductId == 1358881369 then
		data.Skips1 += 1
		print(data.Skips1)

Plus @D1CEL is correct in saying that you’ll need to:

And also I suspect that when testing in Studio the data doesn’t have enough time to save properly before the server closes so you’ll need to replace the game:BindToClose section like so:

if game:GetService("RunService"):IsStudio() then
	game:BindToClose(function()
		task.wait(1)
	end)
else
	game:BindToClose(function()
		local x, y = 0, 0

		for _, player in game:GetService("Players"):GetPlayers() do
			x += 1

			task.spawn(function()
				module:Save(player, data)

				y += 1
			end)
		end

		repeat task.wait() until y == x
	end)
end

It’s rather complicated because there’s a lot of stuff that can go wrong even if your code works as intended (things can fail on Roblox’s end or the user could join another server before your callback returns). The docs for ProcessReceipt give you an example and some notes on stuff that could possibly go wrong as well as guarantees you have to work with.

2 Likes