PAYATHON 2026

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 实现,因此不能假定某个私有视图层级或类名始终不变。

还可以建立一个最小测试页面,只保留以下内容:

  1. 一个支付按钮。
  2. 一次 payOrder:fromScheme:callback: 调用。
  3. URL Scheme 配置。
  4. 支付结果回调处理。

如果最小工程显示正常,问题基本来自主工程中的全局 UI 定制、遮罩窗口或重复调用。如果最小工程仍然能够稳定复现,应记录 iOS 版本、设备型号、支付宝 SDK 版本,并保留复现视频和 SDK 日志,提交给支付宝技术支持确认是否存在兼容性问题。

最终支付结果应以后端异步通知和订单查询结果为准,不能只根据 H5 页面或客户端回调判断。

备注:内容仅供参考。