Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a good method for preventing a user from submitting a form twice?

I have a purchase page and I don't want the user to be able to refresh the page and resubmit the form once they get to the 'order complete' page because it automatically sets them up in our system via database values and charges their card via paypal (only want these to happen ONCE)... I have seen some sites that say 'Don't hit refresh or you will get charged twice!' but that is pretty lame to leave it open to possibility, what's a good way to only allow it to be submitted once or prevent them from refreshing, etc?

PS: I saw a few similar questions: PHP: Stop a Form from being accidentally reprocessed when Back is pressed and How do I stop the Back and Refresh buttons from resubmitting my form? but found no satisfactory answer... an ASP.NET MVC specific answer would be ideal too if there is a mechanism for this.

EDIT: Once they click submit it POSTS to my controller and then the controller does some magic and then returns a view with an order complete message, but if I click refresh on my browser it does the whole 'do you want to resend this form?' that is bad...

like image 834
MetaGuru Avatar asked Jan 21 '10 19:01

MetaGuru


2 Answers

The standard solution to this is the POST/REDIRECT/GET pattern. This pattern can be implemented using pretty much any web development platform. You would typically:

  • Validate submission after POST
  • if it fails re-render the original entry form with validation errors displayed
  • if it succeeds, REDIRECT to a confirmation page, or page where you re-display the input - this is the GET part
  • since the last action was a GET, if the user refreshes at this point, there is no form re-submission to occur.
like image 87
D'Arcy Rittich Avatar answered Sep 18 '22 11:09

D'Arcy Rittich


I 100% agree with RedFilter's generic answer, but wanted to post some relevant code for ASP.NET MVC specifically.

You can use the Post/Redirect/Get (PRG) Pattern to solve the double postback problem.

Here's an graphical illustration of the problem:

Diagram

What happens is when the user hits refresh, the browser attempts to resubmit the last request it made. If the last request was a post, the browser will attempt to do that.

Most browsers know that this isn't typically what the user wants to do, so will automatically ask:

Chrome - The page that you're looking for used information that you entered. Returning to that page might cause any action you took to be repeated. Do you want to continue?
Firefox - To display this page, Firefox must send information that will repeat any action (such as a search or order confirmation) that was performed earlier.
Safari - Are you sure you want to send a form again? To reopen this page Safari must resend a form. This might result in duplicate purchases, comments, or other actions.
Internet Explorer - To display the webpage again, the web browser needs to resend the information you've previously submitted. If you were making a purchase, you should click Cancel to avoid a duplicate transaction. Otherwise, click Retry to display the webpage again.

But the PRG pattern helps avoid this altogether by sending the client a redirect message so when the page finally appears, the last request the browser executed was a GET request for the new resource.

Here's a great article on PRG that provides an implementation of the pattern for MVC. It's important to note that you only want to resort to a redirect when an non-idempotent action is performed on the server. In other words, if you have a valid model and have actually persisted the data in some way, then it's important to ensure the request isn't accidentally submitted twice. But if the model is invalid, the current page and model should be returned so the user can make any necessary modifications.

Here's an example Controller:

[HttpGet]
public ActionResult Edit(int id) {
    var model = new EditModel();
    //...
    return View(model);
}

[HttpPost]
public ActionResult Edit(EditModel model) {
    if (ModelState.IsValid) {
        product = repository.SaveOrUpdate(model);
        return RedirectToAction("Details", new { id = product.Id });
    }
    return View(model);
}

[HttpGet]
public ActionResult Details(int id) {
    var model = new DetailModel();
    //...
    return View(model);
}
like image 44
KyleMit Avatar answered Sep 21 '22 11:09

KyleMit