Developer Products Gives Old Rewards

Hello!

In my developer products handler script, it handles all of the developer products in the game. However I have one big problem. When the player buys the first developer product, it gives the reward and works fine. But, if the player buy another developer product, it gives that reward and the reward from the first developer product as well.
If the player buys a third developer product, it gives the reward and the rewards from the last two developer products on top of that. Etc.

Script:

local MarketplaceService = game:GetService("MarketplaceService")
local DataStoreService = game:GetService("DataStoreService")
local DailyStore = DataStoreService:GetDataStore("DailyRewardProgressTest4")
local Players = game:GetService("Players")

local PurchaseSuccessRem = game.ReplicatedStorage:WaitForChild("Events"):WaitForChild("Spinner"):WaitForChild("PurchaseSuccess")
local SkinPurchaseSuccessRem = game.ReplicatedStorage:WaitForChild("Events"):WaitForChild("TowerSkins"):WaitForChild("PurchaseSuccess")
local ThanksPurchaseRem = game.ReplicatedStorage.Events.ThanksPurchase.ThanksPurchase
local GrantRewardRem = game.ReplicatedStorage.Events.DailyRewards.GrantReward
local TrollGamepassRem = game.ReplicatedStorage.Events.Troll.TrollGamepass
local RevealAnswerRem = game.ReplicatedStorage.Events.PlaneGui.RevealAnswer
local AddRemoveTimeRem = game.ReplicatedStorage.Events.PlaneGui.AddRemoveTime
local AddTimeTextRem = game.ReplicatedStorage.Events.Rounds.AddTime.AddTimeText

local SkinsModule = require(game.ReplicatedStorage.Modules.TowerSkins.SkinModule)
local playerSkins = require(game.ReplicatedStorage.Modules.TowerSkins.PlayerSkinsData)
local DailyRewards = require(game.ReplicatedStorage.Modules.DailyRewards.DailyRewards)

TrollGamepassRem.OnServerEvent:Connect(function(player, GamepassID)
	MarketplaceService:PromptGamePassPurchase(player, GamepassID)
	
	MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player, purchasedPassID, wasPurchased)
		if purchasedPassID == GamepassID and wasPurchased then
			print(player.Name .. " just bought the gamepass!")
			TrollGamepassRem:FireClient(player)
			ThanksPurchaseRem:FireClient(player)
		end
	end)
end)



