react-native-webview 无法跳转到 3Ds security 页面
结论
问题可能与 react-native-webview 的运行环境有关,但 ACCESS DENIED 通常不是 WebView 生成的错误,而是 AzeriCard、3DS 服务端、发卡行 ACS 或相关安全防护系统拒绝了请求。
如果网站在浏览器中正常、在 WebView 中失败,可以重点排查这些差异:
- Cookie 或跨站 Cookie 没有正确保存或发送
incognito={true}导致支付会话丢失- 3DS 跳转依赖 JavaScript、DOM Storage、表单 POST 或新窗口
- WebView 的 User-Agent、来源头或 IP 被风控系统拦截
- 支付平台明确禁止在嵌入式 WebView 中完成认证
onNavigationStateChange中的业务逻辑提前拦截了跳转
首先关闭无痕模式
当前代码启用了:
incognito={true}
支付网关通常使用 Cookie 关联支付初始化、银行卡提交和 3DS 认证。无痕模式采用隔离或临时存储,而支付流程往往包含多次跨域跳转,更容易出现会话中断。
先移除该属性,或将其设为 false:
<WebView
ref={(ref) => {
this._webView = ref;
}}
source={{ uri: paymentUrl }}
onNavigationStateChange={this.checkForBackRef}
incognito={false}
javaScriptEnabled={true}
domStorageEnabled={true}
style={{ marginTop: 0, opacity: this.state.opacity }}
/>
如果关闭后恢复正常,基本可以判断问题出在支付会话或 Cookie 存储上。
检查 Cookie 配置
3DS 页面通常位于其他域名,可能依赖跨站 Cookie。Android 可以尝试启用第三方 Cookie。iOS 如果先通过原生网络请求取得了会话 Cookie,还可能需要共享 Cookie。
<WebView
ref={(ref) => {
this._webView = ref;
}}
source={{ uri: paymentUrl }}
javaScriptEnabled={true}
domStorageEnabled={true}
thirdPartyCookiesEnabled={true}
sharedCookiesEnabled={true}
onNavigationStateChange={this.checkForBackRef}
/>
这些属性是否生效以及具体如何工作,会受到平台、系统版本和 react-native-webview 版本影响,需要分别在 Android 和 iOS 真机上测试。
不要保留 incognito={true},同时又期待 Cookie 能长期共享。这两项配置的目标本身就有冲突。
确认没有误拦截跳转
检查 checkForBackRef 是否在识别回调地址时调用了 stopLoading()、修改状态或卸载 WebView,也要确认它没有把某个中间地址误判为支付完成地址。
调试时可以记录完整的导航链:
<WebView
source={{ uri: paymentUrl }}
onNavigationStateChange={(navigation) => {
console.log('WebView navigation:', {
url: navigation.url,
loading: navigation.loading,
title: navigation.title,
canGoBack: navigation.canGoBack,
});
this.checkForBackRef(navigation);
}}
onShouldStartLoadWithRequest={(request) => {
console.log('WebView request:', {
url: request.url,
method: request.method,
navigationType: request.navigationType,
});
return true;
}}
onHttpError={(event) => {
console.log('HTTP error:', event.nativeEvent);
}}
onError={(event) => {
console.log('WebView error:', event.nativeEvent);
}}
/>
不要因为 URL 中含有 success、error 或 callback 之类的模糊字符串就终止加载。只应匹配 AzeriCard 配置中确定的返回地址。支付结果则应以服务端通知或服务端查询为准。
检查 3DS 是否通过新窗口打开
部分支付页面会使用 window.open()、target="_blank" 或弹窗打开 ACS 页面。普通浏览器可以处理这些请求,但嵌入式 WebView 不一定会自动显示新窗口。
可以监听新窗口请求:
<WebView
source={{ uri: paymentUrl }}
javaScriptEnabled={true}
onOpenWindow={(event) => {
const targetUrl = event.nativeEvent.targetUrl;
console.log('Requested new window:', targetUrl);
}}
/>
Android 上还可以测试:
setSupportMultipleWindows={false}
这样可能会让目标页面继续在当前 WebView 中加载,具体效果取决于支付页面的实现。不要在没有验证域名的情况下自动打开任意 targetUrl,否则可能带来安全风险。
对比浏览器和 WebView 请求
如果页面仍然显示 ACCESS DENIED,需要先确认这个页面由哪一方返回。可以记录以下信息,或将其提供给 AzeriCard 技术支持:
- 出错页面的完整 URL 和域名
- HTTP 状态码,例如
403 - 交易时间、订单号和网关交易标识
- Android 或 iOS 及其系统版本
- WebView 的 User-Agent
- 浏览器成功和 WebView 失败时各自的重定向链
- 测试环境是真机、模拟器、VPN 还是代理
如果 ACCESS DENIED 来自 AzeriCard、ACS 或 WAF,客户端只能呈现拒绝结果,具体原因通常要通过服务端日志确认。可能的原因包括 User-Agent 限制、来源校验、会话失效、签名参数错误、商户回调地址未加入白名单,或网关禁止嵌入式浏览器。
可以临时设置一个接近移动浏览器的 User-Agent 进行对照测试,但不能把伪装 User-Agent 当作正式修复:
<WebView
source={{ uri: paymentUrl }}
userAgent="Mozilla/5.0 (...) Mobile Safari/..."
/>
正式值应来自设备的实际 User-Agent,并确认符合支付网关要求。不要直接使用示例中带有省略内容的值。
优先采用网关支持的支付方式
如果 AzeriCard 明确不支持嵌入式 WebView,应使用其官方 SDK,或在系统浏览器、受支持的认证会话中打开托管支付页。支付完成后,再通过 HTTPS 回调或应用 Deep Link 返回。
支付结果不能只以 WebView 最终显示的页面为准。应用后端应通过 AzeriCard 的服务端回调或订单查询接口验证交易状态,防止用户伪造成功跳转。
可以按以下顺序排查:
- 去掉
incognito={true}。 - 启用 JavaScript、DOM Storage 和必要的 Cookie 支持。
- 暂时移除
checkForBackRef,确认它没有中断导航。 - 记录全部重定向、HTTP 错误和新窗口请求。
- 分别在 Android、iOS 真机和系统浏览器中进行对比。
- 将失败 URL、状态码和交易标识交给 AzeriCard,由其查询服务端拒绝原因。
- 如果网关不支持 WebView,改用官方推荐的浏览器或 SDK 集成方式。
备注:内容仅供参考。
