The ability to decline Developer Product purchases

As a Roblox developer, it is too hard to decline a Developer Product purchase.

After 72 hours, the purchase will automatically cancel, but often I don’t want to grant a purchase, and I do not want my function to be called for the next 72 hours because of this. Maybe the player’s data isn’t loaded yet. Maybe the ID isn’t purchasable in this server.

As an example of the friction this causes, whenever someone buys a Developer Product, in many cases their data should be saved before the purchase is granted. This is annoying to do, because making a yield call will cause the purchase handler to keep getting called. You currently need to maintain an inventory of all purchase identifiers that are currently being handled to avoid reprocessing the same purchase multiple times.

Not being able to easily save player data after a purchase is made hurts newer developers, and is probably the culprit behind a fair amount of data loss since this is not obvious.

I propose ProductPurchaseDecision.PurchaseDeclined, which would decline the purchase immediately. Additionally, the callback would not be called again with the same purchase ID until a decision is returned, to allow for easy yielding in purchase handlers.

61 Likes

Why would you need to decline a purchase? Shouldn’t you just not prompt it in the first place if you don’t want the player to buy it?

10 Likes

My specific use case is that I want players to be able to bypass player voting and directly pick the map for the next round in my game. Many will try and do this during a short intermission so the situation could occur where many people are looking at the purchase prompt. I could have a lock that stops all players from seeing this prompt whilst another user is looking at the prompt.

However, from a revenue making standpoint, I want multiple people to see the prompt - not all of them will actually click Buy, and if I had a lock on this then actual purchases would be lost to people who look at the prompt for 10 seconds and then click Cancel.

With the addition of PurchaseDeclined, the first person to click ‘Buy’ on the prompt would choose the map and anyone else who clicked buy after would get their purchase declined. Right now these users still have their purchase appear to go through and then refunded a few days later. Thats going to frustrate kids who think they’ve lost their money.

My use case is quite specific but in most cases I’d reckon it would be far better for the end user to see their purchase denied than for it to go through and to not get their item/currency etc. because something failed internally and the dev decided to return ProductPurchaseDecision.NotProcessedYet .

25 Likes

For starter packs and offers shown to new players

Eg for the first purchase the player could can get $10000 in game for R$80 when it’s usually R$100

2 Likes

Your server should be able to prompt the correct purchase though shouldn’t it? Show the r$80 one if they’re new, r$100 if they’re not.

Making failed purchases a standard mechanism in your game is a bad idea. If I went to a game and constantly got purchases denied I’d leave that game because something in the logic and game design has gone wrong if you’re giving me the incorrect product prompt.

The client could prompt themselves to buy the discounted product though, when they should be paying full price

5 Likes

It’s been just under a year, no interest shown from Roblox.

To quote the ancient reply I didn’t have permissions to reply to at the time in case anyone else ever wondered, the server obviously needs to be able to decline purchases because you can’t trust the client or assume that its state is updated to the server’s. Not to mention you generally want to save a player’s data as soon as they make a purchase, and decline if that fails.

Right now, there are two options:

  1. Track each purchase ID and just keep deferring processing for 72 real world hours. Terrible solution. Probably most popular games do not do this.
  2. Assume success in all cases, probably amounting in some small minority of players losing Robux.

If Roblox cared as much about their users as they pretend to, this wouldn’t even need to be changed, it’d have been the original design. However, I am sure they have higher priorities than making sure users spending money get the product they paid for.

2 Likes

let me explain why this is needed

an exploiter can prompt purchase anything they want by themselves which means instead of waiting for the merchant to offer something, they can just get the better deal any time they want

of course I can write code to prevent this from happening say, check if the merchant is offering what the player is trying to purchase if so give 1.5x if not then 1x

but it’s such a pain to work with Developer products

generally I don’t care about Exploiters but the problem is even though they are exploiters I can’t just not give them anything for the purchase

and I can’t cancel the purchase otherwise this would be a lot easier

because of this I can’t implement any cleaver monetization designs and I’m only limited to offering mundane products

13 Likes

I don’t understand the points the original post is making and the purpose it serves doesn’t make sense with the term ‘decline.’ A declined purchase is a purchase I would expect to not be processed and for robux to be returned to the user. The real issue here is that Roblox is firing the function again before the previous time it got fired even returned anything. I cannot imagine a single case where that should happen.

However, the ability to actually say no to a purchase would be incredibly useful. Some use cases off the top of my head are:

  • Declining an attempted purchase of an item the user already owns, for whatever reason they may have obtained it between opening the purchase prompt and making their purchase
  • Declining in the case of two players attempting to gift the same thing to someone - only accepting the first one
  • Declining purchases that happen later than they were meant to be possible - users are able to open the prompt and leave it open for any amount of time resulting in a late attempted purchase

This is necessary for a good user experience as well as for the developer’s monetization. Forcing users’ robux to spend any time at all out of their account would easily cause them to believe their robux was lost and possibly that they just got scammed by the game they’re playing. Declining a purchase could inform the player immediately in-game that they were not charged, as well as returning their robux which they may then choose to spend on something else in your experience.

4 Likes

Hello. I’ve run into the need for this today.

