PAYATHON 2026

CS-Cart Ajax 加载支付页后如何将 form 提交到 iframe

支付小周

结论

不要只在 $(document).ready() 中编写初始化逻辑。CS-Cart 通过 Ajax 替换支付步骤后,不会再次触发 ready。应监听 CS-Cart 的 ce.commoninit 事件,每次内容初始化时重新查找支付表单、创建 iframe,并绑定提交事件。

另外,form.target 对应的是 iframe 的 name,不是 id。提交表单时,设置好 target 后让表单正常提交即可。不要在 submit 回调中再次调用 .submit(),否则可能造成递归提交,也可能绕过 CS-Cart 原有的校验逻辑。

推荐实现

将下面的代码放进结账页面正常加载的 JavaScript 文件,不要放在会被 Ajax 反复替换的支付模板中:

(function (_, $) {
    'use strict';

    function initPaymentFrames(context) {
        var $context = $(context);
        var formSelector = "form[name^='payments_form_']";

        // find() 不包含 context 自身,因此同时检查 context。
        var $forms = $context.find(formSelector)
            .add($context.filter(formSelector));

        $forms.each(function () {
            var $form = $(this);
            var formName = $form.attr('name');

            if (!formName) {
                return;
            }

            // 使用字母开头的安全名称,避免 #2frame、.3dlayer 这类选择器问题。
            var suffix = formName.replace(/[^a-zA-Z0-9_-]/g, '_');
            var frameName = 'payment_frame_' + suffix;
            var frameId = 'payment-frame-' + suffix;

            var $frame = $form.find('#' + frameId);

            if (!$frame.length) {
                $frame = $('<iframe>', {
                    id: frameId,
                    name: frameName,
                    class: 'payment-frame',
                    src: 'about:blank',
                    frameborder: '0'
                }).hide();

                var $buttons = $form.find('.ty-checkout-buttons');

                if ($buttons.length) {
                    $frame.prependTo($buttons.parent());
                } else {
                    $frame.appendTo($form);
                }
            }

            // target 必须与 iframe 的 name 一致。
            $form.attr('target', frameName);

            // Ajax 重新初始化或支付方式切换时,避免重复绑定。
            $form
                .off('submit.paymentFrame')
                .on('submit.paymentFrame', function () {
                    // 在提交前再次设置,防止其他脚本修改 target。
                    $(this).attr('target', frameName);

                    $frame.show();
                    $form.find('.payment-3d-layer').show();

                    // 不调用 $(this).submit(),让当前提交继续执行。
                });

            $frame
                .off('load.paymentFrame')
                .on('load.paymentFrame', function () {
                    $('.ty-ajax-overlay, .ty-ajax-loading-box').fadeOut();
                    $(document).scrollTop(100);
                });
        });
    }

    /*
     * ce.commoninit 会在首次页面初始化以及 Ajax 内容更新后执行。
     * context 是本次需要初始化的页面区域。
     */
    $.ceEvent('on', 'ce.commoninit', function (context) {
        initPaymentFrames(context);
    });
}(Tygh, Tygh.$));

如果模板需要显示遮罩层,建议使用合法且便于选择的类名:

<div class="payment-3d-layer" style="display: none;">
    正在连接支付页面……
</div>

为什么原来的代码会失效

$(document).ready() 只会在整个文档首次加载完成时触发一次。step 4、支付方式区域等内容被 Ajax 替换后,会出现以下情况:

  • 新表单没有执行原来的 .attr('target', ...)。
  • 新表单没有绑定 submit 事件。
  • 原 iframe 可能随旧的支付区域一起被删除。
  • 更换支付方式后生成的是新的 DOM 节点,旧节点上的事件不会自动转移到新节点。

ce.commoninit 用于初始化 CS-Cart 首次加载和 Ajax 新增的内容,适合处理这种场景。

不要在提交事件中再次提交表单

原代码的执行顺序有问题:

if (!$form.hasClass('protex')) {
    $('.protex').submit();
}

$form.addClass('protex');

第一次执行时,当前表单还没有 protex 类,所以 $('.protex') 通常无法找到它。即使改成 $form.submit(),也会再次触发同一个 submit 处理器,造成递归或重复发送支付请求。

应在提交前设置 target,然后让当前提交继续执行:

$form.on('submit.paymentFrame', function () {
    $(this).attr('target', frameName);
});

如果确实需要通过其他按钮主动提交表单,可以在提交事件之外调用:

$form.get(0).requestSubmit();

不要在该表单自己的 submit 回调中再次调用它。

Ajax 类的影响

如果表单或 Buy Now 按钮被 cm-ajax、cm-ajax-force 等机制拦截,CS-Cart 可能将提交转换为 Ajax 请求。浏览器此时不会执行传统的“提交到 iframe”行为。

对于需要提交到 iframe 的支付表单:

  • 不要额外添加 cm-ajax-force。
  • 检查支付插件是否给实际的提交按钮或表单添加了 Ajax 类。
  • 如果其他 JavaScript 拦截提交并调用了 preventDefault(),只设置 target 不会生效,需要修改该支付插件的提交逻辑。

不要为了使用 iframe 就直接删除 CS-Cart 核心结账表单上的类。应先确认具体是哪个处理器拦截了提交,以免破坏订单创建、条款校验和支付前验证。

其他注意事项

iframe 必须设置 name:

<iframe
    id="payment-frame"
    name="payment_frame"
    class="payment-frame">
</iframe>

表单的 target 必须使用相同的值:

<form target="payment_frame">

也不建议继续使用 .3dlayer 或 #2frame 这类选择器。以数字开头的 class 或 id 用在 CSS 选择器中需要转义,在不同的 jQuery 版本中还可能直接引发选择器解析错误。使用 .payment-3d-layer 和 #payment-frame 会更稳妥。

如果 iframe 加载的是第三方支付网站,还需要确认对方没有通过 X-Frame-Options 或 CSP 的 frame-ancestors 禁止嵌入。如果支付服务明确禁止使用 iframe,即使表单的 target 设置正确,浏览器也会拒绝显示支付页面。此时只能改用该支付服务支持的跳转、弹窗或官方嵌入方案。

备注:内容仅供参考。