Help With Dev Products

Hey! How are you? Seems I can’t figure out why this is causing issues?

Local Script (Fires MPS)

	game.ReplicatedStorage.Ad.Ad:FireServer(player, script.Parent.Parent.Parent.InsertPlaceId.TextBox.Text)

Server Script

local MPS = game:GetService("MarketplaceService")

local function setAd(reciept)
	if(reciept.ProductId == (id)) then
		game.Workspace.AdBoard.Billboard.Screen.Decal.Texture = "" .. id .. "&width=768&height=432&format=png"
	return Enum.ProductPurchaseDecision.PurchaseGranted

game.ReplicatedStorage.Ad.Ad.OnServerEvent:Connect(function(player, id)
	MPS:PromptProductPurchase(player, 1196219157)

MPS.ProcessReceipt = setAd()


ServerScriptService.Dev Product Handler.Ad:6: attempt to index nil with 'ProductId'

Instead of setting the function setAd as the callback, you are making a call to the function setAd and putting the return value as the callback. While making that call to setAd (with no arguments), it crashes because the expected argument ‘reciept’ is missing.

Instead of

MPS.ProcessReceipt = setAd()

you could do:

MPS.ProcessReceipt = setAd

setAd is the function, setAd() is a call to that function (and so immediately executes it). A bit tricky, but hope it’s clear. You don’t want to execute the function yet, you want to set it as the callback so that it’s called later. The callback will then provide the receiptInfo data as argument.

How can I get the text in the player’s gui textbox? My brain is probably just being stupid right now.

Well, your variable ‘id’ in your setAd function is not defined yet. Because it is ‘nil’, the if-statement will be false. It seems what you are trying to do is to set the decal image to the thumbnail of the AssetId passed in your remoteEvent. However, I don’t think it’s safe to have a single devProduct for a lot of different assetIds.

Technically you could save the passed assetId and then use it when the purchase is processed, but these events may be far away in time (even after a relog). Perhaps the player after purchasing quickly starts another purchase but cancels it. It would lead to problems, the cancelled purchase may go through instead of the original one.

It may be safer to make a separate devProduct for each assetId. One way to then code it would be to hardcode the productId-AssetId combinations into a table.

Also note that the way your setAd function works may not be safe. If someone makes an unrecognized purchase, nothing happens, but the purchase is still marked as complete. It may be better to only return Enum.ProductPurchaseDecision.PurchaseGranted if the productId was a known Id and succesfully processed, and otherwise return Enum.ProductPurchaseDecision.NotProcessedYet


local validProducts = {
  "1196219157" = "12345", -- where 12345 is the assetId of the asset you are requesting a thumbnail of

local function setAd(receipt)
	local myAssetId = validProducts[tostring(receipt.ProductId)]
	if myAssetId then
		game.Workspace.AdBoard.Billboard.Screen.Decal.Texture = "" .. myAssetId .. "&width=768&height=432&format=png"
		return Enum.ProductPurchaseDecision.PurchaseGranted
	warn("Warning! Unknown product id: ", receipt.ProductId)
	return Enum.ProductPurchaseDecision.NotProcessedYet

This way, if someone makes a purchase of an unknown productId (not in your validProducts table), it is not marked as purchased. Usually, you would also want other things to happen when the purchase is granted, such as saving the purchase or its effects.

If the product is marked as ‘not processed yet’, the game will try again later. You probably want to make sure that all your product sales in your place are accounted for and marked as ‘processed’ (after its effects are applied), so that it doesn’t keep trying forever. That’s why I added a warning in that case, because it means something may have gone wrong, maybe you forgot to add that ID to the validProducts table.

By not marking it as a succesful purchase, you can fix the bug when you see the warning, and next time the player comes back they can still get their purchased reward.

Enum.ProductPurchaseDecision.NotProcessedYet is normally used after a pcall, for example when you save the purchase in datastores: if saving in datastores fails, the pcall catches the error, and you can write some code to in that case return NotProcessedYet.

Not a very clear answer, but I hope it helps! Also have a look at the documentation and examples there. Processing player purchases safely can be a bit difficult, but it’s ofc important to try and prevent that players spend Robux without getting what they paid for, as best you can.

To be complete, I interpreted that you mean ‘texture in the decal’ instead of ‘text in the players gui textbox’. My suggestion (as would your script) would change the texture on the server, for all players. Normally changing a players UI, or doing other things for only one player, would be done from a localscript. That would involve a remoteEvent (or for extra safety this might be a good place for a remoteFunction) to the client, when the purchase is made.

I’m not quite sure if this is what I needed. The “id” variable I originally used was the text in a textbox on the player’s ui.

I guess in that case this approach won’t work. Perhaps then it would be better to let players enter the assetId after they bought the dev product.

Thought note that you are responsible for the content people import into your game. This is a problematic approach, as you might get banned if someone uses an asset ID of an inappropriate model… Or even simply because your game is not safe in that regard, because people could do that.