PAYATHON 2026

如何在 Ionic 和 Angular 的 WebView 中集成 payment gateway

支付小周

明确结论

只修改 new Card(configJson).init(),不能保证支付页留在 Ionic 主 WebView 中。支付页面之所以会在外部浏览器打开,通常是因为支付 SDK 内部调用了 window.open()、使用了 _system,或者 Cordova 的导航策略没有放行支付域名。

比较稳妥的处理方式是:

  1. 由服务端生成订单和支付令牌。
  2. 用 Cordova InAppBrowser 在应用内打开支付页面。
  3. 监听回调 URL,收到支付结果后关闭支付窗口。
  4. 通过服务端查询支付网关或接收 webhook,确认最终支付状态。

如果支付网关明确禁止使用 WebView,或者 3-D Secure、网银、UPI 等流程需要跳转到银行应用,就不能强制把页面放进主 WebView。此时应使用网关提供的 Cordova/Ionic SDK,或者按要求跳转到外部应用。

先安装 InAppBrowser

旧版 Ionic/Cordova 项目可以安装以下插件:

cordova plugin add cordova-plugin-inappbrowser

如果项目使用 Ionic Native,还要安装与当前项目版本匹配的包装包。不同 Ionic 大版本使用的包名和调用方式可能不同,应以项目现有依赖版本为准。

代码需要等 Cordova 就绪后再执行:

document.addEventListener('deviceready', function () {
    // InAppBrowser is ready
}, false);

推荐的支付流程

不要把支付网关密钥、签名逻辑或可信的支付结果放进 AngularJS 控制器。客户端只负责发起支付和显示支付页面:

Ionic App
    → 请求业务服务端创建支付订单
    → 服务端返回 paymentUrl / token
    → InAppBrowser 打开 paymentUrl
    → 网关跳转到约定的 returnUrl
    → App 识别 returnUrl 并关闭窗口
    → App 请求服务端查询订单最终状态

这样既能防止用户伪造客户端回调,也能应对付款后应用被关闭或网络中断等情况。

使用 InAppBrowser 打开支付页面

假设服务端已经返回支付地址 paymentUrl,AngularJS 控制器可以这样写:

$scope.paymentOpen = function () {
    paymentService.createOrder().then(function (response) {
        var paymentUrl = response.data.paymentUrl;

        var paymentWindow = cordova.InAppBrowser.open(
            paymentUrl,
            '_blank',
            'location=yes,clearcache=yes,clearsessioncache=yes'
        );

        paymentWindow.addEventListener('loadstart', function (event) {
            var url = event.url;

            if (url.indexOf('https://example.com/payment/callback') === 0) {
                paymentWindow.close();

                // 不要只相信 URL 中的 success 参数。
                // 应通过服务端重新查询交易状态。
                verifyPayment(response.data.orderId);
            }
        });

        paymentWindow.addEventListener('loaderror', function (event) {
            paymentWindow.close();
            $scope.$applyAsync(function () {
                $scope.paymentMessage = '支付页面加载失败,请稍后重试';
            });
        });

        paymentWindow.addEventListener('exit', function () {
            // 用户主动关闭支付窗口时也可以查询一次订单状态,
            // 防止“已经付款但回调页面尚未加载”的情况。
            verifyPayment(response.data.orderId);
        });
    });
};

function verifyPayment(orderId) {
    paymentService.getPaymentStatus(orderId).then(function (response) {
        $scope.paymentStatus = response.data.status;
    });
}

这里的 _blank 会让 InAppBrowser 插件打开应用内浏览窗口,并不会调用系统外部浏览器。不要使用 _system,它会把页面交给设备的默认浏览器。

如果必须继续使用 new Card(configJson).init()

现有代码中的 responseHandler 只能接收支付 SDK 主动返回的结果,不能控制 SDK 用什么方式打开页面。如果 SDK 支持配置打开目标,可以查看文档中是否有 target、openMode、webView 或 Cordova 专用参数。除非网关文档明确说明,否则不要假设 tarCall 能控制页面的打开方式。

可以先整理现有代码,并加强对回调数据的检查:

