Game thinks player purchased dev product when they join game even though they didn't, this only happens if player purchased it last game(s)

There is bug where when player buys dev product and leaves game, then as soon as player joins game, game thinks player bought the dev product even though they didn’t. How do i fix this? This bug only happens in-game and not in Roblox studio so it’s hard to test it.

Code:

local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local redEvent = ReplicatedStorage:WaitForChild("RedEvent")
local targetShadesEvent = ReplicatedStorage:WaitForChild("TargetShadesEvent")
local productPurchase1 = ReplicatedStorage:WaitForChild("DevProductPurchase1")
local productPurchase2 = ReplicatedStorage:WaitForChild("DevProductPurchase2")
local MarketplaceService = game:GetService("MarketplaceService")
local passId = 1729789798 -- set to your dev product id
local passId2 = 1730160326
local mps = game:GetService("MarketplaceService")
local ps = game:GetService("Players")

local function onPurchaseComplete(player, productId)
	-- Check if this purchase has already been processed
	-- If not, process the purchase
	print(player.Name .. " has purchased the product.")
	-- Depending on the product, fire the appropriate client event
	if productId == passId then
		print("e")
		productPurchase1:FireClient(player)
	elseif productId == passId2 then
		print("er")
		productPurchase2:FireClient(player)
	else
		print(productId)
	end
end

local function onPurchaseComplete2(player)
	print(player.Name .. " has purchased the product.")
	productPurchase2:FireClient(player)
end

local function processReceipt(receiptInfo)
	local player = ps:GetPlayerByUserId(receiptInfo.PlayerId)
	if not player then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	if receiptInfo.ProductId == passId or receiptInfo.ProductId == passId2 then
		local success, result = pcall(onPurchaseComplete, player, receiptInfo.ProductId)
		if success then
			return Enum.ProductPurchaseDecision.PurchaseGranted
		else
			warn("Error processing receipt:", result)
		end
	end

	return Enum.ProductPurchaseDecision.NotProcessedYet
end

local function redFunction(player)
	mps:PromptProductPurchase(player, passId)
end

local function shadesOfTargetColor(player)
	mps:PromptProductPurchase(player, passId2)
end
-- MarketplaceService.ProcessReceipt
function MarketplaceService.ProcessReceipt(receiptInfo)
	local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
	if not player then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	local productId = receiptInfo.ProductId
	local amount = 0

	if productId == passId2 or productId == passId then 
		processReceipt(receiptInfo)
		return 
	end

	return Enum.ProductPurchaseDecision.PurchaseGranted
end

redEvent.OnServerEvent:Connect(redFunction)
targetShadesEvent.OnServerEvent:Connect(shadesOfTargetColor)

Does this only apply to the same server or all servers? If it’s all servers, then you’re using a DataStore that’s causing that to happen. Studio doesn’t keep DataStores by default, but that can be enabled in the experience settings.

I checked and it applies to all servers, how do you enable it in experience settings?

Then also make sure that the ID in Studio is your Roblox ID. If you’re using normal test mode, it should be. But if you’re using the server test mode, your IDs will be 0 or negative. Also I don’t believe that server test mode “players” will save data.

Word of warning: This also means that messing with your data in Studio, or anyone’s data for the matter, will impact their actual data in the live game.

1 Like

The bug is still happening, even after updating the code:

local function onPurchaseComplete(player, productId)
	-- Check if this purchase has already been processed
	-- If not, process the purchase
	print(player.Name .. " has purchased the product.")
	-- Depending on the product, fire the appropriate client event
	if productId == passId then
		print("e")
		productPurchase1:FireClient(player)
	elseif productId == passId2 then
		print("er")
		productPurchase2:FireClient(player)
	end
end

game.Players.PlayerAdded:Connect(function(player)
	local function processReceipt(receiptInfo)
		local productId = receiptInfo.ProductId
		local quantity = ProductToQuantity[productId]

		if quantity then
			if productId == passId2 or productId == passId then 
				--processReceipt(receiptInfo)
				onPurchaseComplete(player, productId)
				return 
			end
		else
			local amount = 0
			-- Determine the amount based on the product ID
			for i, id in ipairs(donationIDs) do
				if id == productId then
					amount = determineAmount(i) 
					if amount >= 0 then
						onDonationReceived(player, amount)  -- Updated call
						break
					elseif amount >= 1 then
						-- Handle server-specific logic if needed
					end
				end
			end
			if amount > 0 then
				handlePurchase(player, amount)
			end

		end

		return Enum.ProductPurchaseDecision.PurchaseGranted
	end

	game:GetService("MarketplaceService").ProcessReceipt = processReceipt
end)

On the documentation it says it’s possible for something to go wrong even when Enum.ProductPurchaseDecision.PurchaseGranted is returned, and therefore it can be called again. There is multiple issues with your script:

  1. You should store the receipt info in a DataStore, and check if the receipt appears in the DataStore before granting the purchase (if it is, then return Enum.ProductPurchaseDecision.PurchaseGranted because it has already been granted).

  2. Another important thing you’ve done wrong is set the process receipt callback in the Players.PlayerAdded event, which simply means whenever a player purchases a dev product, the player who last joined will recieve the grant, not the player who purchased the item.

What you should do is discard the code you’ve got so far and try again starting from the code shown in documentation code example.


EDIT AT 2024-01-17T09:07:00Z: Your original code would have actually worked except for this part here:

It is returning void instead of a purchase grant decision. Instead you should change it to this:

if productId == passId2 or productId == passId then 
	return processReceipt(receiptInfo)
end

However you should still save your purchase receipts to a data store and ensure it hasn’t already been processed.

1 Like

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