PAYATHON 2026

React Native iOS 支付宝支付完成后无法获取回调结果

支付阿杰

结论

问题一般不在 JavaScript 的 async/await,而在 iOS 支付完成后的回跳链路。

支付宝客户端完成支付后,会通过 URL Scheme 重新拉起 App。iOS 端需要处理以下环节:

  1. 在 Xcode 中注册 URL Scheme,并确保它与 payOrder:fromScheme:callback: 使用的值完全一致。
  2. 在 AppDelegate 中接收回跳 URL。使用 Scene 生命周期的项目还需要检查 SceneDelegate。
  3. 将 URL 传给支付宝 SDK 的 processOrderWithPaymentResult:standbyCallback:。
  4. React Native 原生模块收到结果后,主动 resolve Promise,或者向 JavaScript 发送事件。

部分 iOS 支付路径中,只调用 payOrder 并等待它的回调并不够。App 跳转到支付宝后,原来的调用链可能被挂起,最终结果会从 AppDelegate 的 URL 回调进入。

1. 检查 URL Scheme

在 Xcode 中打开:

Target → Info → URL Types

添加一个唯一的 Scheme,例如:

com.example.myapp.alipay

调用支付宝 SDK 时必须传入相同的值:

[[AlipaySDK defaultService] payOrder:orderString
                          fromScheme:@"com.example.myapp.alipay"
                            callback:^(NSDictionary *resultDic) {
    NSLog(@"Alipay direct callback: %@", resultDic);
}];

如果 Scheme 不一致,支付宝可能已经完成支付,却无法正确唤起当前 App,后续也就拿不到 resultStatus。

如果 Scheme 来自 Info.plist、环境变量或 React Native 参数,还要确认最终传给 SDK 的值不是空字符串,并且没有多余空格。

2. 在 AppDelegate 中处理支付宝回跳

Objective-C 项目可以在 AppDelegate.m 中这样处理:

#import <AlipaySDK/AlipaySDK.h>
#import <React/RCTLinkingManager.h>

- (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
{
    if ([url.host isEqualToString:@"safepay"]) {
        [[AlipaySDK defaultService]
            processOrderWithPaymentResult:url
            standbyCallback:^(NSDictionary *resultDic) {
                NSLog(@"Alipay standby callback: %@", resultDic);

                [[NSNotificationCenter defaultCenter]
                    postNotificationName:@"AlipayResultNotification"
                    object:nil
                    userInfo:resultDic];
            }];

        return YES;
    }

    return [RCTLinkingManager application:app
                                  openURL:url
                                  options:options];
}

支付宝和 React Native Linking 的处理逻辑不能互相覆盖。如果项目中已经有 application:openURL:options:,需要把两部分逻辑合并到同一个方法里,不能再添加一个签名完全相同的方法。

仍在使用旧版生命周期方法的项目,可以补充:

- (BOOL)application:(UIApplication *)application
           openURL:(NSURL *)url
 sourceApplication:(NSString *)sourceApplication
        annotation:(id)annotation
{
    if ([url.host isEqualToString:@"safepay"]) {
        [[AlipaySDK defaultService]
            processOrderWithPaymentResult:url
            standbyCallback:^(NSDictionary *resultDic) {
                [[NSNotificationCenter defaultCenter]
                    postNotificationName:@"AlipayResultNotification"
                    object:nil
                    userInfo:resultDic];
            }];

        return YES;
    }

    return [RCTLinkingManager application:application
                                  openURL:url
                        sourceApplication:sourceApplication
                               annotation:annotation];
}

具体实现哪个方法,要看项目的 iOS Deployment Target 和生命周期配置。现代项目通常以 application:openURL:options: 为主。

3. 使用 SceneDelegate 的项目

如果项目启用了 Scene 生命周期,URL 可能会进入 SceneDelegate,而不是 AppDelegate:

#import <AlipaySDK/AlipaySDK.h>

- (void)scene:(UIScene *)scene
    openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts
{
    NSURL *url = URLContexts.allObjects.firstObject.URL;
    if (url == nil) {
        return;
    }

    if ([url.host isEqualToString:@"safepay"]) {
        [[AlipaySDK defaultService]
            processOrderWithPaymentResult:url
            standbyCallback:^(NSDictionary *resultDic) {
                NSLog(@"Alipay scene callback: %@", resultDic);

                [[NSNotificationCenter defaultCenter]
                    postNotificationName:@"AlipayResultNotification"
                    object:nil
                    userInfo:resultDic];
            }];
    }
}

如果项目中有 SceneDelegate 和 UIApplicationSceneManifest,应重点检查这里是否正确接收并处理了 URL。

4. 将回调结果传回 React Native

只在 AppDelegate 中打印结果还不够,原生模块还要把结果传回 JavaScript。

常见的做法是发起支付时保存 Promise,等支付宝 URL 回调到达后再 resolve:

#import <React/RCTBridgeModule.h>

@interface RNAlipayModule : NSObject <RCTBridgeModule>
@end
#import "RNAlipayModule.h"
#import <AlipaySDK/AlipaySDK.h>

@interface RNAlipayModule ()

@property (nonatomic, copy) RCTPromiseResolveBlock payResolve;
@property (nonatomic, copy) RCTPromiseRejectBlock payReject;

@end

@implementation RNAlipayModule

RCT_EXPORT_MODULE();

- (instancetype)init
{
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter]
            addObserver:self
            selector:@selector(handleAlipayResult:)
            name:@"AlipayResultNotification"
            object:nil];
    }
    return self;
}

