移动端 SecurionPay Checkout popup 被浏览器阻止
结论
JavaScript 无法绕过移动端浏览器的弹窗拦截机制,也无法在用户没有操作页面时强制打开 SecurionPay Checkout popup。
支付窗口需要由点击、触摸等明确的用户操作直接触发。在页面加载、定时器回调或异步请求回调中打开窗口,浏览器通常会将其视为非用户触发并予以拦截。用户不必手动允许所有弹窗,保留“点击后支付”的交互即可。
出现 Cannot read property 'focus' of null,通常是因为弹窗创建失败并返回了 null,但后续代码仍调用了窗口的 focus() 方法。
为什么页面加载时会被阻止
移动端浏览器通常只允许在短暂的用户激活状态内创建新窗口,例如:
clicktouchend- 其他可信的用户输入事件
下面的调用发生在页面初始化阶段,没有用户操作,很容易被浏览器拦截:
$(function () {
SecurionpayCheckout.open({
checkoutRequest: 'xyz=',
name: 'SecurionPay',
description: 'Checkout example'
});
});
改用 setTimeout()、Promise 或模拟点击,也无法可靠地绕过这项限制:
setTimeout(function () {
SecurionpayCheckout.open(/* ... */);
}, 1000);
浏览器判断的是事件是否来自真实用户,而不是代码有没有触发某个 click 事件。
推荐实现
在页面上保留一个明确的支付按钮,并在用户点击时立即调用 SecurionpayCheckout.open():
$(function () {
SecurionpayCheckout.key = 'key';
SecurionpayCheckout.success = function (result) {
// Send the payment result to your server for verification
console.log(result);
};
SecurionpayCheckout.error = function (errorMessage) {
console.error(errorMessage);
};
$('#payment-button').on('click', function () {
SecurionpayCheckout.open({
checkoutRequest: 'xyz=',
name: 'SecurionPay',
description: 'Checkout example'
});
});
});
<button id="payment-button" type="button">
Payment button
</button>
按钮文案可以使用“继续支付”或“打开安全支付页面”,让用户知道点击后会发生什么。
避免在异步回调后打开窗口
即使操作最初由用户点击触发,如果代码先执行耗时的异步操作,再打开 Checkout,浏览器提供的用户激活状态也可能已经失效:
$('#payment-button').on('click', function () {
fetch('/create-checkout-request')
.then(function (response) {
return response.json();
})
.then(function (data) {
// 此时用户激活状态可能已经失效
SecurionpayCheckout.open({
checkoutRequest: data.checkoutRequest,
name: 'SecurionPay',
description: 'Checkout example'
});
});
});
更稳妥的做法是提前从服务器获取 checkoutRequest,准备完成后启用支付按钮。用户点击按钮时,直接在点击处理函数中打开 Checkout:
let checkoutRequest = null;
fetch('/create-checkout-request')
.then(function (response) {
return response.json();
})
.then(function (data) {
checkoutRequest = data.checkoutRequest;
$('#payment-button').prop('disabled', false);
})
.catch(function () {
$('#payment-status').text('暂时无法初始化支付,请稍后重试。');
});
$('#payment-button').on('click', function () {
if (!checkoutRequest) {
return;
}
SecurionpayCheckout.open({
checkoutRequest: checkoutRequest,
name: 'SecurionPay',
description: 'Checkout example'
});
});
<button id="payment-button" type="button" disabled>
Payment button
</button>
<p id="payment-status"></p>
关于 focus 错误
浏览器阻止 window.open() 时,返回值通常是 null:
const popup = window.open('https://example.com');
if (popup) {
popup.focus();
} else {
console.warn('Popup was blocked');
}
如果 focus() 由 SecurionPay Checkout 内部调用,业务代码可能无法直接加上这个判断。解决问题的办法仍是让 SecurionpayCheckout.open() 在真实的用户操作中同步执行,不要放在页面加载流程或异步回调中。
可选方案
如果业务要求用户进入页面后立即开始支付流程,可以考虑在当前标签页中跳转,而不是打开弹窗:
window.location.assign(paymentUrl);
这种方式能否用于当前的 SecurionPay 集成,要看其接口是否提供可直接跳转的托管支付地址。仅修改现有 SecurionpayCheckout.open() 的参数无法实现,需要根据实际接入版本支持的 Checkout 流程进行确认。
无论使用弹窗还是页面跳转,都应将支付成功结果发送到服务端验证,不能只根据前端 success 回调认定订单已经支付。
备注:内容仅供参考。
