More Info On ProductPurchaseDecision Behaviour?

-------------------Question-------------------

I already did a quick search and I found no documentation on

Enum.ProductPurchaseDecision.NotProcessedYet

Enum.ProductPurchaseDecision.PurchaseGranted

My question is what happens when the Player leaves the game and re-enter?
There is no documentation on “Enum.ProductPurchaseDecision.NotProcessedYet” Behaviour as I already mentioned I think that it would be useful if more information can be provided.

I have no problem with the current Behaviour nor my scripts are broken because of this however I want to know if there is a way to Cancel the purchase?

Also found a similar thread back in the year 2016 (It’s a feature request)

-------------------Testing That I Have Been Doing------------------

DP = DevProduct

Results in the Output

TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(80065e26a9a26af98c4113d4c0576f4f) Price:80 "NotProcessedYet" >:( (x2)
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(94ff4079b355c28aed2330dd5bcb65f5) Price:80 "NotProcessedYet" >:(
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(80065e26a9a26af98c4113d4c0576f4f) Price:80 "NotProcessedYet" >:(
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(94ff4079b355c28aed2330dd5bcb65f5) Price:80 "NotProcessedYet" >:(
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(83e504802b26a36727e003a25761a953) Price:80 "NotProcessedYet" >:(
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(80065e26a9a26af98c4113d4c0576f4f) Price:80 "NotProcessedYet" >:(
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(94ff4079b355c28aed2330dd5bcb65f5) Price:80 "NotProcessedYet" >:(
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(83e504802b26a36727e003a25761a953) Price:80 "NotProcessedYet" >:(
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(6de5220d0a4b64fb55c6113bdbc45d99) Price:80 "NotProcessedYet" >:(
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(80065e26a9a26af98c4113d4c0576f4f) Price:80 "NotProcessedYet" >:(
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(94ff4079b355c28aed2330dd5bcb65f5) Price:80 "NotProcessedYet" >:(
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(83e504802b26a36727e003a25761a953) Price:80 "NotProcessedYet" >:(
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(6de5220d0a4b64fb55c6113bdbc45d99) Price:80 "NotProcessedYet" >:(
TestService: RuizuKun_Dev - 1 - Jack In The Box DevProId:(146144776) PurchaseId:(fe099fdbc4d7774a7e4f2b15611c2ffc) Price:80 "NotProcessedYet" >:(

The result was it will try to repurchase the DP until
“Enum.ProductPurchaseDecision.PurchaseGranted”
is returned if the player tries to purchase the DP again

That’s good because the game will automatically retry the purchase for the player, I understand why this Behaviour is important, because it’s okay if the Player gets x2 or x3 the amount of money for
only 1 purchase, it’s better to give them more than giving them nothing at all.

-------------------Use Case--------------------

I am working on a DP system that sells In-Game Items,
I have many prices for them as well, Godly 800R$, Ultimate 400R$, Legendary 250R$, etc

I know how to handle the purchases, that’s easy the hard part is dealing with Exploiters

but the problem is you can’t cancel the purchase, now I am not sure what will happen next if the Exploiter tries to do this again.

I can use a RemoteFunction for this too and then return the Item that the player selected but the it’s same problem, Exploiters can return a 800R$ item while buying a 50R$ item.

Does anyone have a cleaver solution for this?

2 Likes

You shouldn’t use development support for feature requests. Please refer to rule 15.1 of the devforum rules.

4 Likes

I know, but it’s not just a Feature Requests, I am asking for more information about the topic and saying that maybe adding this feature might solve the problem or someone might come up with a clever solution for my use case and so if many people agree that Enum.ProductPurchaseDecision.CancelPurchase should be added then I would PM the Top Contributors so the Feature Request can be created correctly.

4 Likes

But whether or not there was a development question doesn’t change the fact there’s a feature request in the post. Development support is for development support. Feature requests is for feature requests.

File your feature requests the same way everyone else does.

2 Likes

So what do you suggest I should do?

I am asking for more Information about the ProductPurchaseDecision Behaviour, and explaining my use case and how it would be useful to have Enum.ProductPurchaseDecision.CancelPurchase added.

2 Likes

I don’t have the answer to your question, but I’m sure you’d get the answer faster if your original post were edited to have more readable structure, as well as removing everything about the feature request.

When you file a feature request (see @Firefaul’s post above) you should follow the feature request format

This is enough off-topic though. If something is still confusing you, pm me.

2 Likes

Checking if the prices are equal should be enough to prevent exploitation. You should only grant the purchase if the prices are the same. There is no way for the exploiter to trigger a successful purchase of a high-cost item with a low-Robux product, regardless of how many times they try to trigger it.

The only remaining concern is what happens if an exploiter sets their selected item and the price matches up with the price an old (but smaller than 3 days old) purchase. This can only happen to exploiters and the exploiter is still getting what they requested for what they paid. Since your ProcessReceipt compares the developer product price with the selected item price, all they can do is match up the product price with a different in-game item of the same price. This may be a “bug”, but it only happens to exploiters and doesn’t affect any other players. Exploiters are still only getting a R$50 item for R$50, and it’s not possible for them to get a R$800 item for R$50.

Make sure you DPPrice is using receiptInfo.CurrencySpent.


Is there something I’m missing such that only comparing the prices won’t be enough? You only grant the purchase and give the item if the prices are the same. That should prevent all product price/item price mismatches.

2 Likes

I am checking both the price and the item, so checking if the Item is the same item as the request item and the price is equal to the correct price, I am just afraid that the Exploiter might change the Item they selected to a higher price after ProcessReceipt event, it’s fine if they try, I’ll just return Enum.ProductPurchaseDecision.NotProcessedYet , it would be nice if I can Cancel the purchase entirely tho, how about kicking the player?

Also switching an item with the same price is fine, it effects nothing, I just don’t want them to get an item with a higher price, that’s all of my concern.

1 Like

I don’t see any way they can match a high-cost item with a low-Robux product. Some example code:

MarketplaceService.ProcessReceipt = function(receiptInfo)
	local player = game.Players:GetPlayerByUserId(receiptInfo.PlayerId)
	if not player then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
	local selectedItem = getSelectedItem(player)
	if selectedItem.costRobux ~= receiptInfo.CurrencySpent then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
	-- give the player one of selectedItem
	-- grant the purchase
end

The only way I can see your code failing is if you don’t give the player the item in ProcessReceipt. If you give the player the item in ProcessReceipt then the item must have the same price as the product. This example code – when fully implemented – would give the item in ProccessReceipt and therefore be safe.


  • This won’t stop ProcessReceipt from firing with the purchase. It will fire if they join any new servers or make any new purchases within 3 days.
  • I almost never recommend kicking or banning players for stuff like this. You don’t want to risk false-positives. There is no way for the exploiter to get a high-cost item at a low-Robux price, so why punish them any more than that? Maybe if you don’t kick them they’ll stop trying to exploit (since it didn’t work) and become a useful player in your game.

Me and many others agree, but that’s sadly not possible.

2 Likes

I see you are right they can’t do anything other than fail at this point, Thank you for pointing this out

so now that my concern is solved, do you think that there should be more documentation available for us to learn more about

Enum.ProductPurchaseDecision.NotProcessedYet
&
Enum.ProductPurchaseDecision.PurchaseGranted

Behaviour?

idk how to do quotes, :confused:

Me and many others agree, but that’s sadly not possible.

Maybe I should make a Feature Request for Enum.ProductPurchaseDecision.CancelPurchase

1 Like

Some other things I’d like to address, but don’t directly relate to your problem:


This is not what’s happening here.

If a purchase fails, then it failed i.e. the player did not get their money. This is why Roblox retries it. If your ProcessReceipt is programmed right, then it will only fail if the player did not get their money. In that case, because the player already spent Robux on the item, it should be retried later when it might succees.


Product purchases have unique purchase ids. When a player makes a purchase, you’re supposed to save that id so that if ProcessReceipt fires with that id again, you can return PurchaseGranted since the purchase was already granted sometime in the past.

Notice the pattern:

Roblox will retry all failed ProcessReceipts from any products from within the last 3 days. If it’s not processed within 3 days, the player gets their money back and it stops retrying.

If I recall correctly, it retries all failed ProcessReceipts on join and on purchase of any developer product, but I could be wrong.


A fully implemented ProcessReceipt looks something like this:

workingPurchaseIds = {}

MarketplaceService.ProcessReceipt = function(receiptInfo)
	-- 0. if we are already working on this id, return NotProcessedYet. we don't want to grant it twice.
	if workingPurchaseIds[receiptInfo.PurchaseId] then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	-- 1. only work on in-game players
	local player = game.Players:GetPlayerByUserId(receiptInfo.PlayerId)
	if not player then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	-- 2. if the purchase was granted sometime in the past, return granted
	local playerData = getPlayerSaveData(player)
	if playerData.purchases[receiptInfo.PurchaseId] then
		return Enum.ProductPurchaseDecision.PurchaseGranted
	end

	-- 3. if the purchase is unsaved, try to save it instead of doing it again
	if playerData.unsavedPurchases[receiptInfo.PurchaseId] then
		local saveSuccess = savePlayerData(player)
		workingPurchaseIds[receiptInfo.PurchaseId] = nil
		if not saveSuccess then
			return Enum.ProductPurchaseDecision.NotProcessedYet
		end
		return Enum.ProductPurchaseDecision.PurchaseGranted
	end

	-- 4. check validity. this will vary per-product, per-game
	--  this time, i'll be using my previous example
	local selectedItem = getSelectedItem(player)
	if selectedItem.costRobux ~= receiptInfo.CurrencySpent then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	-- 5. start "working" on this PurchaseId. this is only a concern if we yield/wait anywhere.
	--  yield/waiting *will* happen for saving so we *do* always need this!
	workingPurchaseIds[receiptInfo.PurchaseId] = true

	-- 6. give the player the item. this will vary per-product, per-game
	--  this time, i'll use an example related to the above one
	local addSuccess = addToInventory(player, selectedItem)
	if not addSuccess then
		workingPurchaseIds[receiptInfo.PurchaseId] = nil  -- remove from working dictionary; we're no longer working on it.
		return Enum.ProductPurchaseDecision.NotProcessedYet  -- if any part of the process failed, we return not processed
	end

	-- 7. mark the purchase as unsaved but processed
	playerData.unsavedPurchases[receiptInfo.PurchaseId] = true
	
	-- 8. save the data. only grant the purchase if saving succeeds.
	--  the savePlayerData method will automatically move the purchase from unsavedPurchases to purchases in the saved data and in game if successful.
	local saveSuccess = savePlayerData(player)
	workingPurchaseIds[receiptInfo.PurchaseId] = nil
	if not saveSuccess then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end
	return Enum.ProductPurchaseDecision.PurchaseGranted
end

If you’re purchasing items that are tied to one specific product, you could just get the in-game item from the product id. To my understanding, this is how Roblox intended you use the API.


We need documentation on the whole process, what the guarantees are, and all of its behavior. We need a fully-implemented example, not a partial example like the ProcessReceipt wiki page has. We need a list of best practices for handling developer products. All of this information should be linked to from the ProcessReceipt wiki page, the ProductPurchaseDecision wiki page, the wiki pages for the decision enums, and the MarketPlaceService wiki page.

The example at Handling multiple developer products is much better, but I don’t think it’s complete and it’s not clear enough. I think the example on the ProcessReceipt wiki page should be removed entirely – incomplete examples will lead to improperly programmed ProcessReceipt functions. An incomplete example is a bad example.

Also, the Developer Products page has an even worse example. Purchases are too complex to tell developers “figure it out yourself” or “dissect this example that lacks clear steps and reasoning”.

10 Likes

I see, I agree with you that the examples are bad, I learned that I had to return the correct Enums or else I could get in trouble, thanks to my mentor.

I am also not going to mark this thread as Solved yet until someone provides more information about ProductPurchaseDecision Behaviour, not that your replies aren’t helpful, it’s just that I want as much attention to this thread as possible so we can get more documentation about the topic.

2 Likes

I think at the core of this is a great feature request. The platform really should have more differentiated states for purchases. I recommend @RuizuKun_Dev file that as a request using the normal procedure.

Updating the documentation is also a feature request - more or less. But no worries. Let me see if I can’t get someone to provide a little more detail in the ProductPurchaseDecision enum.

We also found this article, which may or may not be useful to you: http://wiki.roblox.com/index.php?title=Developer_Products_–_In-Game_Purchases

2 Likes

Thank you for linking the article.

I didn’t know that asking for more information about a topic is a Feature Request, it’s really hard to tell which topic goes to which sub category, especially when you are new to the Dev forum.

In my defense(not that my actions are illegal) I thought that posting here was a more appropriate approach because I was talking more about how to deal with my situation rather than asking for a Feature directly.

I will create a Feature Request for this topic.

So do I have to make 2 separate Threads or just one for both Documentation and New Enum feature?

4 Likes

I’m very confused and I don’t understand is what the problem is here. Shouldn’t you just PromptProductPurchase of the desired item and then process all requests on the ProcessReceipt callback using receiptInfo.ProductId to know what the player purchased? Doing this, how would an exploiter “change” the item bought?

1 Like

By using a RemoteEvent, you haven’t read the entire post… :confused:

2 Likes

I did read it. Let me try to put my concerns in another way. You have a PlayerSelectedItem variable. Is this variable a global one that is set on the RemoteEvent and read in the ProcessReceipt callback to know what the player bought? If so, why not use only receiptInfo.ProductId to know what the player bought?

1 Like

It’s not that simple… you haven’t read it all, and I don’t really want to explain it to you because of that.

and also your solution is bad, I’m not creating hundreds of DeveloperProducts for every single item that I want to sell, especially when some of them have the same price.

I am working on a DP system that sells In-Game Items,
I have many prices for them as well, Godly 800R$, Ultimate 400R$, Legendary 250R$, etc

Exploiter can PromptProductPurchase on a 50R$ item and then sends a RemoteEvent to change the Item to a 800R$ item

However I already got a solution to this, just checking if both Values matches, but that’s not all of it,
we are talking about More Documentation on ProductPurchaseDecision Behaviour & adding Enum.ProductPurchaseDecision.CancelPurchase, I am creating a Feature Request thread atm.

1 Like