How to fix the t shirt purchase exploit for donation games

iwe’re wanting to understand how much they bought what they bought for yeah. or else i dont think this would be an issue.
in short
they change price to say 1 robux
then prompt and buy
they change price to say 1 million robux
then press okay

roblox reads the value as what they pressed buy for (what the prompt was for)
your game server reads the prompt as what the value was when they press okay

in theory though, if what oddcraft was saying is true and the price value holds true (which i believe it does, it’s just the updated that is chached and does not hold true) then this should be a fix…

although my quesstion is the rate limit. if we have multiple players in a game going through this process would it work?

2 Likes

You can test that your self with multiple of friends or devices. I’m not sure if its rate limited because PLS donate definetly might have the same syste,

3 Likes

but that is client sided so if we’re getting price from client they could manipulate that. is it not a better option to have an event fire to server letting the server know a player is making a purchase and then repeatedly checking the price to get what holds true? then if there isnt any pending transaction we just dont count it.

3 Likes

well you can do that too, just pass the asset id to server and get the product info from there.

3 Likes

preview ig??

Client

-- Client
local marketplace = game:GetService("MarketplaceService")
local shirtId = 8245844314
local remote = game:GetService("ReplicatedStorage"):WaitForChild("thingIdk")

script.Parent.MouseButton1Click:Connect(function()
	marketplace:PromptPurchase(game.Players.LocalPlayer,shirtId)
	remote:FireServer(shirtId)
end)

Server

-- Server
local remote = game:GetService("ReplicatedStorage"):WaitForChild("thingIdk")
local marketplace = game:GetService("MarketplaceService")
local players = game:GetService("Players")

players.PlayerAdded:Connect(function(plr)
	local leaderstats = Instance.new("Folder",plr)
	leaderstats.Name = "leaderstats"
	
	local num = Instance.new("NumberValue", leaderstats)
	num.Name = "Donated"
end)

remote.OnServerEvent:Connect(function(plr, shirtId)
	local info = marketplace:GetProductInfo(shirtId,Enum.InfoType.Asset)
	local oldPrice = info.PriceInRobux
	plr:SetAttribute("shirtId", shirtId)
	plr:SetAttribute("price", oldPrice)
end)

marketplace.PromptPurchaseFinished:Connect(function(player, assetId, ispurchased)
	if ispurchased then
		local shirtId = player:GetAttribute("shirtId")
		local price = player:GetAttribute("price")

		if shirtId == assetId then
			player.leaderstats.Donated.Value += price
			player:SetAttribute("shirtId", nil)
			player:SetAttribute("price", nil)
		end
	end
end)
3 Likes

i wonder if they can change price like you did before pressing buy too. i thought they did it after buy and before okay.

yeah and then you’d make a web request every 0.5 seconds or so to get price until purchase in creased to see if it had been modified.

3 Likes

You dont really have to do that if you just store the price whenever the prompt is shown to the player.

As you can see the old price got added instead of the new price which is 99999.

3 Likes

they could exploit that. but i think can just use getProductInfo because that price holds true. and just run that every 0.5 seconds to see if any modifications.

2 Likes

They can’t exploit that, you are setting everything on server.
And running getProductInfo every 0.5 seconds isnt a good practice.

Most of the devs use the same method for gamepass gifting system.

2 Likes

i don’t think i understand what you’re saying because it seems they can still exploit this by manipulating the price.

if they join the game with price of 1,000,000 it gets set to 1,000,000.
they change price to 1 robux then buy
they change back to 1,000,000 then press okay.

server will read 1,000,000

1 Like

No, server will not read it 1,000,000.
Whenever player clicks shirt button as shown in the video, the prompt will be shown.

Then it will send product id to the server, server will get the asset information (price) which is currently 1, then it will set that price to attribute on player’s instance.

Once someone clicks buy button, server will check the asset id and get the price that is stored in a attribute, and then it will update the donated value.

(we are not checking product price whenever the prompt is purchased)

3 Likes

they can just prompt themselves without the button though.

1 Like

But the server will ignore them.

Send remote and start prompt
Get price, listen for purchase
Presses ok
Uses saved price and removes listener

2 Likes

You can do if attribute doesnt exists then do nothing.

2 Likes

they could send the event themselves then prompt themselves. so i would go back to the loop consistently checking while purchase is being made.

1 Like

All I’m going to say is good luck. :face_with_hand_over_mouth:

I am curious to know what donation game you’re posting this on behalf of, though.

2 Likes

no specific one. just wanting to fix the issue. i think the loop for getProductInfo is a good fix for now though. still testing

1 Like

I believe GetProductInfo caches the price, so that won’t work.

2 Likes

then web request because i dont believe that price property is cached. just the updated is

1 Like

If I was a hacker, I could still fire game.ReplicatedStorage.thingIdk:FireServer(8245844314) after pressing ‘click to buy for 5 R$’ and before clicking ‘ok’ to make your script think that I purchased it for 999999 R$.

I could also disable your localscript, and make your remote fire whenever I want so tracking the lowest price after the remote starts isn’t an option.

Any other ideas besides looping from the start of game?

4 Likes