iOS 未安装支付宝时 SDK 调起 H5 页面显示异常
结论
如果设备没有安装支付宝,支付宝 SDK 会自动使用内置的 H5 收银台完成支付。H5 页面应由 SDK 创建和关闭。业务代码不要使用 WKWebView、SFSafariViewController 或自定义的 UINavigationController 再包装一层,也不要在支付回跳时重新调用支付接口。
返回按钮无法点击或出现双导航条,通常有以下原因:
- 重复调用支付接口,打开了多个 H5 收银台。
- App 顶部有透明视图、加载蒙层或额外的
UIWindow,导致返回按钮接收不到触摸事件。 - 业务层为 SDK 的 H5 页面添加了额外的导航容器。
- 全局修改了
UINavigationBar、UIViewController或点击事件处理逻辑,干扰了 SDK 内部界面。 - 支付宝 SDK 版本较旧,与当前 iOS 版本兼容不佳。
使用 SDK 的标准支付流程
发起支付时只需调用支付宝 SDK。不要自行判断设备是否安装支付宝,也不要在未安装时改为手动加载支付 URL:
#import <AlipaySDK/AlipaySDK.h>
- (void)startAlipayWithOrder:(NSString *)orderString {
if (orderString.length == 0) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[[AlipaySDK defaultService] payOrder:orderString
fromScheme:@"your_app_scheme"
callback:^(NSDictionary *resultDic) {
[self handleAlipayResult:resultDic];
}];
});
}
fromScheme 必须与工程 URL Types 中配置的回跳 Scheme 完全一致。
不要用下面的方式包装 SDK 的 H5 页面:
// 不建议:不要自行提取或加载支付宝支付地址
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
// [webView loadRequest:...];
// 不建议:不要在调用支付宝 SDK 后再额外弹出浏览器控制器
// [self presentViewController:safariViewController animated:YES completion:nil];
SDK 会根据设备环境选择拉起支付宝客户端或显示 H5 收银台,业务层无需重复处理这部分跳转。
防止重复调起支付页面
按钮连续点击、接口多次回调或页面生命周期触发重复调用,都可能打开两层支付界面。页面会因此出现两个导航条,底层的返回按钮也无法响应。
可以在业务层增加支付状态保护:
@property (nonatomic, assign) BOOL alipayInProgress;
- (void)startAlipayWithOrder:(NSString *)orderString {
if (self.alipayInProgress || orderString.length == 0) {
return;
}
self.alipayInProgress = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[[AlipaySDK defaultService] payOrder:orderString
fromScheme:@"your_app_scheme"
callback:^(NSDictionary *resultDic) {
self.alipayInProgress = NO;
[self handleAlipayResult:resultDic];
}];
});
}
页面重新显示或 App 回到前台时,不要因为 viewWillAppear: 或 sceneDidBecomeActive: 被调用就再次发起支付。
正确处理支付回跳
回跳入口只需要把 URL 交给支付宝 SDK 解析,不能在这里再次调用 payOrder:fromScheme:callback:。
项目使用 AppDelegate 生命周期时:
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
if ([url.host isEqualToString:@"safepay"]) {
[[AlipaySDK defaultService] processOrderWithPaymentResult:url
standbyCallback:^(NSDictionary *resultDic) {
[self handleAlipayResult:resultDic];
}];
return YES;
}
return NO;
}
如果项目采用 Scene 生命周期,可以在对应入口转交 URL:
- (void)scene:(UIScene *)scene
openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts API_AVAILABLE(ios(13.0)) {
NSURL *url = URLContexts.allObjects.firstObject.URL;
if (url == nil) {
return;
}
if ([url.host isEqualToString:@"safepay"]) {
[[AlipaySDK defaultService] processOrderWithPaymentResult:url
standbyCallback:^(NSDictionary *resultDic) {
[self handleAlipayResult:resultDic];
}];
}
}
应根据项目使用的生命周期和 SDK 公开接入文档选择回跳入口,避免在多个入口重复处理同一个 URL。
排查触摸事件被拦截
如果页面只有一层,但底部的蓝色返回箭头仍然无法点击,需要检查是否有视图覆盖在支付页面上:
- 全屏加载动画或透明遮罩是否已移除。
- 是否有用于防截屏、悬浮按钮、埋点或手势处理的额外
UIWindow。 - 自定义窗口的
windowLevel是否高于正常应用窗口。 - 是否重写了
hitTest:withEvent:或pointInside:withEvent:。 - 是否通过 Method Swizzling 全局修改了
UIViewController、UINavigationController或触摸事件。 - 支付前显示的 HUD 是否仍在接收事件。
例如,应在调用 SDK 前关闭支付按钮触发的蒙层:
- (void)payButtonTapped {
[self hideLoadingOverlay];
NSString *orderString = self.orderString;
[self startAlipayWithOrder:orderString];
}
同时确认支付期间没有创建新的业务窗口:
// 支付期间避免使用额外 UIWindow 覆盖 SDK 页面
// self.overlayWindow.hidden = NO;
如果业务必须使用悬浮窗口,可以在进入支付前暂时隐藏,等支付流程结束后再恢复。
处理导航条重复
不要通过遍历 SDK 内部视图并强制隐藏某个 UINavigationBar 来处理双导航条。这种方案依赖 SDK 的私有实现,SDK 升级后很容易失效。
应移除业务层对支付页面的二次包装,并检查项目中是否有以下全局设置:
[[UINavigationBar appearance] setHidden:YES];
或者:
@implementation UINavigationController (CustomStyle)
// 检查此类全局分类是否修改了 SDK 内部导航控制器
@end
如果项目需要统一导航样式,尽量只调整自定义的导航控制器子类,不要无条件修改所有 UINavigationBar 或 UINavigationController:
@interface AppNavigationController : UINavigationController
@end
让业务页面使用 AppNavigationController,以免全局行为影响第三方 SDK 创建的控制器。
SDK 版本与最小化验证
确认没有重复调用、额外导航容器或遮罩视图后,可以升级到支付宝当前提供的 iOS SDK,并按照对应版本的接入说明重新核对依赖和资源文件。不同版本的 SDK 可能采用不同的内部 H5 实现,因此不能假定某个私有视图层级或类名始终不变。
还可以建立一个最小测试页面,只保留以下内容:
- 一个支付按钮。
- 一次
payOrder:fromScheme:callback:调用。 - URL Scheme 配置。
- 支付结果回调处理。
如果最小工程显示正常,问题基本来自主工程中的全局 UI 定制、遮罩窗口或重复调用。如果最小工程仍然能够稳定复现,应记录 iOS 版本、设备型号、支付宝 SDK 版本,并保留复现视频和 SDK 日志,提交给支付宝技术支持确认是否存在兼容性问题。
最终支付结果应以后端异步通知和订单查询结果为准,不能只根据 H5 页面或客户端回调判断。
备注:内容仅供参考。
