PAYATHON 2026

iOS 客户端网页支付拉起支付宝App后未跳转支付页面

支付老李

结论

支付宝 App 能被拉起,却没有进入支付页面,通常意味着“打开 App”这一步已经成功,但传给支付宝的支付 Deep Link 无效、不完整,或者被重复处理了。排查重点不是 WKWebView 能否打开支付宝,而是最终的 alipays://、alipay:// 等跳转 URL 是否被原样交给了系统。

优先检查以下问题:

  • WKNavigationDelegate 拦截请求后重新拼接 URL,造成参数丢失或编码损坏。
  • H5 收银台通过 target="_blank" 或 window.open 发起跳转,但应用没有实现 WKUIDelegate。
  • 同一个支付宝 Scheme 被多次打开,或者交给系统后仍由 WKWebView 继续加载。
  • 支付订单、签名、商户产品权限或订单状态异常。
  • H5 收银台链接经过业务中间页、短链或重定向后,关键参数被过滤。
  • 自定义 User-Agent、Cookie 或重定向处理改变了支付宝收银台的正常流程。

先确定问题发生在哪一层

把同一个 H5 支付链接放到 iPhone 的 Safari 中测试,通常最容易判断问题所在。

  • Safari 能正常拉起支付宝并显示支付页面:订单本身大概率有效,应重点检查 WKWebView 的导航拦截和新窗口处理。
  • Safari 也只能打开支付宝首页:重点检查订单、签名、收银台地址和服务端返回内容。
  • 更换订单后恢复正常:原订单可能已经关闭、超时、支付完成或状态异常。
  • 只有部分设备异常:继续检查支付宝客户端版本、系统版本、网络环境,以及设备上是否安装了多个相关客户端。

不要因为支付宝已经打开,就认为接入成功。普通或残缺的 Scheme 也可能启动支付宝,但支付宝无法从中解析出有效的支付指令。

检查实际跳转 URL

在 WKNavigationDelegate 中记录每一次导航的完整 URL,特别是即将跳出 http 或 https 的那一次:

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

    print("WKWebView navigation URL:", url.absoluteString)

    let scheme = url.scheme?.lowercased() ?? ""
    if scheme == "alipays" || scheme == "alipay" {
        UIApplication.shared.open(url, options: [:]) { success in
            print("Open Alipay:", success)
        }
        decisionHandler(.cancel)
        return
    }

    decisionHandler(.allow)
}

这里要注意两点:

  1. 必须使用 navigationAction.request.url 原对象打开,不要拆分参数后再手动拼接。
  2. 调用 UIApplication.shared.open 后,应执行 decisionHandler(.cancel),避免系统和 WKWebView 同时处理同一个请求。

下面这种写法风险很高:

let decoded = url.absoluteString.removingPercentEncoding
let newURL = URL(string: decoded!)
UIApplication.shared.open(newURL!)

支付 Scheme 经常包含多层 URL 编码。提前执行 removingPercentEncoding,可能破坏 %2F、%3F、%26、+ 等内容。结果是支付宝虽然能启动,却无法识别支付参数。

也不要用字符串替换来“修复”URL:

let value = url.absoluteString
    .replacingOccurrences(of: "%25", with: "%")

除非支付宝当前的接入文档明确要求这样处理,否则此类替换很容易破坏嵌套参数和签名相关内容。

处理 target="_blank" 和 window.open

支付宝 H5 收银台的某些跳转可能会通过新窗口触发。如果只实现了 WKNavigationDelegate,却没有处理 WKUIDelegate,点击后流程可能直接中断。

final class PaymentViewController: UIViewController,
                                   WKNavigationDelegate,
                                   WKUIDelegate {

    private lazy var webView: WKWebView = {
        let configuration = WKWebViewConfiguration()
        let view = WKWebView(frame: .zero, configuration: configuration)
        view.navigationDelegate = self
        view.uiDelegate = self
        return view
    }()

    func webView(
        _ webView: WKWebView,
        createWebViewWith configuration: WKWebViewConfiguration,
        for navigationAction: WKNavigationAction,
        windowFeatures: WKWindowFeatures
    ) -> WKWebView? {
        guard navigationAction.targetFrame == nil,
              let url = navigationAction.request.url else {
            return nil
        }

        let scheme = url.scheme?.lowercased() ?? ""

        if scheme == "alipays" || scheme == "alipay" {
            UIApplication.shared.open(url, options: [:])
        } else {
            webView.load(navigationAction.request)
        }

        return nil
    }
}

如果日志中完全看不到点击按钮后的 URL,应重点排查 target="_blank"、window.open 或 JavaScript 弹窗是否没有得到处理。

不要依赖 canOpenURL 决定是否跳转

如果代码调用了 canOpenURL,需要在 Info.plist 中配置查询白名单。否则,即使设备已经安装支付宝,canOpenURL 也可能返回 false:

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

