Best prevention for double clicks?

Hare

Senior Member
What is the best way to prevent double clicking on a $_POST submit form on browser games?

For example, a player collecting interest from the bank. I have a code that uses a timestamp to update itself when clicked, but it can still be clicked rapidly in succession and rake up extra interest regardless of the timestamp.

I tried Post/Redirect/Get method and saving tokens in a session, but for some reason neither of these worked for me. I could just be doing them incorrectly. 

I also tried Javascript, but that's easy to get around (just turn off JS).

So I'm wondering whether I'm just doing it wrong and should keep trying, or if these methods actually aren't viable for petsite/SIM features like banks? What is the best method to prevent double clicking and resubmission that someone in my situation should learn for their game?

 
How I have handled this in the past is to add a timestamp to a table (sometimes an int field for versioning), and make sure this gets updated everytime I update the data in that column. I also pass in the current value of this in the form submission.

So the process is:

  1. timestamp is a hidden form field set the current timestamp in the database.
  2. use where timestamp = $_POST['timestamp'] in my update, also updating timestamp as well.
  3. if rows updated is = 1, was successful, otherwise no.
Number 2's query would be something like this:

UPDATE bank SET balance = 200, has_collected_interest = 1, timestamp = NOW() where user_id = 123 and timestamp = ?


Because you are updating the timestamp at the same time you are updating the other data, any subsequent click will be voided because well, the timestamp will be different. Assuming you are using the "has_collected_interest" to drive your interface to allow them to see the button, a refresh will hide the button. Only one click will allow the update, subsequent clicks will not update any data.

It is called optimistic concurrency, and is normally used to keep two users from overwriting changes when editing the same data by allowing you to capture concurrency issues, but in this case can keep you from allowing someone to submit a form multiple times and actually committing the same action more then once.

 
It really depends on the feature/functionality. 

As digital suggested, if it is for a one-off feature or "daily" you will likely validate your form to decide whether to show it to the user. This same validation can be used on submit to check the user still has permission to view the form (this means a double click - the second click would be denied)

if however it is something where the user user should be able to do it twice, but you specifically want to stop a double click, let's say - when posting a forum post - what I actually do when a post has been submitted is to check the database to look for a post with the exact same content, posted by the same user id with the same topic id and posted in the last 30 seconds. If all this is true then the system just does not insert the content. This means a on a double click, the first click will submit the post and the second click will submit it but it won't save to the database. 

 
I second what Digital and Nate have said...

...plus with a combination of some javascript. (I know you said you can turn it off, but your average user won't). I.e disable the button on click and wait for the request to go through.

 
How I have handled this in the past is to add a timestamp to a table (sometimes an int field for versioning), and make sure this gets updated everytime I update the data in that column. I also pass in the current value of this in the form submission.

So the process is:

  1. timestamp is a hidden form field set the current timestamp in the database.
  2. use where timestamp = $_POST['timestamp'] in my update, also updating timestamp as well.
  3. if rows updated is = 1, was successful, otherwise no.
Number 2's query would be something like this:

UPDATE bank SET balance = 200, has_collected_interest = 1, timestamp = NOW() where user_id = 123 and timestamp = ?


Because you are updating the timestamp at the same time you are updating the other data, any subsequent click will be voided because well, the timestamp will be different. Assuming you are using the "has_collected_interest" to drive your interface to allow them to see the button, a refresh will hide the button. Only one click will allow the update, subsequent clicks will not update any data.

It is called optimistic concurrency, and is normally used to keep two users from overwriting changes when editing the same data by allowing you to capture concurrency issues, but in this case can keep you from allowing someone to submit a form multiple times and actually committing the same action more then once.
Thank you for explaining this, Digital! I just used it and it works like a charm! I was only using the has_collected_interest and not both with the timestamp. Now it's preventing double clicks.

I went with a timestamp for has_collected_interest with the date method Nate told me about to eliminate crons, not sure if that's the best way to go in this case, but it's workng great. There's also a 'you have 23 hours and 59 minutes until you can collect interest' message now.

Thank you Nate and PaulSonny, too! see how checking for duplicate data would work for forums and will keep that in mind! I did something similar with UNIQUE columns to prevent duplicate inserts, it worked out great last time.

Yeah most people won't know to turn off Javascript. I remember back in the day on Neopets, people discovered that they could disable Javascript and would 'blow up' forum topics by spamming the submit button. lol Then they fixed it.

 
Thank you for explaining this, Digital! I just used it and it works like a charm! I was only using the has_collected_interest and not both with the timestamp. Now it's preventing double clicks.
I am glad it worked, it is actually a very simple way to guarantee a single action per click of a submit button. It is actually one of those tricks I keep in my bag of goodies since I find I use it a lot for your use case.

(On a tangent but applicable to your Neopets comment)

One thing I got asked often by new Programmers starting was the difference between a Programmer and and Engineer was in Software. My response is this:

A programmer when crossing a one way street will only look one way for traffic, because he takes the traffic signs as fact (he trusts that what is written in code is absolute). An engineer will look both ways, because he doesn't trust that traffic will follow the signs (he assumes that code will fault).
Neopets was not terribly well written, partly because of the technical limits of the time it was created, but also because there was no real thought process into what could go wrong. It has bitten them many a time!

 
Great quote and way of thinking, Digital =D All those exploits were exciting on Neopets back in the day, it's nice we have better methods now. 

 
Back
Top