function handleResponse(res) {
    var transaction =
        res &&
        res.paymentMethod &&
        res.paymentMethod.paymentTransaction;

    if (transaction && transaction.statusCode === '0300') {
        // 这里只能作为界面提示依据。
        // 仍需调用业务服务端确认交易状态。
        verifyPayment(transaction.txnId);
    } else {
        $scope.$applyAsync(function () {
            $scope.paymentMessage = '支付未完成或支付失败';
        });
    }
}

$scope.paymentOpen = function () {
    var configJson = {
        tarCall: false,
        features: {
            showPGResponseMsg: true
        },
        consumerData: {
            deviceId: 'WEBSH10',
            token: '',
            returnUrl: 'https://example.com/payment/callback',
            responseHandler: handleResponse,
            paymentMode: 'all',
            merchantId: 'T1',
            consumerId: 'c',
            consumerMobileNo: '9876543210',
            consumerEmailId: 'test@test.com',
            txnId: '148xxxxxxxxx',
            items: [{
                itemId: 'test',
                amount: '10',
                comAmt: '0'
            }]
        }
    };

    new Card(configJson).init();
};

如果 SDK 把外部浏览器写死在内部逻辑中,应用层无法通过注释或包装 $scope.paymentOpen 改变这一行为。下面的写法也不正确:

window.open($scope.paymentOpen, '_blank');

window.open() 接收的是 URL,而 $scope.paymentOpen 是函数。应该打开支付网关返回的地址:

cordova.InAppBrowser.open(paymentUrl, '_blank', 'location=yes');

配置 Cordova 导航白名单

如果决定直接在主 WebView 中导航,还需要放行相关域名。具体配置方式取决于 Cordova/Ionic 版本,常见的 config.xml 配置如下:

<access origin="https://payment.example.com" />
<allow-navigation href="https://payment.example.com/*" />
<allow-intent href="https://bank.example.com/*" />

不要为了方便而放开所有域名。支付流程可能经过支付网关、3-D Secure 验证页和银行域名,应根据实际的跳转链逐个配置。

即使已经配置 <allow-navigation>,第三方支付页也未必适合在 Ionic 主 WebView 中运行。页面可能依赖新窗口、多因素认证、第三方 Cookie、银行 App 或自定义 URL Scheme。因此,一般更适合使用 InAppBrowser,避免让主应用 WebView 离开当前 Angular 页面。

回调地址的处理

示例中的地址:

returnUrl: 'https://www.tekprocess.co'

除非该地址确实由业务方控制,并且已经实现订单回调逻辑,否则不适合作为应用的支付回调地址。应改为自己的 HTTPS 地址,例如:

returnUrl: 'https://example.com/payment/callback'

这个页面可以显示“正在确认支付”,应用则通过 loadstart 识别该地址。也可以使用 Universal Links、App Links 或正确注册的自定义 URL Scheme 返回应用,但能否这样设置,取决于支付网关是否允许相应格式的 returnUrl。

安全和兼容性注意事项

  • token、商户签名和密钥应由服务端生成,不能写入 Ionic JavaScript。
  • txnId 必须由服务端生成并保证唯一,不能把前端时间戳当作可信订单号。
  • statusCode === '0300' 是否表示成功,应以当前支付网关的正式文档为准。
  • 客户端 responseHandler 和回调 URL 都可能被伪造,不能只凭这些信息直接发货。
  • 应通过 webhook 或服务端订单查询接口确认最终状态,同时校验金额、币种、订单号和商户号。
  • 必须使用 HTTPS,不能关闭 TLS 或证书校验。
  • 某些网关禁止用户在 WebView 中输入银行卡信息。遇到这种限制时,应使用网关的原生 SDK、Hosted Checkout 或系统浏览器。
  • Android 返回键、用户关闭窗口、支付超时和重复点击都需要妥善处理,服务端创建订单的接口也应具备幂等性。

实际需要调整的不只是按钮代码。应用应先向服务端获取支付 URL,再通过 InAppBrowser 打开支付页面,并在回调后向服务端确认结果。只有支付网关明确支持嵌入式 WebView 时,才适合考虑在 Ionic 主 WebView 中完成整个支付流程。

备注:内容仅供参考。