RCT_EXPORT_METHOD(pay:(NSString *)orderString
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    self.payResolve = resolve;
    self.payReject = reject;

    [[AlipaySDK defaultService]
        payOrder:orderString
        fromScheme:@"com.example.myapp.alipay"
        callback:^(NSDictionary *resultDic) {
            [self finishPaymentWithResult:resultDic];
        }];
}

- (void)handleAlipayResult:(NSNotification *)notification
{
    [self finishPaymentWithResult:notification.userInfo];
}

- (void)finishPaymentWithResult:(NSDictionary *)result
{
    if (self.payResolve == nil) {
        return;
    }

    self.payResolve(result ?: @{});

    self.payResolve = nil;
    self.payReject = nil;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

@end

JavaScript 端正常等待结果即可:

import { NativeModules } from 'react-native';

const { RNAlipayModule } = NativeModules;

try {
  const result = await RNAlipayModule.pay(orderString);

  switch (String(result.resultStatus)) {
    case '9000':
      console.log('客户端返回支付成功');
      break;
    case '6001':
      console.log('用户取消支付');
      break;
    case '6002':
      console.log('网络连接错误');
      break;
    default:
      console.log('其他支付状态', result);
  }
} catch (error) {
  console.error('调用支付宝失败', error);
}

同一笔支付可能同时触发 SDK 的直接 callback 和 URL 的 standby callback。原生层应像示例中那样,在 resolve 后立即清空 Promise,防止重复回调。

如果使用的是第三方 React Native 支付库,应优先调用该库提供的 handleOpenURL、setAlipayScheme 或类似接口,不要另建一套互不相通的回调状态。具体方法名以所用库的文档和源码为准。

5. 检查 Info.plist 配置

URL Types 对应的原始配置类似:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>com.example.myapp.alipay</string>
        </array>
    </dict>
</array>

部分 SDK 或封装会通过 canOpenURL: 判断支付宝是否已安装。在这种情况下,iOS 9 及以上版本可能还需要在 LSApplicationQueriesSchemes 中加入当前 SDK 要求的查询 Scheme。

这项配置主要用于检测是否安装了支付宝,不能代替支付结果的回跳处理。具体 Scheme 不要凭经验填写,应以当前接入的支付宝 SDK 文档或官方 Demo 为准。

6. 快速定位问题的位置

可以按下面的顺序添加日志:

NSLog(@"1. start alipay");
NSLog(@"2. openURL received: %@", url);
NSLog(@"3. process result: %@", resultDic);
NSLog(@"4. resolve promise");

根据缺少哪条日志来判断问题:

  • 没有第 2 条:检查 URL Scheme、AppDelegate、SceneDelegate,以及对应方法是否被其他 SDK 覆盖。
  • 有第 2 条但没有第 3 条:确认 URL 是否已经传给 processOrderWithPaymentResult:,并检查它是否确实来自支付宝回跳。
  • 有第 3 条但 JavaScript 没有收到结果:问题出在 React Native Bridge。常见原因包括 Promise 没有保存、原生模块实例不一致,或者原生层只打印了结果却没有 resolve。
  • JavaScript 收到了结果,但状态不符合预期:检查 resultStatus 的类型和内容,并结合服务端异步通知确认最终订单状态。

注意事项

9000 表示支付宝客户端返回支付成功,但不能把它作为发货、充值或更新正式订单状态的唯一依据。客户端结果可能被伪造,也可能出现客户端回调成功,而服务端尚未完成处理的情况。涉及资金或履约时,应由服务端验签支付宝异步通知,或者由 App 请求业务服务端查询订单状态,再确认支付是否成功。

不要在每次支付时重复注册通知监听,也不要让多个支付请求覆盖同一个 payResolve。如果业务允许并发发起支付,需要为每笔请求建立明确的状态管理。

备注:内容仅供参考。