Skip Stage script working on PlayTest but not Public?

Just trying to make a simple Skip Stage (dev product) clickable Part, but finding that my script is only working in Play Test, not Public servers.

My Checkpoint Parts are in Workspace > CheckpointFolder > Checkpoint1, Checkpoint2, etc.
ClickDetector in SkipStage Part, and the code below inside the SkipStage Part

When a Player successfully buys the Dev Product, the expected outcome is their leaderboard Value (Checkpoint) increases by 1, and they are teleported to the next corresponding Checkpoint Part (if bought on Checkpoint 5, their Checkpoint Value increases +1, and are teleported to Checkpoint6 Part).

But yeah, main issue is this doesn’t work in a public server. Can’t figure out why but I’ve ran into this issue before aswell. Thanks, code below.

local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local CheckpointDataStore = DataStoreService:GetDataStore("CheckpointData2")

local productId = 3234396541 -- Change to your actual Developer Product ID
local checkpointFolder = game.Workspace:FindFirstChild("CheckpointFolder")

-- Function to handle product purchases
local function processReceipt(receiptInfo)
	local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
	if not player then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	if receiptInfo.ProductId == productId then
		local leaderstats = player:FindFirstChild("leaderstats")
		if leaderstats then
			local checkpointValue = leaderstats:FindFirstChild("Checkpoint")
			if checkpointValue then
				local newCheckpoint = checkpointValue.Value + 1
				local newCheckpointPart = checkpointFolder:FindFirstChild("Checkpoint" .. newCheckpoint)

				if newCheckpointPart then
					checkpointValue.Value = newCheckpoint
					player.Character:SetPrimaryPartCFrame(newCheckpointPart.CFrame + Vector3.new(0, 3, 0)) -- Teleport player slightly above the checkpoint
					CheckpointDataStore:SetAsync(player.UserId, newCheckpoint) -- Save to datastore
				end
			end
		end
	end

	return Enum.ProductPurchaseDecision.PurchaseGranted
end

-- Connect to ProcessReceipt event
MarketplaceService.ProcessReceipt = processReceipt

-- Click Detector to prompt purchase
script.Parent.ClickDetector.MouseClick:Connect(function(player)
	MarketplaceService:PromptProductPurchase(player, productId)
end)

My assumption is that ProcessReceipt is firing locally and the leaderboard isn’t picking up the change. Any ideas?

2 Likes

ProcessReceipt should be on the server only. Studio doesn’t always distinguish the server to client boundary which could explain why it works in studio but not in a live session.

Well in this instance, I presume that ProcessReceipt is happening on the serverside with the Script that is in the SkipStage Part (in Workspace), more of just a sneaking suspicion, but I do believe it’s being handelled on the serverside.

Any other ideas?

Bumping this, desparately need help!

no, you can prompt a purchase on the server

put a print statement on the first line of the process receipt and let me know if it prints

ProcessReceipt must be handled in a Server Script (Not a Local Script), only ONE script in your entire game can have a process receipt function - you must handle all products at once. Your script should also ideally be in ServerScriptService. Can you confirm all these things are true?

It seems (from your explanation) like you have multiple scripts running process receipt which won’t work in live servers as only one script can be registered for the process receipt callback. You should combine all your products into one script.

It doesn’t matter if you prompt the purchase on the server or client, Roblox will still process the receipt on the server.



I just went ahead and copied your script. The only thing I had changed around was I have leadersats and the devProductHandler inside of serverscriptstorage and not in the part, and have the only script in the part be the actually click detector prompt. It seemed to work for me inside a public server, unless its passed the purchase part witch I did not attempt.

Okay so judging by all this ProcessReceipt talk, I feel like that’s probably not the best method for detecting a Player’s developer product purchase, and yes, we have 3-4 different Developer Products all in separate scripts, so I could see why there would be issues.

So what would be the best way to detect that a Player has purchased a Developer Product, followed by my teleporting logic? Seems like ProcessReceipt is a little intense for what I’m trying to accomplish.

Yeah, it’s passed the purchase prompt, after a successful purchase the Player needs to be teleported and value changed but it isn’t happening.

I could see how combining the Skip Stage dev products into one Script could help, but ultimately it seems like I need to stay away from ProcessReceipt, looking for alternatives options on detecting a Player’s dev product purchase, any idea?

you could use MarketPlaceService.PromptProductPurchaseFinished, but the documentation on it says that it’s not really reliable and you should use ProcessReceipt. I would just combine everything into one function

Okay so in this instance I should setup the 4 developer products in one serverscript, then when a Player clicks one of the four specific SkipStage Parts (SkipStages are a physical clickable Part), it triggers the correct Dev Product and handles the purchase/Checkpoint change using ProcessReceipt

I feel like there is still issues with this; does this allow the Player to buy further Skips or are we running into ProcessReceipt issues again?

I currently have one SkipStage Product covering the first 3 stages, another SkipStage Product with a higher price covering stage 4-10 for example, and two more with higher pricing for harder stages. Should I set these up as individual products to avoid issues: example- Stage 1 has SkipStage1 DevProduct, Stage2 has SkipStage2 Product, etc?

Sorry not at my computer but thank you a ton for any help!

what’s the difference between the ProcessReceipt function of the different dev products? they all increase the stage by one. the only difference between them is, when prompting the purchases, you need to make sure that the player is in the right stage, but you can still have one ProcessReceipt function. like this:

local productId1 = 000000 -- change to ID for skip stages 1 - 3
local productId2 = 000000 -- change to ID for next product
local productId3 = 000000 -- change to ID for next product
local productId4 = 000000 -- change to ID for next product
local productIds = {
	[productId1] = {1,3}, -- stages 1 - 3
	[productId2] = {4,10}, -- stages 4 - 10
	[productId3] = {11,16},
	[productId4] = {17,25}
	-- change these to the stages you want
}

local MarketPlaceService = game:GetService("MarketplaceService")
MarketPlaceService.ProcessReceipt = function(receiptInfo)
	local Players = game:GetService("Players")
	local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
	if not player then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
	
	-- do checkpoint stuff
end

local CollectionService = game:GetService("CollectionService")
for _,skipStagePart in CollectionService:GetTagged("SkipStagePart") do
	local id = skipStagePart:GetAttribute("ProductId")
	local stageRange = productIds[id]
	local minStage,maxStage = unpack(stageRange)
	
	skipStagePart.ClickDetector.MouseClick:Connect(function(player)
		local checkPointValue = player.leaderstats.Checkpoint.Value
		if checkPointValue >= minStage and checkPointValue <= maxStage then
			MarketPlaceService:PromptProductPurchase(player,id)
		end
	end)
end

you don’t need a script for each part, just set the SkipStagePart tag and the ProductId attribute for each one

(Adding to post above)

If you’re going to have different ‘Skip stage’ parts with different prices etc…

I’d recommend having one ModuleScript doing the prompting and whats done after the prompting, it’d be much more efficient and better for organization.

Here’s an exemple of how you could start to set up said ModuleScript:
(This might not be too understandable as it’s pretty late, if you have anymore questions or find this unclear, don’t hesitate to ask.)


function module.ExempleMdl(devProductID, DestinationPrt)
	
	
	
end

return module

Yes it is. All (or at least the vast majority) of modern games use this, the only other way is ProductPurchaseFinished which can easily be manipulated by the client (exploiters). The best and only way to do it properly and safely is ProcessReceipt.