MarketplaceService.ProcessReceipt = function(receiptInfo)
	local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
	if not player then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	local timeout = 5
	local elapsed = 0
	while not playerSkins[player] and elapsed < timeout do
		task.wait(0.1)
		elapsed += 0.1
	end
	
	local success, err = pcall(function()
		local DevID = receiptInfo.ProductId
		
		

		-- 🎯 Handle Spin Purchase
		if DevID == 3403418065 then
			local SpinValue = player:FindFirstChild("Spins")
		
			SpinValue.Value += 1
			PurchaseSuccessRem:FireClient(player, 1)
			ThanksPurchaseRem:FireClient(player)
			print(player.Name .. " received 1 spin from purchase.")
			return Enum.ProductPurchaseDecision.PurchaseGranted
		elseif DevID == 3403418330 then
			local SpinValue = player:FindFirstChild("Spins")
		
			SpinValue.Value += 3
			PurchaseSuccessRem:FireClient(player, 3)
			ThanksPurchaseRem:FireClient(player)
			print(player.Name .. " received 3 spins from purchase.")
			return Enum.ProductPurchaseDecision.PurchaseGranted
		elseif DevID == 3405672749 or DevID == 3406367053 then
			local function getDailyData(player)
				local key = "User_" .. player.UserId
				local success, data = pcall(function()
					return DailyStore:GetAsync(key)
				end)

				if success and data then
					return data
				else
					return {
						LoginCount = 0,
						LastLoginDate = "",
						LastClaimedReward = nil,
						ClaimedRewards = {}
					}

				end
			end
			
			local function saveDailyData(player, data)
				local key = "User_" .. player.UserId
				pcall(function()
					DailyStore:SetAsync(key, data)
				end)
			end


			local data = getDailyData(player)
			local NextReward = nil
			local LastClaimedReward = data.LastClaimedReward
			
			if not LastClaimedReward then
				warn("No last claimed reward found for", player.Name)
				return Enum.ProductPurchaseDecision.NotProcessedYet
			end


			for i, reward in ipairs(DailyRewards) do
				if reward.Name == LastClaimedReward.Name then
					NextReward = DailyRewards[i + 1]
					break
				end
			end
			
			if NextReward then
				data.LoginCount = NextReward.Day
				saveDailyData(player, data)
			end
			
			
			ThanksPurchaseRem:FireClient(player)
			GrantRewardRem:FireClient(player)
			return Enum.ProductPurchaseDecision.PurchaseGranted
		elseif DevID == 3406365266 then
			local function getDailyData(player)
				local key = "User_" .. player.UserId
				local success, data = pcall(function()
					return DailyStore:GetAsync(key)
				end)

				if success and data then
					return data
				else
					return {
						LoginCount = 0,
						LastLoginDate = "",
						LastClaimedReward = nil,
						ClaimedRewards = {}
					}

				end
			end

			local function saveDailyData(player, data)
				local key = "User_" .. player.UserId
				pcall(function()
					DailyStore:SetAsync(key, data)
				end)
			end
			local data = getDailyData(player)

			ThanksPurchaseRem:FireClient(player)
		
			for i, reward in ipairs(DailyRewards) do
				if not table.find(data.ClaimedRewards, reward) then
					data.LoginCount = reward.Day
					saveDailyData(player, data)

					table.insert(data.ClaimedRewards, reward)
					GrantRewardRem:FireClient(player, reward)
					task.wait(0.5)
				end
			end
			return Enum.ProductPurchaseDecision.PurchaseGranted

		elseif DevID == 3410890795 then -- Reveal Answer
			RevealAnswerRem:FireClient(player)
			ThanksPurchaseRem:FireClient(player)
			print(player.Name .. " revealed the answer")
			return Enum.ProductPurchaseDecision.PurchaseGranted
		elseif DevID == 3410940798 then -- Add 10 seconds
			AddTimeTextRem:FireAllClients(10, "Add")
			AddRemoveTimeRem:FireClient(player, 10, "Add")
			ThanksPurchaseRem:FireClient(player)
			print(player.Name .. " Add 10 seconds")
			return Enum.ProductPurchaseDecision.PurchaseGranted
		elseif DevID == 3410941000 then -- Add 20 seconds
			AddTimeTextRem:FireAllClients(20, "Add")
			AddRemoveTimeRem:FireClient(player, 20, "Add")
			ThanksPurchaseRem:FireClient(player)
			print(player.Name .. " Add 20 seconds")
			return Enum.ProductPurchaseDecision.PurchaseGranted
		elseif DevID == 3410941139 then -- Add 60 seconds
			AddTimeTextRem:FireAllClients(60, "Add")
			AddRemoveTimeRem:FireClient(player, 60, "Add")
			ThanksPurchaseRem:FireClient(player)
			print(player.Name .. " Add 60 seconds")
			return Enum.ProductPurchaseDecision.PurchaseGranted
		end

		-- 🎯 Handle Skin Purchase
		local data = playerSkins[player]
		print(playerSkins[player])
		if data then
			print("Checking skin purchase for DevID:", DevID)

			for _, skin in pairs(SkinsModule) do
				if skin.DevID == DevID then
					data.Purchased[skin.Name] = true
					print(player.Name .. " purchased skin: " .. skin.Name)
					SkinPurchaseSuccessRem:FireClient(player, skin.Name)
					ThanksPurchaseRem:FireClient(player)
					local SkinStore = game:GetService("DataStoreService"):GetDataStore("PlayerSkinsTest")
					local key = "User_" .. player.UserId
					pcall(function()
						SkinStore:SetAsync(key, data)
					end)

					return Enum.ProductPurchaseDecision.PurchaseGranted
				end
			end
		end
	end)

	--[[if not success then
		warn("Error processing receipt for player " .. receiptInfo.PlayerId .. ": " .. err)
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end]]

	return Enum.ProductPurchaseDecision.NotProcessedYet
end

2 Likes

Change from Connect to Once

MarketplaceService.PromptGamePassPurchaseFinished:Once(function(player, purchasedPassID, wasPurchased)
		if purchasedPassID == GamepassID and wasPurchased then
			print(player.Name .. " just bought the gamepass!")
			TrollGamepassRem:FireClient(player)
			ThanksPurchaseRem:FireClient(player)
		end
	end)
1 Like

What you’re experiencing is the retry logic of processing devproducts:

ProcessReceipt has no time-based retry mechanism. If a user makes a purchase that returns a Enum.ProductPurchaseDecision enum of NotProcessedYet, the ProcessReceipt callback is only called again on the same server if:

The user successfully initiates another developer product purchase.
The user re-joins any server under the same experience.

This is happening because you are returning Enum.ProductPurchaseDecision.PurchaseGranted inside of a function inside of your ProcessReceipt function, not in the ProcessReceipt function itself.

Everything outside of the pcall is then ran unconditionally, so what’s actually being returned by marketplaceService.ProcessReceipt is always Enum.ProductPurchaseDecision.NotProcessedYet at the end of the function here:

To fix it, you need to return PurchaseGranted (I would suggest returning err if the pcall was successful, such as the following, or just removing the pcall altogether):

if success then
    return err --[[ returning err would allow us to return 
                    NotProcessedYet in case we returned that 
                    in our pcall function ]]
else
    warn(err)
    return Enum.ProductPurchaseDecision.NotProcessedYet
end
1 Like

To add to this, if pcall() is successful then the ‘err’ argument actually becomes whatever is returned within the protected call function! So assuming it works as intended, when successful ‘err’ will actually be the Enum.ProductPurchaseDecision variable.

So optionally your code could be structured as so:

local success, value = pcall(function()
	if productId == ... then
		return Enum.ProductPurchaseDecision.PurchaseGranted
	end
end)

if success then
	return value -- should be Enum.ProductPurchaseDecision.PurchaseGranted
else
	warn(value)
	-- if not successful, the returned value from the pcall is a debug traceback for the script error
	return Enum.ProductPurchaseDecision.NotProcessedYet
end