iOS WKWebView 通过 alipays 协议无法唤起支付宝 App
结论
decidePolicyForNavigationAction 没有被调用,通常并不是 alipays:// 被 iOS 拦截,而是 WKWebView 没有产生新的导航请求,或 WKNavigationDelegate 没有正确绑定。
这个回调只能处理已经发生的导航动作。如果支付宝页面的 JavaScript 在跳转前报错、支付请求返回异常、页面资源加载失败,或页面通过新窗口发起跳转但没有实现 WKUIDelegate,代码就不会进入预期的 alipays:// 处理逻辑。
问题中没有提供具体的 console 和网络日志,仅凭页面停在“加载中”无法确定唯一原因。排查时应先确认回调链是否正常,再检查支付宝页面是否真的生成了 alipays:// 导航。
先确认 WKNavigationDelegate 是否生效
创建 WKWebView 后,需要设置 navigationDelegate,并确认它没有被其他对象覆盖:
@interface PaymentViewController () <WKNavigationDelegate, WKUIDelegate>
@property (nonatomic, strong) WKWebView *webView;
@end
@implementation PaymentViewController
- (void)viewDidLoad {
[super viewDidLoad];
WKWebViewConfiguration *configuration =
[[WKWebViewConfiguration alloc] init];
self.webView =
[[WKWebView alloc] initWithFrame:self.view.bounds
configuration:configuration];
self.webView.navigationDelegate = self;
self.webView.UIDelegate = self;
[self.view addSubview:self.webView];
}
@end
可以先加载一个普通链接来验证代理:
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:@"https://example.com"]];
[self.webView loadRequest:request];
如果普通页面跳转也没有进入 decidePolicyForNavigationAction,需要检查:
navigationDelegate是否确实指向当前控制器;- WKWebView 是否由其他组件创建;
- 设置代理后,第三方 SDK 或封装层是否又覆盖了代理;
- 控制器或代理对象是否已经释放;
- 方法签名是否准确;
- 页面上显示的是否就是当前监听的 WKWebView。
发起支付前可以输出以下信息:
NSLog(@"webView = %@", self.webView);
NSLog(@"navigationDelegate = %@", self.webView.navigationDelegate);
NSLog(@"UIDelegate = %@", self.webView.UIDelegate);
正确处理 alipays 导航
在导航代理中识别支付宝自定义协议,再交给系统打开:
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL *URL = navigationAction.request.URL;
NSString *scheme = URL.scheme.lowercaseString;
NSLog(@"navigation URL: %@", URL.absoluteString);
if ([scheme isEqualToString:@"alipays"] ||
[scheme isEqualToString:@"alipay"]) {
decisionHandler(WKNavigationActionPolicyCancel);
if (@available(iOS 10.0, *)) {
[[UIApplication sharedApplication]
openURL:URL
options:@{}
completionHandler:^(BOOL success) {
NSLog(@"open Alipay result: %@",
success ? @"YES" : @"NO");
}];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
BOOL success =
[[UIApplication sharedApplication] openURL:URL];
#pragma clang diagnostic pop
NSLog(@"open Alipay result: %@",
success ? @"YES" : @"NO");
}
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
每条执行路径都必须调用 decisionHandler,而且只能调用一次。遗漏或重复调用都可能让页面导航卡住,甚至触发异常。
不要只判断完整 URL 的前缀,例如:
[URL.absoluteString hasPrefix:@"alipays://"]
直接判断 scheme 更稳妥,也能避开大小写或 URL 形式差异带来的问题。
检查是否通过新窗口发起跳转
支付页面可能使用 window.open,也可能使用带有 target="_blank" 的链接。这类请求通常会进入 WKUIDelegate 的新窗口回调,不一定会按现有代码的预期在当前页面跳转。
可以把没有目标 WKWebView 的请求加载到当前 WKWebView:
- (WKWebView *)webView:(WKWebView *)webView
createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
forNavigationAction:(WKNavigationAction *)navigationAction
windowFeatures:(WKWindowFeatures *)windowFeatures {
if (navigationAction.targetFrame == nil) {
NSURL *URL = navigationAction.request.URL;
NSString *scheme = URL.scheme.lowercaseString;
if ([scheme isEqualToString:@"alipays"] ||
[scheme isEqualToString:@"alipay"]) {
if (@available(iOS 10.0, *)) {
[[UIApplication sharedApplication]
openURL:URL
options:@{}
completionHandler:nil];
}
return nil;
}
[webView loadRequest:navigationAction.request];
}
return nil;
}
同时设置:
self.webView.UIDelegate = self;
捕获页面脚本错误
如果页面一直显示“加载中”,日志里却从未出现 alipays://,支付宝页面很可能还没有执行到唤起 App 的代码。此时应检查 JavaScript 异常、资源加载失败和支付接口响应。
可以注入脚本,把常见的前端错误转发给 Native:
WKUserContentController *controller =
[[WKUserContentController alloc] init];
NSString *scriptSource =
@"window.addEventListener('error', function(e) {"
" window.webkit.messageHandlers.jsError.postMessage({"
" message: e.message || '',"
" source: e.filename || '',"
" line: e.lineno || 0,"
" column: e.colno || 0"
" });"
"});"
"window.addEventListener('unhandledrejection', function(e) {"
" window.webkit.messageHandlers.jsError.postMessage({"
" message: String(e.reason || 'unhandledrejection')"
" });"
"});";
WKUserScript *script =
[[WKUserScript alloc] initWithSource:scriptSource
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
forMainFrameOnly:NO];
[controller addUserScript:script];
[controller addScriptMessageHandler:self name:@"jsError"];
WKWebViewConfiguration *configuration =
[[WKWebViewConfiguration alloc] init];
configuration.userContentController = controller;
接收错误:
- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:@"jsError"]) {
NSLog(@"JavaScript error: %@", message.body);
}
}
还需要补充导航失败日志:
- (void)webView:(WKWebView *)webView
didFailProvisionalNavigation:(WKNavigation *)navigation
withError:(NSError *)error {
NSLog(@"provisional navigation failed: %@", error);
}
- (void)webView:(WKWebView *)webView
didFailNavigation:(WKNavigation *)navigation
withError:(NSError *)error {
NSLog(@"navigation failed: %@", error);
}
- (void)webView:(WKWebView *)webView
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential * _Nullable credential))completionHandler {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
在 macOS Safari 中开启“开发”菜单并连接 iPhone,远程检查 WKWebView,可以直接查看:
- JavaScript 异常;
- 支付页面的 Network 请求;
- 请求状态码和响应内容;
- 页面是否生成过
alipays://地址; - 是否有 CSP、跨域、Cookie 或资源加载错误。
检查支付请求的生成方式
手机网站支付通常由服务端调用支付接口,再返回支付宝要求的跳转内容,例如自动提交的 HTML 表单。客户端不应自行拼接未经验证的地址:
https://mapi.alipay.com/gateway.do?xxxxxx
需要逐项确认:
- 支付产品、网关地址和签名算法是否与当前服务端接入方案一致;
- 服务端是否正确生成了所有必填参数;
- 参数编码、签名内容和实际发送的内容是否完全一致;
- 服务端返回的支付跳转内容是否完整有效;
- 是否把应通过
POST提交的表单直接转换成了 URL; - 支付请求是否过期,系统时间是否正确;
return_url、通知地址和业务参数是否符合接入要求;- 是否混用了旧版网关示例和当前接口参数。
建议记录服务端响应中的支付宝错误码和错误信息,但不要在日志中写入私钥、完整签名、用户身份信息或完整支付参数。
如果支付页面本身没有完成初始化,它就不会生成 alipays:// 导航。此时修改 WKWebView 代理代码解决不了根本问题。
Info.plist 配置的作用
如果代码使用 canOpenURL: 检查支付宝是否已经安装,需要在 Info.plist 中加入查询白名单:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>alipays</string>
<string>alipay</string>
</array>
例如:
NSURL *URL = [NSURL URLWithString:@"alipays://"];
if ([[UIApplication sharedApplication] canOpenURL:URL]) {
NSLog(@"Alipay is available");
}
缺少 LSApplicationQueriesSchemes 主要会影响 canOpenURL: 的返回结果,无法解释 WKWebView 为什么连导航代理都没有触发。因此,不要把这项配置当作页面始终停在“加载中”的唯一原因。
如果不需要提前检测,可以直接调用 openURL:options:completionHandler:,再通过 completion handler 判断是否打开成功。
推荐排查顺序
- 加载普通网页,确认
navigationDelegate确实生效。 - 在所有导航回调中记录完整 URL,检查是否出现过
alipays或alipayscheme。 - 实现
WKUIDelegate,处理window.open和target="_blank"。 - 使用 Safari 远程调试查看 console 和 Network,找到页面停在“加载中”的直接原因。
- 核对服务端返回的支付表单、参数、签名和支付宝错误响应。
- 分别在已安装和未安装支付宝的设备上测试。
- 更换网络、iOS 版本和真机复测,排除网络拦截、Cookie、系统环境或单台设备状态的影响。
- 检查应用是否已经正确注册并处理支付完成后的回跳 URL。
排查的核心是先确认页面有没有发起 alipays:// 导航。如果没有,应检查页面脚本、网络请求或服务端支付参数。如果已经发起但无法打开,再检查 scheme 拦截、系统打开逻辑、支付宝安装状态和应用配置。
备注:内容仅供参考。