LSApplicationQueriesSchemes 主要影响 canOpenURL 查询。加入白名单并不能修复支付参数。当前现象已经表明支付宝可以启动,所以它通常不是首要原因,但仍要检查代码中是否存在类似逻辑:

if UIApplication.shared.canOpenURL(url) {
    UIApplication.shared.open(url)
}

Scheme 白名单中只能填写 Scheme 名称,不能填写跳转地址、Bundle ID 或完整 URL。

检查是否误拦截了支付宝中间页

导航代理不能简单地取消所有非业务域名。例如,下面的策略可能会阻断支付宝 H5 收银台内部所需的重定向:

if url.host != "example.com" {
    decisionHandler(.cancel)
    return
}

处理时应明确区分不同情况:

  • http、https:通常继续交给 WKWebView。
  • 已确认需要外部打开的支付宝 Scheme:交给 UIApplication。
  • 其他未知 Scheme:按照业务白名单处理。
  • about:blank 等页面:结合实际流程判断,不要无条件拦截所有中间导航。

示例:

let scheme = url.scheme?.lowercased() ?? ""

switch scheme {
case "http", "https":
    decisionHandler(.allow)

case "alipay", "alipays":
    UIApplication.shared.open(url, options: [:])
    decisionHandler(.cancel)

default:
    if UIApplication.shared.canOpenURL(url) {
        UIApplication.shared.open(url, options: [:])
    }
    decisionHandler(.cancel)
}

具体应放行或外部打开哪些 Scheme,要以当前支付宝接入文档和实际抓取到的跳转地址为准,不建议只参考示例写死全部规则。

排查订单和服务端参数

如果 WKWebView 已经把完整 Scheme 原样交给系统,但支付宝仍不显示支付页面,就需要回到服务端继续检查:

  • 收银台 URL 是否直接来自支付宝接口的原始返回结果。
  • 是否对返回的 URL 做了二次 URL 编码或解码。
  • 签名所用参数是否与实际提交参数一致。
  • 订单号是否重复。
  • 订单是否已经支付、关闭或超时。
  • 金额、商户号、应用标识和支付产品配置是否对应。
  • 当前商户是否已经开通所使用的手机网站支付产品。
  • 异步通知地址和同步返回地址是否为合法且可访问的 URL。
  • 中间页、网关、短链服务或前端路由是否删减了查询参数。

建议每次测试都生成新订单,并在服务端保存以下信息,方便逐项对照:

业务订单号
支付宝交易号(如果已经产生)
接口返回码和子错误码
生成的 H5 收银台 URL
创建订单时间
订单当前状态
客户端最终拦截到的 Scheme URL

日志中如果包含签名、令牌、用户信息或完整支付参数,应先做脱敏,不要直接上传到公共平台。

检查 User-Agent、Cookie 和回调处理

如果应用修改过 customUserAgent,可以暂时恢复 WKWebView 的默认 User-Agent,再对比测试结果。部分业务代码会把它伪装成桌面浏览器,可能导致收银台选择错误的跳转策略。

还要检查:

  • 是否禁用了 Cookie。
  • 是否在每次重定向时清理 WKWebsiteDataStore。
  • 是否阻止了 JavaScript 执行。
  • 是否把支付宝页面强制改成临时拼装的本地 HTML。
  • 是否在支付过程中重新创建或销毁 WKWebView。
  • App 进入后台后,是否立即取消订单或清理支付上下文。

支付宝支付完成后的回跳属于另一条链路。App 的返回 Scheme、Universal Link 或 scene(_:openURLContexts:) 配置错误,通常影响的是“支付后返回商户 App”,不一定会阻止支付宝支付页出现。因此,应分别排查“进入支付宝支付页”和“支付完成后回跳”。

推荐排查顺序

  1. 用 Safari 打开同一个 H5 收银台链接,确认该订单能否正常支付。
  2. 在 WKNavigationDelegate 和 WKUIDelegate 中记录完整的导航链路。
  3. 确认打开支付宝 Scheme 时使用的是原始 URL,中间没有解码、替换或重新拼接。
  4. 确认外部打开后调用了 .cancel,没有重复处理同一个请求。
  5. 补充对 target="_blank" 和 window.open 的处理。
  6. 暂时移除自定义 User-Agent、域名拦截和特殊重定向逻辑,对比测试结果。
  7. 使用全新订单测试,同时核对支付宝接口返回码、订单状态和商户产品权限。
  8. 如果 Scheme 完整、Safari 正常,而且问题仍只发生在 App 内,再把脱敏后的完整跳转链路、订单号和发生时间提交给支付宝技术支持定位。

这类问题最常见的原因是,客户端虽然成功打开了支付宝,却没有把收银台生成的原始支付指令完整交给它。先对比 Safari 和 WKWebView 的最终跳转 URL,通常就能判断问题出在客户端导航处理,还是订单与服务端参数。

备注:内容仅供参考。