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 设置正确,浏览器也会拒绝显示支付页面。此时只能改用该支付服务支持的跳转、弹窗或官方嵌入方案。
备注:内容仅供参考。
