Value reducing twice

Im making a shop for my drawing game but my problem is, when the user buy’s it. It reduced a token at they’re datastore. But what happen is, instead of reducing the value once, it reduced twice. But other problem is, it works with other’s even though its on a single script/ line.
The script i made:

Server Script

game.ReplicatedStorage.ShopBuy.OnServerEvent:Connect(function(player,Price,FaceColorName,ShopType)
	print(tonumber(Price))
	if player.Economy.Tokens.Value > tonumber(Price) then
		player.Economy.Tokens.Value = player.Economy.Tokens.Value - tonumber(Price)
		if ShopType == "Face" then
			player.FaceInventory:FindFirstChild(FaceColorName).Value = true
		elseif ShopType == "Color" then
			player.ColorInventory:FindFirstChild(FaceColorName).Value = true
		end
		game.ReplicatedStorage.ShopBuy:FireClient(player,ShopType)
		else return
	end
end)

Local script

while task.wait(1) do
	for _, p in pairs(script.Parent:GetChildren()) do
		if p:IsA("Frame") then
			if game.Players.LocalPlayer.FaceInventory:FindFirstChild(p.Name).Value then
				p.Purchase.Text = "BOUGHT"
			end
			local function Purchase()
				local Price = p.Purchase.Text
				local FaceColorName = p.Name
				local ShopType = "Face"
				if tonumber(Price) ~= nil then
					game.ReplicatedStorage.ShopBuy:FireServer(Price,FaceColorName,ShopType)
					local function Return(ShopType)
						if ShopType == "Face" then
							script.ClickSound:Play()
							p.Purchase.Text = "BOUGHT"
						end
					end
					game.ReplicatedStorage.ShopBuy.OnClientEvent:Connect(Return)
				end
			end
			p.Purchase.MouseButton1Click:Connect(Purchase)
		end
	end
end

Maybe, try removing it from a while loop. Use functions outside a while loop instead!

i don’t know why i did this on the script, i don’t remember my explanation lol but i made it while task.wait(1) do and I’m using for do so it keeps changing buttons. So when the user clicked the selected button in for do it will run the script. Sorry I’m not good at explanation

local rs = game:GetService("ReplicatedStorage")
local shopBuy = rs.ShopBuy

shopBuy.OnServerEvent:Connect(function(player, Price, FaceColorName, ShopType)
	local Price = tonumber(Price)
	if Price then
		if player.Economy.Tokens.Value > Price then
			player.Economy.Tokens.Value -= Price
			if ShopType == "Face" then
				player.FaceInventory:FindFirstChild(FaceColorName).Value = true
			elseif ShopType == "Color" then
				player.ColorInventory:FindFirstChild(FaceColorName).Value = true
			end
		else
			return
		end
	end
end)

Server script looks fine, added some slight changes.

local rs = game:GetService("ReplicatedStorage")
local shopBuy = rs.ShopBuy
local player = game.Players.LocalPlayer
local parent = script.Parent
local sound = script.ClickSound

for _, p in ipairs(parent:GetChildren()) do
	if p:IsA("Frame") then
		if player.FaceInventory:FindFirstChild(p.Name).Value then
			p.Purchase.Text = "BOUGHT"
		end
		p.Purchase.MouseButton1Click:Connect(function()
			local Price = p.Purchase.Text
			local FaceColorName = p.Name
			local ShopType = "Face"
			local Price = tonumber(Price)
			if Price then
				if player.Economy.Tokens.Value > Price then
					shopBuy:FireServer(Price,FaceColorName,ShopType)
					if ShopType == "Face" then
						sound:Play()
						p.Purchase.Text = "BOUGHT"
					end
				end
			end
		end)
	end
end

Attempt at fixing the server script.

You don’t need the FireClient()/OnClientEvent bit.

Maybe the event is firing twice because of the detection to fire the event on the client. The best way to fix this is to use a debounce system. Here is an example.

local debounce = false

