如何在 cordova webView 返回后检测支付成功事件?
结论
不要用 Onsen UI 的 postpop 判断支付是否成功。postpop 只能说明导航栈中的页面已经弹出,与支付网关的交易状态无关。
建议按以下流程处理:
- 支付完成后,由支付网关跳转到预设的回调 URL。
- 在 Cordova WebView 中监听
loadstart或loadstop,通常可使用cordova-plugin-inappbrowser。 - 检测到回调 URL 后,关闭支付窗口。
- 调用服务端接口查询并核验订单状态,再向用户展示支付结果。
用户按下返回键,只能说明支付页面已关闭,不能证明交易成功。
为什么 postpop 没有生效
postpop 是 Onsen UI 的导航事件,通常在 ons-navigator 执行 popPage() 后触发。如果支付页面通过独立的 InAppBrowser 或 WebView 打开,它就不在 Onsen 的页面栈中,关闭时自然不会触发 postpop。
即使用户返回时触发了某个页面事件,也不能据此判断支付成功。此时可能存在多种情况:
- 支付已经成功;
- 支付失败;
- 用户取消了支付;
- 支付仍在处理中;
- 页面加载失败,但服务端已经完成扣款。
因此,页面返回事件可以用来触发订单状态查询,但不能直接把订单标记为成功。
推荐实现:监听支付回调 URL
下面的示例使用 cordova-plugin-inappbrowser 打开支付页面,并假设支付网关支持配置返回地址。
function openPayment(paymentUrl, orderId) {
const paymentWindow = cordova.InAppBrowser.open(
paymentUrl,
'_blank',
'location=yes'
);
let callbackDetected = false;
paymentWindow.addEventListener('loadstart', async function (event) {
if (!isPaymentCallback(event.url)) {
return;
}
callbackDetected = true;
paymentWindow.close();
await refreshPaymentStatus(orderId);
});
paymentWindow.addEventListener('exit', async function () {
// 用户手动关闭或按返回键时,也向服务端确认一次。
if (!callbackDetected) {
await refreshPaymentStatus(orderId);
}
});
}
function isPaymentCallback(url) {
try {
const parsedUrl = new URL(url);
return (
parsedUrl.origin === 'https://example.com' &&
parsedUrl.pathname === '/payment/return'
);
} catch (error) {
return false;
}
}
不要只用 url.includes('success') 判断回调地址,否则其他参数或恶意构造的 URL 也可能被误判。应尽量同时校验协议、域名和路径。
从服务端确认交易结果
客户端检测到回调 URL 后,应查询自己的服务端,不要直接相信回调 URL 中的 status=success 等参数。
async function refreshPaymentStatus(orderId) {
try {
const response = await fetch(
`https://api.example.com/orders/${encodeURIComponent(orderId)}/payment-status`,
{
method: 'GET',
headers: {
Accept: 'application/json'
}
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const result = await response.json();
switch (result.status) {
case 'paid':
ons.notification.alert('支付成功');
break;
case 'failed':
ons.notification.alert('支付失败,请重新尝试');
break;
case 'pending':
ons.notification.toast('支付结果确认中,请稍后查看', {
timeout: 3000
});
break;
default:
ons.notification.alert('暂时无法确认支付状态');
}
} catch (error) {
console.error('Failed to query payment status:', error);
ons.notification.alert(
'支付结果查询失败,请稍后在订单页面确认,请勿重复支付'
);
}
}
服务端应通过支付网关的异步通知、签名验证或官方订单查询接口确认交易。客户端传来的订单号、返回参数和页面 URL,都不能单独作为入账依据。
如果支付页面使用系统浏览器
如果支付流程会离开 App,并在系统浏览器中打开,可以在 App 恢复到前台时查询订单状态:
let pendingOrderId = null;
document.addEventListener('deviceready', function () {
document.addEventListener('resume', async function () {
if (!pendingOrderId) {
return;
}
await refreshPaymentStatus(pendingOrderId);
});
});
function startPayment(orderId) {
pendingOrderId = orderId;
// 打开支付页面……
}
resume 只表示 App 恢复运行,同样不能证明付款成功。它只是一个重新查询服务端状态的时机。
如果支付网关支持自定义 URL Scheme、Universal Links 或 App Links,也可以让支付完成页跳回 App,并在深链处理逻辑中提取订单标识。无论采用哪种方式,最终都要向服务端核验支付状态。
注意事项
- 不要把
exit、resume、postpop或返回键事件直接当作支付成功的依据。 - 优先通过支付网关的服务端异步通知更新订单,前端查询只负责展示结果。
- 订单状态更新需要具备幂等性,避免重复回调造成重复入账或重复发货。
- 处理回调时,不能只校验客户端提供的金额和订单号。服务端还应核对订单、金额、币种和支付平台交易号。
- 对
pending状态可以进行短时间轮询,但要设置最大次数和请求间隔,避免持续请求。 - 用户关闭支付窗口后,如果暂时无法确认结果,应提示“结果确认中,请勿重复支付”,不要直接提示支付失败。
- 不同支付网关的回调方式和 Cordova 插件事件可能不同,具体事件名称应以当前插件和支付网关的接口说明为准。
备注:内容仅供参考。
