This is often called "double click protection" or "double submit protection".
One of the simplest is as advocated in the ecommerce chapter of the venerable panda book by Phillip Greenspun (http://web.archive.org/web/20010119104500/http://www.arsdigita.com/books/panda/ecommerce):
Reload 5 Times = 5 Orders?
The obvious way to implement an ecommerce system is the following:
- Serve user a static order form pointing to an "insert-order" page.
- When the user hits submit, the insert-order page will run and
- generate a unique order ID
- insert a row in the database
- run the credit card
- update the row in the database to say "authorization succeeded"
- print a thank-you page back to the user's browser
- send email thanking the user for the order
The problem with this approach is that various aspects of Step 2 may be slow, prompting the user to hit Reload. At which point another unique order ID is generated and another row is inserted into the database and the credit card number is authorized again. If the user hits Reload five times, it looks to the merchant's database just the same as five actual orders. The merchant can only hope that the user will notice the duplicate email messages and alert the merchant's customer service department.
There are potentially many ways of getting around this problem, but I think the simplest approach instead is to
- Serve user a dynamically generated order form that includes a unique order ID, pointing to an "insert-order" page.
- When the user hits submit, the insert-order page will run and
- insert a row into a database table with a primary key constraint on the order id. If the insert fails (Oracle won't allow two rows with the same primary key), catch it and look to see if there is already an order in the database with the same id. If so, serve the user an order status page instead.
- if the insert succeeded, proceed as above
You might want to use a shared memory store or local disk store if you're concerned about the scalability/performance of using a db, but if you're processing form data you're probably hitting a db already.