Every form in Drupal has the problem where a user can click Submit two times, triggering two form submissions (entity saves, etc),
This is especially problematic on checkout though, cause submitting the Review step places the order, sends emails, creates payments. That needs to happen only once.

So, we want a bit of JS that attaches itself to a submit button and disables it when it's clicked.
We at least need to attach it to "edit-actions-next" on checkout, but it's worth investigating whether we can do this for every submit button on checkout, and maybe even other commerce pages (such as cart/)

Related D7 module: https://www.drupal.org/project/hide_submit

Comments

bojanz created an issue. See original summary.

bojanz’s picture

Commerce 1.x version: https://github.com/drupalcommerce/commerce/blob/7.x-1.x/modules/checkout...
There's also a throbber added via CSS in https://github.com/drupalcommerce/commerce/blob/7.x-1.x/modules/checkout...

However, I don't see a reason to duplicate the submit button when we can just disable it.
This code is from 2010 so it might be working around a long gone browser bug. Will ask Ryan if he remembers.

sumanthkumarc’s picture

@bojanz i just found something in core/misc/form.js , where there is js code to prevent identical submissions twice?? doesn't this help us??


/**
   * Prevents consecutive form submissions of identical form values.
   *
   * Repetitive form submissions that would submit the identical form values
   * are prevented, unless the form values are different to the previously
   * submitted values.
   *
   * This is a simplified re-implementation of a user-agent behavior that
   * should be natively supported by major web browsers, but at this time, only
   * Firefox has a built-in protection.
   *
   * A form value-based approach ensures that the constraint is triggered for
   * consecutive, identical form submissions only. Compared to that, a form
   * button-based approach would (1) rely on [visible] buttons to exist where
   * technically not required and (2) require more complex state management if
   * there are multiple buttons in a form.
   *
   * This implementation is based on form-level submit events only and relies
   * on jQuery's serialize() method to determine submitted form values. As such,
   * the following limitations exist:
   *
   * - Event handlers on form buttons that preventDefault() do not receive a
   *   double-submit protection. That is deemed to be fine, since such button
   *   events typically trigger reversible client-side or server-side
   *   operations that are local to the context of a form only.
   * - Changed values in advanced form controls, such as file inputs, are not
   *   part of the form values being compared between consecutive form submits
   *   (due to limitations of jQuery.serialize()). That is deemed to be
   *   acceptable, because if the user forgot to attach a file, then the size of
   *   HTTP payload will most likely be small enough to be fully passed to the
   *   server endpoint within (milli)seconds. If a user mistakenly attached a
   *   wrong file and is technically versed enough to cancel the form submission
   *   (and HTTP payload) in order to attach a different file, then that
   *   edge-case is not supported here.
   *
   * Lastly, all forms submitted via HTTP GET are idempotent by definition of
   * HTTP standards, so excluded in this implementation.
   *
   * @type {Drupal~behavior}
   */
  Drupal.behaviors.formSingleSubmit = {
    attach: function () {
      function onFormSubmit(e) {
        var $form = $(e.currentTarget);
        var formValues = $form.serialize();
        var previousValues = $form.attr('data-drupal-form-submit-last');
        if (previousValues === formValues) {
          e.preventDefault();
        }
        else {
          $form.attr('data-drupal-form-submit-last', formValues);
        }
      }

      $('body').once('form-single-submit')
        .on('submit.singleSubmit', 'form:not([method~="GET"])', onFormSubmit);
    }
  };
nicola85’s picture

Status: Active » Needs review
StatusFileSize
new598 bytes
chrisrockwell’s picture

This has caused me a few late night headaches :D. If a customer clicks "Pay and Complete Purchase" 2x the order gets the payment, but it never gets moved to Fulfillment/Complete state, presumably because Auth.net, only seconds after the first payment, throws back an error, leaving the order in Draft status.

I'm attaching an alternative patch that adds the dependency to our commerce_checkout/form library as that's already attached to the forms. Another alternative is to roll just that function into Commerce, as I don't know hat impact these other behaviors will have.

subhojit777’s picture

Status: Needs review » Reviewed & tested by the community

Confirmed. The patch is working. However this patch does not works with Commerce Authorize.net accept.js https://www.drupal.org/project/commerce_authnet/issues/2813401. But that is okay. commmerce_authnet.accept.form.js handles the payment form submission in a different way.

The last submitted patch, 4: commerce_prevent_double_submit-2866638-4.patch, failed testing. View results

subhojit777’s picture

Status: Reviewed & tested by the community » Needs work
subhojit777’s picture

Status: Needs work » Reviewed & tested by the community

I cloned the latest version of commerce, and the patch cleanly applied.

  • bojanz committed 70ab01e on 8.x-2.x authored by chrisrockwell
    Issue #2866638 by chrisrockwell, nicola85, subhojit777, bojanz,...
bojanz’s picture

Status: Reviewed & tested by the community » Fixed

Tested with the example gateway and with Braintree. Committed #5. Thanks, everyone! A great team effort.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.