You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS Swift基于WKWebView实现3D安全支付的JS POST迁移方案

Swift + WKWebView 实现3D Check支付验证方案

我之前刚把旧的Objective-C+UIWebView的3D支付验证流程迁移到Swift+WKWebView,踩过几个小坑,给你整理一套完整的可行方案,完美适配你描述的支付流程:

核心流程回顾

  1. 发起支付请求,后端返回包含PaReq和ACSURL的响应
  2. 通过WKWebView构造POST请求跳转到ACS验证页面
  3. 用户完成3D验证后,拦截回调并提取PaRes参数
  4. 将PaRes传入后续支付接口完成流程

具体实现步骤

1. 配置WKWebView与导航代理

首先创建WKWebView并设置导航代理,用来拦截验证完成后的回调URL:

class ThreeDSecureViewController: UIViewController, WKNavigationDelegate {
    private var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupWebView()
        // 这里替换成你从后端拿到的真实参数
        start3DSecureFlow(acsURL: "https://your-payment-gateway-acs.com", paReq: "your-pa-req-token")
    }
    
    private func setupWebView() {
        let config = WKWebViewConfiguration()
        // 确保允许JavaScript执行(部分网关依赖JS渲染验证页面)
        config.preferences.javaScriptEnabled = true
        
        webView = WKWebView(frame: view.bounds, configuration: config)
        webView.navigationDelegate = self
        webView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(webView)
        
        // 约束让WebView铺满整个屏幕
        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: view.topAnchor),
            webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

2. 构造自动提交的POST表单HTML

WKWebView没有直接的POST请求加载方法,我们可以构造一个自动提交的HTML表单,加载后自动跳转到ACS验证页面:

private func build3DSecureHTML(acsURL: String, paReq: String, termUrl: String) -> String {
    return """
    <html>
        <head>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
        </head>
        <body onload="document.forms['3dForm'].submit()">
            <form name="3dForm" method="POST" action="\(acsURL)">
                <input type="hidden" name="PaReq" value="\(paReq)">
                <input type="hidden" name="TermUrl" value="\(termUrl)">
                <!-- 部分支付网关需要额外的MD参数,根据实际需求添加 -->
                <!-- <input type="hidden" name="MD" value="your-md-identifier"> -->
            </form>
        </body>
    </html>
    """
}

private func start3DSecureFlow(acsURL: String, paReq: String) {
    // 自定义URL Scheme,用于拦截验证完成后的回调,需要在Info.plist中配置
    let termUrl = "yourapp://3dsecure/callback"
    let htmlContent = build3DSecureHTML(acsURL: acsURL, paReq: paReq, termUrl: termUrl)
    webView.loadHTMLString(htmlContent, baseURL: nil)
}

3. 拦截回调URL提取PaRes

用户完成验证后,支付网关会跳转到我们设置的TermUrl,通过WKNavigationDelegate拦截这个URL并提取PaRes:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    guard let callbackUrl = navigationAction.request.url else {
        decisionHandler(.allow)
        return
    }
    
    // 检查是否是我们自定义的回调Scheme
    if callbackUrl.scheme == "yourapp" && callbackUrl.host == "3dsecure" && callbackUrl.path == "/callback" {
        // 解析URL参数,提取PaRes
        if let urlComponents = URLComponents(url: callbackUrl, resolvingAgainstBaseURL: false),
           let paResItem = urlComponents.queryItems?.first(where: { $0.name == "PaRes" }),
           let paResToken = paResItem.value {
            // 拿到PaRes后,执行后续支付逻辑
            processPaymentWithPaRes(paRes: paResToken)
        }
        
        // 取消跳转,因为我们已经拿到需要的参数了
        decisionHandler(.cancel)
        return
    }
    
    // 其他正常页面跳转允许加载
    decisionHandler(.allow)
}

private func processPaymentWithPaRes(paRes: String) {
    // 这里调用你的后端支付完成接口,传入PaRes
    print("成功获取PaRes: \(paRes)")
    
    // 完成后可以关闭当前验证页面,回到支付流程
    dismiss(animated: true)
}

4. 配置Info.plist的URL Scheme

在Info.plist中添加自定义URL Scheme,确保APP能拦截到回调链接:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>yourapp</string> <!-- 替换成你的APP自定义Scheme -->
        </array>
    </dict>
</array>

额外处理:错误与异常情况

别忘了添加错误处理,比如页面加载失败时提示用户:

func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
    showErrorAlert(message: "验证页面加载失败,请重试")
}

func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    showErrorAlert(message: "验证请求失败,请检查网络或重试")
}

private func showErrorAlert(message: String) {
    let alert = UIAlertController(title: "提示", message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "确定", style: .default))
    present(alert, animated: true)
}

关键注意事项

  • TermUrl设置:部分支付网关要求TermUrl为HTTPS地址,这种情况下需要后端配合:网关跳转到后端的HTTPS回调地址,后端再通过APP的URL Scheme将PaRes参数传递回来
  • 参数完整性:有些网关会要求额外的MD参数(支付会话标识),需要和后端确认后添加到表单中
  • WKWebView权限:确保开启JavaScript支持,部分3D验证页面依赖JS渲染交互组件

内容的提问来源于stack exchange,提问作者RavikanthM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 07:55:22