game.ReplicatedStorage.ShopBuy.OnServerEvent:Connect(function(player,Price,FaceColorName,ShopType)
	print(tonumber(Price))
	if player.Economy.Tokens.Value > tonumber(Price) and debounce == false then
		debounce = true
		player.Economy.Tokens.Value = player.Economy.Tokens.Value - tonumber(Price)
		if ShopType == "Face" then
			player.FaceInventory:FindFirstChild(FaceColorName).Value = true
		elseif ShopType == "Color" then
			player.ColorInventory:FindFirstChild(FaceColorName).Value = true
		end
		game.ReplicatedStorage.ShopBuy:FireClient(player,ShopType)
		wait(1) --How long until you can press the button again.
		debounce = false
	else return
	end
end)

I found out your script had a huge vulnerability where exploiters could buy stuff for free. I tried fixing everything I could and completely removed the while loop(now the script is signal based):

--Server Script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--script wont run until it finds the remote
local ShopBuy = ReplicatedStorage:WaitForChild("ShopBuy", math.huge)

--always store item prices on the server so exploiters can't manipulate them
local Prices = {
	["Faces"] = {
		["Item1"] = 10,
		["Item2"] = 20
		--etc.
	}
}

function GetPrice(ShopType, itemName)
	local category = Prices[ShopType] 
	if not category then return nil end 
	local item = category[itemName]
	return item 
end

ShopBuy.OnServerEvent:Connect(function(player, ShopType, itemName)
	local Economy = player:FindFirstChild("Economy") 
	local Tokens = Economy:FindFirstChild("Tokens") 
	local itemPrice = GetPrice(ShopType, itemName)
	if itemPrice and Tokens.Value >= itemPrice then 
		local Success, Error = pcall(function()
			if ShopType == "Face" then
				player.FaceInventory:FindFirstChild(itemName).Value = true
			elseif ShopType == "Color" then
				player.ColorInventory:FindFirstChild(itemName).Value = true
			end
		end)
		if Success then 
			--only remove coins if the request is a success
			Tokens.Value -= itemPrice 
			ShopBuy:FireClient(player, ShopType)
		end
	else 
		--if they passed the client check, without the amount of currency needed, we got em
		player:Kick("Exploiting!")
	end
end)
--Local script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local Player = Players.LocalPlayer 
local inventory = Player:WaitForChild("FaceInventory")

local frames = script.Parent

function GetFrame(item)
	local frame = frames:FindFirstChild(item.Name)
	if frame and frame:IsA("Frame") then return frame end
end

function Purchase(frame) 
	local Price = tonumber(frame.Purchase.Text)
	local FaceColorName = frame.Name 
	local ShopType = "Face" 
	
	local Economy = Player:FindFirstChild("Economy")
	if not Economy then return end 
	local Tokens = Economy:FindFirstChild("Tokens")
	if not Tokens then return end 
	
	--this is a sanity check, if a user fires the remote without enough currency, they are exploiting.
	if Price and Tokens.Value >= Price then 
		ReplicatedStorage.ShopBuy:FireServer(ShopType, FaceColorName)
		local connection 
		connection = ReplicatedStorage.ShopBuy.OnClientEvent:Connect(function()
			script.ClickSound:Play()
			frame.Purchase.Text = "BOUGHT"
			--always disconnect signals which you want to use once.
			connection:Disconnect()
		end)
	end
end

function stateChanged(frame, state)
	if frame and state then 
		frame.Purchase.Text = "BOUGHT"
	end
end

for i, face in pairs(inventory) do 
	local frame = GetFrame(face)
	stateChanged(frame, face.Value)
	face.Changed:Connect(function()
		stateChanged(frame, face.Value)
	end)
	frame.Purchase.Activated:Connect(function()
		Purchase(frame)
	end)
end

Also this may break the script responsible for other types of items(if that’s the case you could try rewriting the other script or fusing it with this one).

This works but it is still reduced twice. My previous script was 50 > 10 but your script makes it 50 > 30. The price is only 10 tokens but it reduced 20

this could work but, the other player that buys it won’t be able to buy it because it’s on debounce.

That’s the point. The debounce is on the server so once a player touches it, nobody in the server will be able to buy for a second, then debounce will be false and everything will work again.