My experience has a system where players can spend a few Robux to cause a small event to happen, upon which no one else can buy any event for a short amount of time while the current event is happening.
Right now, it is entirely possible that two players will prompt the purchase of an event at the same time (before anyone has purchased something thus disabling the ability to purchase), causing one person to potentially try to purchase it after the other already purchased it, and resulting in their Robux being taken unfairly (for a few days).

I can of course return NotProcessedYet, but this is horrible for UX given that I have to somehow explain to the user that their purchase was technically declined due to the current game conditions and their Robux will be refunded eventually. A way to mark the purchase as declined (thus achieving the same result as the user clicking Cancel, no Robux spent at all) is needed for my usecase.

6 Likes

After reading all of this, I still don’t understand how this is an issue? Why would you have the client prompt for a purchase and not the server? That seems to solve the exploit issue right away?
As for multiple clients buying, everything in Roblox is frame stepped, there is no way that two clients could ever time a button press and process to happen at exactly the same time in computer time I mean, down to the microseconds. Even if you script something like this to happen for testing, one still has to happen before the other. If two clients are buying “make it rain” and by the gods of RNG get it to process two purchases at the exact same time, the server could still queue the purchase so that one happens after the other.
It really sounds like a solution looking for a problem to me? :worried:

Please watch the video below. I tried to explain the issue how I see it, and why alternatives don’t really work well:

Apologies if my speech isn’t clear or is hard to understand.

Should a purchase return NotProcessedYet, Roblox will tell the player their purchase went through and continually attempt to process the receipt over the next 72 hours, finally refunding Robux after 3 days should it never return PurchaseGranted. To ensure this happens, you have to put the receipt ID in a Datastore or something similar and return NotProcessedYet every time Roblox attempts to process it. It’d be much simpler for developers (and better for players!) if we were able to just deny it compared to this.

5 Likes

Ok, I understand better now. While I understand how this would be helpful in your example above, I still have to fall back on the developer to plan for something like this. Just like a developer puts an “anti-bounce” function into their game to prevent players from double-firing weapons or double-jumping, this issue would fall under the same category. I already do this in my games for this exact same reason just didn’t realize it related to this topic.

The first player to start the market window should automatically flag the server to not let anyone else open that purchase window until the other user has finished. While I see the benefit of what is being requested, the queue system solution makes more sense from a game design perspective. To each their own of course, if they implement this, I won’t complain. :smiley:

1 Like

One idea is: you can get the players to purchase in-game currency, such as coins. And witht hat currency, they can buy thevote thingy. If someone else beats them to the punch, you can hide the prompt and forget about it.

Only issue is, you have to keep track of the player’s currency yourself via datastores, rather than Roblox hosting it for you.

2 Likes

Bumping this to say I can’t believe I didn’t mention this originally, but a far more prevalent and easier to see reason as to why declining developer product purchases should be added is the possibility that something goes wrong during the purchase on our end of things.

Let’s say that when someone purchases a dev product, it relies on an external server request or another player being in the game. Maybe the webserver suffers issues, or the other player leaves the game. These are both unavoidable errors (in some circumstances), and they’re ones that are easy to catch in code.

Being able to instantly decline the purchase and politely let the user know what went wrong would be a much better user experience.

1 Like

I came to the DevForums to see if anyone solved this exact edge case. I guess not. I’m not really sure why Roblox wouldn’t have added PurchaseDeclined in the first place …

A workaround for now is to use an inventory.

For example with choosing an event, if another player purchased at the same time, the last player will get a “Choose Event” item in their inventory and a message to explain what happened.

They can choose an event for free next time, whenever they want to use it.

This is reasonable to the player - their Robux isn’t gone so it doesn’t cause confusion - and the developer, as they’ll still profit from the transaction.

1 Like

Exactly this, and it should be marked as solution to the entire thread.

People should not be designing their experiences in a way that forces them to have to decline purchases in certain situations. It’s an anti-pattern, bad for you, bad for the player.

1 Like

There are still certainly rare cases in which declining a purchase is much more optimal. For example, in the case in which a dev-product will never become obtainable again (starter packs, limited time offers, maximum quantity items, etc.). Exploiters can prompt purchases whenever they want and force the server to go through the hoops of validating the sale of an exploiter despite the fact that I as a developer, are well aware that the sale will never succeed. Yes, I could add a debounce, but I still need to run the validation at least once every time the exploiter rejoins a different server over the 72-hour time frame.

Even when a player is not an exploiter we run into issues as players may believe we are ‘scamming them’ when they do not receive the item they wished to buy yet still appear to lose the associated Robux (for 72 hours) in the extreme scenario that a player becomes permanently inelegible for the sale between the time that they were prompted and the server validates it. Again, this may be a small and rare case. However, it would be massively distressing to a player.

1 Like

Or we can just… ask Roblox for a way to turn off developer products. :sweat_smile:

This is why we talk in problems rather than in solutions when requesting features. Having to manually decline purchases for the super super edge case you suggest is so convoluted, that’s absolutely a complete waste of my time as a developer. Just let me turn off the dev product so the prompt won’t even come up and Roblox system won’t even transact for those exploited calls.

The other edge case is also so rare that you’re likely not even going to see most creators writing code to protect that case. Just let the system refund those players after 72 hours, it’s not a big deal.