Flutter WebView中PayTM Checkout JS自定义重定向失效问题
问题场景
在Flutter WebView中集成Paytm Checkout JS时,商户配置项redirect参数设为false,目标是在transactionStatus回调返回交易状态后,通过POST表单提交跳转到自有页面paymentReceipt.php做后续处理。该逻辑在桌面浏览器可正常运行,但在Flutter WebView环境中,交易完成后不会跳转至自有页面,反而直接跳转Paytm官方结果页。
核心实现代码如下:
"merchant": { "redirect": false }, "handler": { "notifyMerchant": function(eventName,data){ console.log("notifyMerchant handler function called"); console.log("eventName => ",eventName); console.log("data => ",data); }, transactionStatus: function(data){ console.log("payment status ", data); var form = document.createElement('form'); document.body.appendChild(form); form.method = 'post'; form.action = 'https://<myWebsite>/paymentReceipt.php'; for (var name in data) { var input = document.createElement('input'); input.type = 'hidden'; input.name = name; input.value = data[name]; form.appendChild(input); } form.submit(); }, }
问题根因
- 跨域表单拦截:Paytm收银台运行在Paytm官方域名下,动态创建的隐藏表单提交到自有域名属于跨域POST请求,移动端WebView内核默认对这类跨域DOM导航做拦截,表单提交动作不会实际触发。
- SDK环境兼容逻辑:Paytm Checkout JS内置UA检测,即使显式配置
redirect: false,当识别到运行环境是移动端WebView时,会强制覆盖配置,在transactionStatus回调触发后立刻执行内置的官方结果页跳转,写在回调里的DOM表单操作在事件队列中优先级低于SDK内置逻辑,还没执行完成就被SDK触发的跳转打断。 - 导航规则缺失:未配置WebView的导航拦截逻辑时,Paytm触发的官方页跳转请求不会被阻止,会被WebView优先加载。
排查解决步骤
- 配置WebView导航拦截规则
以官方webview_flutter插件为例,重写导航代理,拦截Paytm官方结果页的跳转请求,只允许自有业务页面加载:final WebViewController controller = WebViewController() ..setNavigationDelegate( NavigationDelegate( onNavigationRequest: (request) { // 匹配Paytm官方支付结果页路径,直接拦截阻止加载 if (request.url.contains('paytm.com/merchant-payment-response') || request.url.contains('paytm.com/payment-status')) { return NavigationDecision.prevent; } // 自有域名页面允许正常加载 if (request.url.contains('<myWebsite>/paymentReceipt.php')) { return NavigationDecision.navigate; } return NavigationDecision.navigate; }, ), ); - 废弃JS侧动态表单提交逻辑,改用JS Channel传值给原生处理
不要在JS上下文里操作DOM触发跳转,支付场景下JS和原生通信的稳定性远高于JS内DOM操作。首先在WebView初始化时注册JS通信通道:
同步修改Paytm JS侧的回调逻辑,拿到交易数据后直接传给原生,同时阻止SDK默认跳转:controller.addJavaScriptChannel( 'PaytmTxnChannel', onMessageReceived: (message) { // 拿到交易数据JSON,直接由原生侧控制后续跳转 final Map<String, dynamic> txnData = jsonDecode(message.message); // 后续可选择: // - 构造POST请求加载paymentReceipt.php // - 直接跳转Flutter原生支付结果页,体验更流畅 // - 把交易数据提交给自有服务端做验签后再渲染结果 }, );"handler": { notifyMerchant: function(eventName,data){ console.log("notifyMerchant handler function called"); console.log("eventName => ",eventName); console.log("data => ",data); }, transactionStatus: function(data){ console.log("payment status ", data); // 阻止事件冒泡,打断SDK内置跳转逻辑 if (window.event) { window.event.stopPropagation(); window.event.preventDefault(); } // 移除Paytm挂载的收银台DOM,避免SDK后续触发跳转 document.querySelector('#paytm-checkout-container')?.remove(); // 把交易数据传给Flutter原生侧 if (window.PaytmTxnChannel) { window.PaytmTxnChannel.postMessage(JSON.stringify(data)); } }, } - 兼容端侧WebView配置(可选)
Android端需要放开WebView的JS执行权限、跨域表单提交权限:
iOS端WKWebView默认放开相关权限,只需要正确实现导航代理的拦截逻辑即可。if (Platform.isAndroid) { controller ..setJavaScriptMode(JavaScriptMode.unrestricted) ..runJavaScript(''' // 重写表单提交方法,避免被Paytm SDK劫持 HTMLFormElement.prototype.submit = function() { window.location.replace(this.action); } '''); }
注意:支付类场景不要依赖前端JS做最终跳转和数据处理,拿到交易数据后一定要先传给自有服务端做验签,再展示支付结果,避免前端篡改交易状态造成资损。
内容的提问来源于stack exchange,提问作者Boris Ashangbam
相关产品推荐
相关产品推荐

