You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何阻止客户重复点击Ajax表单提交按钮?OpenCart 2.0.3.1集成WorldPay支付模块重复下单问题求助

Fixing Duplicate Orders from Double-Clicking Checkout Button in OpenCart 2.0.3.1 WorldPay Module

Got it, let’s tackle that frustrating double-click issue causing duplicate orders and charges. The root problem is customers can click the confirm button multiple times before the page redirects, triggering repeated form submissions. Here’s a straightforward fix that adds submission state tracking and disables the button once it’s clicked:

Key Changes Needed

We’ll implement three core fixes:

  • Add a flag to track if a submission is already in progress
  • Disable the submit button and show loading text when clicked
  • Re-enable the button if there’s a payment error (so customers can retry without reloading)

Modified Script Section (Full Updated Code)

Replace the existing <script> block in your checkout page with this version. I’ve highlighted the critical changes with comments:

<script type="text/javascript"><!--
//Load Worldpay.js and run script functions
$.getScript("<?php echo $worldpay_script; ?>", function (data, textStatus, jqxhr) {
    Worldpay.setClientKey("<?php echo $worldpay_client_key; ?>");
    // disable new card form if existing cards
    <?php if (!empty($existing_cards)) { ?>
    $('#payment-new-form :input').prop('disabled', true);
    <?php } ?>
    // Set if token is reusable, remove first value when Worldpay update
    Worldpay.reusable = true;
    <?php if (isset($recurring_products)) { ?>
    Worldpay.reusable = true;
    <?php } else { ?>
    $('input[name=\'save-card\']').on('change', function () {
        if ($(this).is(':checked')) {
            Worldpay.reusable = true;
        } else {
            Worldpay.reusable = false;
        }
    });
    <?php } ?>
    Worldpay.templateSaveButton = false;
    Worldpay.useTemplate('payment-new-form', 'paymentDetailsHere', 'inline', function (obj) {
        var _el = document.createElement('input');
        _el.value = obj.token;
        _el.type = 'hidden';
        _el.name = 'token';
        document.getElementById('payment-new-form').appendChild(_el);
    });

    // --------------------------
    // NEW: Track submission state
    // --------------------------
    let isSubmitting = false;

    //Submit form
    $('input[type=\'submit\']').on('click', function (e) {
        // Prevent default form submit to take full control
        e.preventDefault();
        
        // Block clicks if we're already processing a payment
        if (isSubmitting) return;
        
        const $button = $(this);
        const originalButtonText = $button.val();
        
        // --------------------------
        // NEW: Disable button and show loading state
        // --------------------------
        isSubmitting = true;
        $button.prop('disabled', true).button('loading');
        
        var existing = $('input[name=\'existing-card\']:checked').val();
        if (existing === '1') {
            var form = document.getElementById('payment-existing-form');
            Worldpay.useForm(form, function (status, response) {
                if (response.error || status != 200) {
                    Worldpay.handleError(form, document.getElementById('payment-errors'), response.error);
                    // --------------------------
                    // NEW: Reset state on error so customers can retry
                    // --------------------------
                    isSubmitting = false;
                    $button.prop('disabled', false).button('reset').val(originalButtonText);
                } else {
                    // Submit the form only once
                    form.submit();
                }
            }, true);
        } else {
            // Handle new card submission with error recovery
            Worldpay.submitTemplateForm(function(status, response) {
                if (response.error || status !== 200) {
                    Worldpay.handleError(document.getElementById('payment-new-form'), document.getElementById('payment-errors'), response.error);
                    // --------------------------
                    // NEW: Reset state on error
                    // --------------------------
                    isSubmitting = false;
                    $button.prop('disabled', false).button('reset').val(originalButtonText);
                }
            });
        }
    });
});

//Delete a card
$('#button-delete').on('click', function () {
    var token = $('select[name=\'token\'] option:selected');
    if (confirm('<?php echo $text_confirm_delete ?>\n' + token.text())) {
        $.ajax({
            url: 'index.php?route=payment/worldpay/deleteCard',
            type: 'post',
            data: {token: token.val()},
            dataType: 'json',
            beforeSend: function () {
                $('#button-delete').button('loading');
            },
            complete: function () {
                $('#button-delete').button('reset');
            },
            success: function (json) {
                if (json['error']) {
                    alert(json['error']);
                }
                if (json['success']) {
                    alert(json['success']);
                    if (json['existing_cards']) {
                        token.remove();
                    } else {
                        $('input[name=\'existing-card\'][value=0]').click();
                        $('#choose-card').remove();
                        $('#payment-existing-form').remove();
                    }
                }
            }
        });
    }
});

// enable or disable forms based on exiting or new card option
$('input[name=\'existing-card\']').on('change', function () {
    if (this.value === '1') {
        $('#payment-existing-form').show();
        $('#payment-new-form').hide();
        $('#payment-new-form :input').prop('disabled', true);
        $('#payment-existing-form :input').prop('disabled', false);
    } else {
        $('#payment-existing-form').hide();
        $('#payment-new-form').show();
        $('#payment-new-form :input').prop('disabled', false);
        $('#payment-existing-form :input').prop('disabled', true);
    }
});
//--></script>

How It Works

  1. Submission Flag: The isSubmitting variable acts as a gatekeeper—once set to true, any subsequent clicks on the button are ignored.
  2. Button Disabling: When the button is clicked, we immediately disable it and show the loading text (using OpenCart’s built-in Bootstrap button methods) to give customers visual feedback that their payment is processing.
  3. Error Recovery: If WorldPay returns an error (like an invalid CVC), we reset the flag and re-enable the button so customers can correct their details and try again.

This should eliminate duplicate orders entirely while keeping the checkout flow user-friendly for customers who need to retry their payment.

内容的提问来源于stack exchange,提问作者ci_j

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 14:52:47