#What's the safest way to check against double developer purchases using a ProximityPromp?

1 messages · Page 1 of 1 (latest)

solemn shadow
#

I'm going to be adding developer products to my game, and there are some instances that can be respawned if someone pays the dev product. (This takes effect on the server as a global purchase.)

Basically what I'm asking about is an edge-case scenario where two people might trigger the proximity prompt, and one person actually pays to respawn the instance, while another person would wait to pay, and then they unnecessarily pay after it's been respawned.

I don't want this to cause issues and have people feel the need to file a complaint agianst me for stealing their robux or something silly like that.

A couple of thoughts I had are:
1. Is there a built-in method to check if the respawn condition has been met already when they press the "Buy" button, and this will cancel the transaction with a custom message, or a generic "purchase failed" prompt?

2. If the respawn condition is met, is there a way to force close the purchase prompt for the client so that they don't make a purchase by mistake?

3. The only real thought I had that I felt could protect me as a developer would be that if there are no such things that could cancel / decline a purchase would be I could just check if it's been respawned, and if it has then deposit a "Credit" into the user's DataStore that would allow for them to respawn the instance for free, since they would have previously purchased something with no benefit / promised good or services.

My idea for what this might look like would be the following:

#

local MSP = game:GetService("MarketplaceService")
local devProductId = 000000

local prox = script.Parent
local part = prox.Parent
local propertiesFolder = part:FindFirstChild("Properties")
local respawned = propertiesFolder:FindFirstChild("Respawned")

local instancesList = {
    MSP,
    devProductId,
    prox,
    part,
    propertiesFolder,
    respawned,
    }

local allInstancesList = not table.find(instancesList, nil)

