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)
}
这里要注意两点:
- 必须使用
navigationAction.request.url原对象打开,不要拆分参数后再手动拼接。 - 调用
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”,不一定会阻止支付宝支付页出现。因此,应分别排查“进入支付宝支付页”和“支付完成后回跳”。
推荐排查顺序
- 用 Safari 打开同一个 H5 收银台链接,确认该订单能否正常支付。
- 在
WKNavigationDelegate和WKUIDelegate中记录完整的导航链路。 - 确认打开支付宝 Scheme 时使用的是原始
URL,中间没有解码、替换或重新拼接。 - 确认外部打开后调用了
.cancel,没有重复处理同一个请求。 - 补充对
target="_blank"和window.open的处理。 - 暂时移除自定义 User-Agent、域名拦截和特殊重定向逻辑,对比测试结果。
- 使用全新订单测试,同时核对支付宝接口返回码、订单状态和商户产品权限。
- 如果 Scheme 完整、Safari 正常,而且问题仍只发生在 App 内,再把脱敏后的完整跳转链路、订单号和发生时间提交给支付宝技术支持定位。
这类问题最常见的原因是,客户端虽然成功打开了支付宝,却没有把收银台生成的原始支付指令完整交给它。先对比 Safari 和 WKWebView 的最终跳转 URL,通常就能判断问题出在客户端导航处理,还是订单与服务端参数。
备注:内容仅供参考。
