PAYATHON 2026

如何在 cordova webView 返回后检测支付成功事件?

支付老李

结论

不要用 Onsen UI 的 postpop 判断支付是否成功。postpop 只能说明导航栈中的页面已经弹出,与支付网关的交易状态无关。

建议按以下流程处理:

  1. 支付完成后,由支付网关跳转到预设的回调 URL。
  2. 在 Cordova WebView 中监听 loadstart 或 loadstop,通常可使用 cordova-plugin-inappbrowser。
  3. 检测到回调 URL 后,关闭支付窗口。
  4. 调用服务端接口查询并核验订单状态,再向用户展示支付结果。

用户按下返回键,只能说明支付页面已关闭,不能证明交易成功。

为什么 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 插件事件可能不同,具体事件名称应以当前插件和支付网关的接口说明为准。

备注:内容仅供参考。