if allInstancesList then
     prox.Triggered:Connect(function(player) wait(0.25)

        local creditsFolder = player:FindFirstChild("Credits")
        local credits = creditsFolder:FindFirstChild("Credits")

        if respawned:IsA("BoolValue") then
            if respawned.Value == true then
                --Not supposed to happen, don't know how to check and decline/cancel
                --The only way this would ever be reachd is if they were already in the purchase screen (I think)
                local success, productInfo = pcall(function()
                    return MSP:GetProductInfo(devProductId, Enum.InfoType.Product)
                end)

                if success and productInfo then
                    print("You purchased when it's been respawned, adding respawn credits to your account!")
                    credits.Value += 1
                end
            else
                --Supposed to happen, they purchase to respawn the instance.
                local success, productInfo = pcall(function()
                    return MSP:GetProductInfo(devProductId, Enum.InfoType.Product)
                end)

                if success and productInfo then
                    print("Purchase was a success!")
                end
                respawned.Value = true
            end
        else
            --TODO
        end
     end
end

Also don't mind if there are any errors or mistakes in the code, I typed that all in discord, not an actual code editor.

#

So yeah, if there's no way for us to actually cancel the purchase due to the respawn conditions being met (Either if another player had already respawned it, or if it just respawned naturally) then I'd like to reimburse the player.

I don't need help with the reimbursement logic, I'd just like to understand how I can go about handling purchases properly without making my players feel like i'm scamming them out of their robux without offering anything in return for edge cases like that

#

Also I thought about making it to where if someone activated the proximity prompt, then it would be disabled until they purchased it, to get around this grey area, though I'd hate for the idea if somoene activated it and sat on the purchase button just so other players wouldn't be able to purchase it

solemn shadow
#

For those who don't know how proximityprompts work, they don't actually handle your purchase. They jsut trigger the MSP event

old goblet
#

this code doesnt even check

#

if they b ought

#

if i see that right

solemn shadow
#

Yeah I wrote that all here

old goblet
#

you should use marketplace service with process receipt

solemn shadow
#

I wrote that all here off the top of my head

solemn shadow
old goblet
#

u can just use this

#

and keep like

#

recent purchases

#

i lowkey didnt read all

#

but if u want just once, just use gamepass

#

they can be deleted tho

solemn shadow
#

I see, I appreciate your time with this! I think I'll continue to use Dev Products since they can be purchased multiple times

old goblet
#

just keep table of recent purchases with timestamps

#

to avoid making 2 people buy

solemn shadow
# old goblet mb didnt fully read

So I tried your method, but it doesn't seem to work the way I need it to.

Basically let's say there are two players one both trigger the proximityPrompt, and both have this "Buy Item" menu open (Attached a screenshot)

If one player purchases it while the other player keeps their UI open, they can still purchase it even after it's been bought, and the instance was respawned.

Unless there's a way to just force close/cancel the "Buy Item" menu?

old goblet
#

so you could have like table/variable
That holds last time bought

When tries to buy -> checks if it was already bought in lets say last few seconds

if not, adds it to list/variable

if already in just fail it

#

or u could just check if respawning/respawned at the time process receipt was fiired

solemn shadow
# old goblet or u could just check if respawning/respawned at the time process receipt was fi...

I see what you're saying and this would work, but the thing is it wouldn't quite catch what I need it to.

For example my game is a game where you mine rocks. Someone could mine a rock and then pay for the dev product and then instantly mine it agian if they have a good enough pickaxe that does enough damage.

It would be entirely up to how the user handles mining the rock, how fast they do it, etc.

I could add checks like this, and then in the case that it does go through and it's respawned, then give them a credit that allows for a free respawn

old goblet
#

hmm

solemn shadow
#

Other than that, the time would be an unknown var

#

Since users could mine something instantly, but I wouldn't know what pickaxe they have

#

Which they could mine it anywhere from a couple of seconds to like 10 seconds

old goblet
#

maybe u could assign id to each rock and like have the purchase find next available rock thats respawnable?

#

or just

#

have the id kept somewhere of which rock hes trying to respawn

#

and see if it exists

#

if doesnt exists -> respawns
exists -> fails

solemn shadow
# old goblet if doesnt exists -> respawns exists -> fails

I like that idea, just one of the things is there are rebirths in-game, and some islands that you need a specific rebirth to get there. By the off-chance someone glitches into it or goes to one of those islands, the rocks there are locked behind a rebirth too.

So if I were to go with your idea then it would either respawn a rock the players would have no way of getting to, and thus feeling unfair/scammed if they paid for something but can't use it,

or if there were no rocks other than the one they mined that needed to be respawned then it would be wasted..

It's a tricky situation I have haha, I think a free respawn credit could be nice if something like that happened though, but..

#

(That's if some areas of the map are locked to them)

#

Also the rock positions are static, they only respawn in the place where they were mined. (They load there at the start of the server)

old goblet
#

is the proximity on the rock itself?

#

or well place it was on

solemn shadow
#

So the rock has a whole system

#

Let me get a screenshot

#

In the "Ore" Folder is the RockOre which is just a part that has the proximityPrompt

old goblet
#

so each rock has its own proximity?

solemn shadow
solemn shadow
#

I could fetch the instance of the rock as the parent

#

But then that would still leave the thing if a person purchases it while another one has their UI still open and then purchases it again

#

I could attach a 1-5 second thing to it tho

old goblet
#

couldnt u just give each rock id

#

name the proximity that way

#

and then keep track on server which proximity they last triggered

#

to know which rockid to respawn

#

if exists -> fails
if doesnt exist -> respawns

old goblet
solemn shadow
#

This is the script I have for my proximityPrompt, the one that handles the purchases

solemn shadow
#
local devProductId = 3564075251

local prox = script.Parent
local mineSpawn_RockModel = prox.Parent.Parent.Parent.Parent
local propertiesFolder = mineSpawn_RockModel:FindFirstChild("Properties")
local respawnTime = propertiesFolder:FindFirstChild("RespawnTime")
local respawning = propertiesFolder:FindFirstChild("Respawning")

prox.Triggered:Connect(function(player)
    MarketplaceService:PromptProductPurchase(player, devProductId)
end)

MarketplaceService.ProcessReceipt = function(receiptInfo)
    local player = game.Players:GetPlayerByUserId(receiptInfo.PlayerId)
    if not player then

        return Enum.ProductPurchaseDecision.NotProcessedYet
    end

    if respawnTime and respawnTime:IsA("NumberValue") then
        if respawnTime.Value == 0 then
            pass()
        else

            respawnTime.Value = 0
        end
    else
        pass()
    end
    
    return Enum.ProductPurchaseDecision.PurchaseGranted
end

if respawning:IsA("BoolValue") then
    
    respawning.Changed:Connect(function()
        if respawning:IsA("BoolValue") then

            if respawning.Value == true then
                prox.Enabled = true

            else
                prox.Enabled = false
            end

        else
            pass()
        end
    end)

    if respawning.Value == true then
        prox.Enabled = true

    else
        prox.Enabled = false
    end

else
    pass()
end```
old goblet
#

u prob shouldnt do it this way

#

process receipt would be fired like 20 times if u have the script cloned 20 times

solemn shadow
#

My pc just crashed

#

lol

old goblet
#

its cloned multiple times right? for each prox

solemn shadow
old goblet
#

i recomend having one single script handle it

#

the receipt part at least

solemn shadow
solemn shadow
# old goblet ye

Okay I'll do that then, I'll use a remoteEvent to call to ServerScriptService and pass the relevant info.

I think I'll go ahead and do the reimbursement of a single credit if they pay while it's currently respawned, but I'll prompt an in-game Gui to display relevant info like this

I'll just pass the price of the dev product and have them purchase it that way, then use a RemoteEvent and fire it to the server and call the purchase there

solemn shadow
# old goblet ye

That's pretty much what I meant with the menu, and so if they don't make a descision within the next 30-60 seconds, I'll just force close that menu in case they're AFK or just sitting on it

slim needle
#

To summarize, you cannot avoid this problem. The most you can do is record the purchase and make their next payment free. You can also force the client to close the payment menu if another user's payment goes through

#

@solemn shadow

solemn shadow
# slim needle To summarize, you cannot avoid this problem. The most you can do is record the p...

Yeah I got that already, I just figured I'd tie it to a menu. Also I'm unable to find where you can force close another client's menu if another client's payment goes through.

Also I'm already doing that by adding the credits, please see the attached image and the mention of the use of "Credits" I've spoken about in the post.

If they pay for something that has already been paid for, then they will be reimbursed a credit, making their next purchase free

#

Thank you for the summary, however it was unnecessary as I already understood most of that

mental spindle
#

wat is this

solemn shadow
mental spindle
solemn shadow
#

My bad, I thought you were like mocking or something 😳

mental spindle
#

can't close the purchase prompt but i did find this https://devforum.roblox.com/t/cancel-marketplaceservice-prompt/3313456/9 kinda janky hack workaround

solemn shadow
mental spindle
#

haven't tested it, but if you need to do this you probably messed up somewhere else

solemn shadow
#

I could very well disable the proximity prompt while someone is in a menu

mental spindle
solemn shadow
#

It's just, I hate the idea of someone gatekeeping it

mental spindle
#

for other players - again, just screengui.enabled=false or whatever you do to close it

#

fire a remote if you have to

solemn shadow
mental spindle
solemn shadow
#

The issue was when they're in this menu

#

Hmm

#

I copied the picture, not text

#

Stupid discord

#

Let me re-copy

mental spindle
#

i get the impression you're trying to deal with a devproduct that is purchased when it is not immediately relevant to gameplay

#

is that about right?

solemn shadow
#

Well no

mental spindle
#

e.g buy respawn rock devproduct, but the rock already exists, what do you do?

#

no?

solemn shadow
#

Wait

#

Yeah

#

Sorry I misunderstood your thing the way you said it

solemn shadow
#

Like two players get this prompt

#

One purchases it

#

Then the rock respawns and another players, while still in this menu, purchases it when the rock is already respawned

mental spindle
mental spindle
solemn shadow
#

Huh

mental spindle
#

that can happen

solemn shadow
#

Well then, that would suck

#

I guess in that case it would just be between them and roblox if they file for a refund, yes?

mental spindle
#

similarly, returning success to processreceipt does not guarantee the success is recorded, all purchases must pass through datastore/memorystore to fix that

mental spindle
solemn shadow
mental spindle
solemn shadow
#

I like to account for as many edge-cases as I can

mental spindle
#

lots of things can happen during a purchase

#

some devs are lazy with it and it just doesnt come up often but it should really be handled properly

solemn shadow
slim needle
solemn shadow
mental spindle
#

respawn credits is fine

solemn shadow
#

Or I could just disconnect them myself 💀

slim needle
mental spindle
#

in fact thats probably the best solution you can have

#

for my games i had to add that for pretty much every devproduct

#

that took a while

solemn shadow
mental spindle
#

e.g processreceipt is fired and they disconnect before it finishes

solemn shadow
#

But hiding it I could definitely do

#

Then just grey out the yellow button

#

Interactable = false

#

You know it

mental spindle
#

and then sell packs of 10 in the store 🙂

#

you have the currency for it already in place, may as well...

#

idk

#

or maybe its better just as its own thing on the side and players buy them one by one

solemn shadow
# mental spindle or just replace the yellow button with some indicator saying 'hey this one is "f...

So for my game there are a total of 16 rocks (Some are locked behind rebirths):
There are a total of 16 rocks.
Rock 1 = 2
Rock 2 = 4
Rock 3 = 6
Rock 4 = 8
Rock 5 = 10
Rock 6 = 12
Rock 7 = 14
Rock 8 = 16
Rock 9 = 18
Rock 10 = 20
Rock 11 = 22
Rock 12 = 24
Rock 13 = 26
Rock 14 = 28
Rock 15 = 30
Rock 16 = 32

I'm thinking Rock 1 will cost 2 robux to respawn while the last one will cost 32 robux.
A credit shop could be nice if people want to buy them prior, so then I could even keep it the way it is.

I would just need to see how much a single credit is worth compared to robux.

I could do 1 credit = 2 robux (Cheap) like how it is in my pricing chart.
So I'm thinking something like this:
Rock 1: 2 Robux → 1 credit
Rock 16: 32 Robux → 16 credits

#

Also I appreciate your help by the way, thank you Pyro

solemn shadow
mental spindle
#

and why is playing your game longer punished with things costing more

#

maybe that's just from this perspective

#

set a flat fee for all stages imo

solemn shadow
solemn shadow
mental spindle
#

maybe throw up indicator that this happened

solemn shadow
mental spindle
#

gemini suggests the same

Note: Roblox will keep retrying this purchase for up to 3 days. If it's never granted, the Robux is eventually refunded to the player.
not sure if this is hallucinated

#
mental spindle
solemn shadow
#

@mental spindle Well as a player if the purchase doesn't go through and I rejoin or join back later I wouldn't want the purchase to automatically go through if I dind't think it went through the first time

#

I see what people are saying, though if it's rejected/doesn't go through, I wouldn't want to keep trying the purchase if they decided that they later didn't want the purchase

#

Cuase it would have been our job as the game to handle that properly the first time

#

So they'd be well within their rights to change their mind

Also to clear any misunderstanding, I got this from your link:
"ProcessReceipt appears to not be invoked when the players rejoin despite the original transaction callback being NotProcessedYet days ago."

Based on that, is it safe to assume that we're the ones who need to trigger it?

solemn shadow
# mental spindle i think spend immediately if they have one without prompting next time they atte...

Also just from a player perspective, if let's say I have robux to spare, and I don't want to use credits, I'll sometimes feel like the game would be playing for me if I were to automate something on the player's behalf without their input. It's like if I wanna throw away 2 robux but keep my credits, I'm robbed of that choice

Plus at the bottom it does say how many credits you getback if the purchase goes trhough, but the criteria for the respawn has already been met

#

Also I never opposed to having a credits shop

#

I feel like there may have been a misunderstanding there and maybe you thought I was agianst it, when you asked "I have 3 credits but it cost 8 to respawn, how?"