Enum.ProductPurchaseDecision.PurchaseFailed

Currently, while we can detect if a purchase failed, we can’t really do anything about it other than hope the player knows they’ll get their money back.

So why don’t we have that control? I propose another Enum to the ProductPurchaseDecision enum called PurchaseFailed with the ID of 2.

When you return this, the ProcessReceipt loop is halted, and the money is instantly refunded.

Example code:

local MarketplaceService = game:GetService("MarketplaceService")
local HealthID, GoldID = 11111, 22222
local PurchaseHistory = game:GetService("DataStoreService"):GetDataStore("PurchaseHistory")
 
function MarketplaceService.ProcessReceipt(receiptInfo) 
	local playerProductKey = receiptInfo.PlayerId .. ":" .. receiptInfo.PurchaseId
	if PurchaseHistory:GetAsync(playerProductKey) then
		return Enum.ProductPurchaseDecision.PurchaseGranted --We already granted it.
	end
 
   	-- find the player based on the PlayerId in receiptInfo
	local player = game:GetService("Players"):GetPlayerByUserId(receiptInfo.PlayerId)
	if not player then -- Seems like we can't find the player... already left?
		return Enum.ProductPurchaseDecision.NotProcessedYet -- Can't process
	end
 
	if receiptInfo.ProductId == HealthID then
		-- handle purchase. In this case we are healing the player.
		player.Character.Humanoid.Health = 100
	elseif receiptInfo.ProductId == GoldID then
		-- handle purchase. In this instance we're giving the player 100 extra gold. 
		player.leaderstats.Gold.Value = player.leaderstats.Gold.Value + 100
	end
	-- record the transaction in a Data Store
	PurchaseHistory:SetAsync(playerProductKey, true)	
	-- tell ROBLOX that we have successfully handled the transaction (required)
	
	-- developers own ID for testing reasons
	if player.userId == 12345 then
		return Enum.ProductPurchaseDecision.PurchaseFailed
	else
		return Enum.ProductPurchaseDecision.PurchaseGranted
	end
end

In this example if your ID is 12345 then you will get your money back. Obviously this can be used alongside code errors when purchasing, but this also allows for mocks.

  • Yes I Do
  • No I Don’t
  • Other (please specify)

0 voters

3 Likes

This is not needed as we are already getting a way to test our products without spending money.

:smile:

1 Like

I’m regretting my example. This has a way more practical use than the example. Sometimes we know when we failed to give them what they’re owed. Times like these we can just refund them, instead of “sorry, but you’ll at least get your money back in 3 days when you’ve already downvoted the game and won’t even notice the refund came from our game”.

1 Like

Ah thanks for clearing that up, I don’t really see a lot of uses for this besides mock purchases. Could you provide more use cases?

I have a developer product that awards gold. When they purchase it, I first save old data to make sure the data is up to date. The data call fails for whatever reason. This makes my code return an error (pcall) soni don’t want to process the purchase until a data save is successful so I return purchase failed so that the user has a good player experience as opposed to feeling scammed.

I voted Other. IMO instead of having us manually have to take care of this, ProcessReceipt should just have better default behavior. Once the player leaves the game, no retries or waiting period – refund the money immediately and send a PM to them letting them know they got a refund (so they don’t think their ROBUX was lost)

Poll not clear, running in circles.

“Yes I do” - Wanted
“No I don’t” - Don’t want
“Other” - ??? other

The other is for people who want this, but not quite in this fashion (such as @EchoReaper) or similar.

This would be a good feature.

The system assumes that product puchases are completed instantaneously. This is a bad assumption.

Consider a game where the first one to spend 5 robux on a dev product is the winner of the round. With the current system, the purchases will go through for all participants, and in the worst case, n - 1 people will have just been stolen from.

A less contrived example is, In the amount of time it takes to purchase a powerup, the round for which the powerup would be used might have already ended. In which case, if their purchase goes through, they will have been stolen from.

2 Likes

This sounds like a really bad way to set things up. If you do want those kind of things, you should consider saving “power-up counters” for each player, have those increment when they buy something, and then only let them use one (and decrease the counter) if they are actually able to. You can also make it so that they can purchase and immediately use a power-up, but then if it happens to not be usable after purchase, throw it on their counter instead.

So, for example, say you have a survival game. And in that game, for about 2 minutes, you have the ability to purchase items. After that two minute interval, you are no longer allowed to purchase items. If you still have the prompt open on your screen after that interval, and either the purchasable items are no longer valid in the new context or you could probably use this as a way to cheat.

There are very valid reasons why you want to be able to deny a purchase.

You’re looking at the wrong problem. The real issue is that ROBLOX handles refunds miserably. They should happen instantly.

If we look at the second example you mentioned, and the datastore request finally goes through after the round ends, right now they’d have their money taken for 3 days or whatever it is. With proper refunds they’d get their money back as soon as ProcessReceipt returned nil. You’re wanting to be able to tell ROBLOX to refund their money, but that’s just manually doing the same thing that would be automatically accomplished with proper and instant refunds.

1 Like

I guess it’s a matter of perspective – personally I would take the purchase anyway and make sure it stays/becomes valid by means of a power-up counter, transferring it to the next round, etc. (i.e. in your 2 minute example I would not let the bought items take effect until the next time they can purchase items, and then give the user some kind of message/animation to reflect this) (actually I would just implement an in-game currency so that I can use purchase dialogs which I can close myself, much cleaner)

Okay. It’s wrong but okay.

Then auto close them, or have a different system. I made this thread, but this isn’t meant to times where you tell it to fail, rather, when something in ProcessReciept fails (should be in a pcall).