PAYATHON 2026

iOS WKWebView 嵌入网页后无法唤起支付宝支付

支付老李

结论

WKWebView 不会自动处理支付宝客户端使用的自定义 URL Scheme。遇到 target="_blank"、window.open() 或跨应用跳转时,也可能不会继续导航。因此,网页跳转到 alipays:// 等地址时,看起来会毫无反应。

排查时,先确认支付宝返回的支付链接有效。然后在 WKNavigationDelegate 中识别外部支付 URL,并交给 UIApplication 打开。新窗口请求、回跳地址和失败回调也需要一并处理。

如果使用支付宝移动支付 SDK,应直接调用 SDK,不要把 SDK 支付流程当成普通网页加载。选择 H5 支付还是 App 支付,要看商户签约的产品以及服务端创建订单所用的接口。

先确认问题发生在哪一层

可以使用 Safari Web Inspector,或在导航代理中打印 URL:

print("WKWebView navigation URL:", navigationAction.request.url?.absoluteString ?? "nil")

主要检查以下情况:

  • 点击支付按钮后完全没有导航请求,通常是 JavaScript 报错、新窗口没有处理,或点击事件被页面逻辑阻止。
  • 已进入支付收银台,但停在某个中间页面,需要检查重定向、Cookie、User-Agent 和网络请求。
  • 出现 alipay:// 或 alipays:// URL,但客户端没有打开,需要让原生代码接管。
  • UIApplication.open 返回失败,可能是支付宝未安装,或 URL 不符合预期。
  • 支付宝已经打开,但支付后无法返回 App,需要配置并处理回跳 Scheme 或 Universal Link。
  • 服务端下单已经失败,这与 WKWebView 无关,应先检查支付接口响应、签名和订单参数。

不要只查看最初加载的支付页面地址。跳转到支付宝客户端的 URL 往往要经过多次 HTTP 重定向才会出现。

在 WKWebView 中接管支付宝跳转

下面的 Swift 示例同时处理普通导航和 target="_blank"。Scheme 白名单应以实际支付流程中捕获的地址为准,不要放行任意非 HTTP URL。

import UIKit
import WebKit

final class PayViewController: UIViewController,
                               WKNavigationDelegate,
                               WKUIDelegate {

    private lazy var webView: WKWebView = {
        let configuration = WKWebViewConfiguration()
        configuration.preferences.javaScriptCanOpenWindowsAutomatically = true

        let webView = WKWebView(
            frame: .zero,
            configuration: configuration
        )
        webView.navigationDelegate = self
        webView.uiDelegate = self
        return webView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        webView.frame = view.bounds
        webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(webView)

        guard let url = URL(string: "https://example.com/pay") else {
            return
        }

        webView.load(URLRequest(url: url))
    }

    func webView(
        _ webView: WKWebView,
        decidePolicyFor navigationAction: WKNavigationAction,
        decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
    ) {
        guard let url = navigationAction.request.url else {
            decisionHandler(.cancel)
            return
        }

        let scheme = url.scheme?.lowercased() ?? ""
        let allowedPaymentSchemes: Set<String> = [
            "alipay",
            "alipays"
        ]

        if allowedPaymentSchemes.contains(scheme) {
            decisionHandler(.cancel)

            UIApplication.shared.open(
                url,
                options: [:]
            ) { success in
                if !success {
                    // 在这里提示用户安装支付宝,或进入支付失败处理流程。
                    print("Unable to open Alipay URL")
                }
            }
            return
        }

        decisionHandler(.allow)
    }

    func webView(
        _ webView: WKWebView,
        createWebViewWith configuration: WKWebViewConfiguration,
        for navigationAction: WKNavigationAction,
        windowFeatures: WKWindowFeatures
    ) -> WKWebView? {
        // target="_blank" 或 window.open() 通常没有目标 frame。
        if navigationAction.targetFrame == nil,
           let url = navigationAction.request.url {
            webView.load(URLRequest(url: url))
        }

        return nil
    }
}

每条代码路径都必须调用一次 decisionHandler,而且只能调用一次。否则,页面可能卡住或出现运行时警告。

配置支付宝安装检测

如果通过 canOpenURL(_:) 检测支付宝是否安装,需要在 Info.plist 中声明要查询的 Scheme:

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>alipay</string>
    <string>alipays</string>
</array>

例如:

if let url = URL(string: "alipays://"),
   UIApplication.shared.canOpenURL(url) {
    print("Alipay may be available")
}

LSApplicationQueriesSchemes 主要影响 canOpenURL(_:) 的查询结果。完成这项配置后,WKWebView 仍不会自动唤起支付宝,实际跳转还要通过 UIApplication.open 处理。

Scheme 的取值应根据支付流程中实际捕获的 URL 和当前支付宝接入要求确定。不要为了所谓的兼容性加入大量无关 Scheme。

处理支付完成后的 App 回跳

如果支付完成后需要返回 App,应在支付请求中配置服务端允许的回跳地址,并在应用中注册相应的 URL Scheme 或 Associated Domains。不要将未经编码的回跳 URL 直接拼接到支付链接中。

使用自定义 Scheme 时,可以在应用生命周期回调中接收返回地址:

func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
    print("Payment callback URL:", url.absoluteString)
    return true
}

采用 Scene 生命周期的应用还要处理:

func scene(
    _ scene: UIScene,
    openURLContexts URLContexts: Set<UIOpenURLContext>
) {
    guard let url = URLContexts.first?.url else {
        return
    }

    print("Payment callback URL:", url.absoluteString)
}

客户端回跳只能用于恢复界面或触发订单查询,不能作为支付成功的最终依据。支付结果应以服务端异步通知或服务端主动查询的结果为准。

如果接入的是支付宝移动支付 SDK

App 支付与 H5 支付的调用方式不同:

  • H5 支付会加载服务端生成的网页支付地址,并处理网页跳转到支付宝客户端的过程。
  • App 支付由服务端生成并签名订单信息,再由 iOS 端调用支付宝 SDK。
  • 两者的签约产品、下单接口、请求参数和回调方式可能不同。

H5 跳转失败后,不能直接截取某段 URL,再将其作为 payOrder 参数调用 SDK。如果准备改用移动支付 SDK,需要同步调整服务端的订单创建和签名流程,并按照当前 SDK 接入文档配置回跳。

仍然无法唤起时的检查清单

  1. 使用真机测试。模拟器不能反映支付宝客户端的实际跳转行为。
  2. 确认设备已经安装支付宝,并且可以正常打开。
  3. 记录每一次 WKNavigationAction,检查是否出现支付宝 Scheme。
  4. 检查页面是否通过 window.open() 或 target="_blank" 打开收银台。
  5. 检查 UIApplication.open 的完成回调是否为 false。
  6. 确认支付地址来自可信服务端,并检查订单创建、签名和商户产品权限是否正常。
  7. 不要把所有非 http、https 地址都交给系统打开。应使用明确的白名单并校验来源。
  8. 不要在客户端生成或保存商户私钥。
  9. 如果流程涉及特定 HTTPS 域名跳转到外部应用,只处理支付宝支付流程中的已知域名,避免将普通网页链接全部交给 Safari 打开。
  10. 如果同一地址在 Safari 中可以唤起支付宝,但在 WKWebView 中不行,应优先检查导航代理、新窗口处理和外部 Scheme 接管逻辑。

备注:内容仅供参考。