PAYATHON 2026

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 存储上。

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 的服务端回调或订单查询接口验证交易状态,防止用户伪造成功跳转。

可以按以下顺序排查:

  1. 去掉 incognito={true}。
  2. 启用 JavaScript、DOM Storage 和必要的 Cookie 支持。
  3. 暂时移除 checkForBackRef,确认它没有中断导航。
  4. 记录全部重定向、HTTP 错误和新窗口请求。
  5. 分别在 Android、iOS 真机和系统浏览器中进行对比。
  6. 将失败 URL、状态码和交易标识交给 AzeriCard,由其查询服务端拒绝原因。
  7. 如果网关不支持 WebView,改用官方推荐的浏览器或 SDK 集成方式。

备注:内容仅供参考。