React Native iOS 支付宝支付完成后无法获取回调结果
结论
问题一般不在 JavaScript 的 async/await,而在 iOS 支付完成后的回跳链路。
支付宝客户端完成支付后,会通过 URL Scheme 重新拉起 App。iOS 端需要处理以下环节:
- 在 Xcode 中注册 URL Scheme,并确保它与
payOrder:fromScheme:callback:使用的值完全一致。 - 在
AppDelegate中接收回跳 URL。使用 Scene 生命周期的项目还需要检查SceneDelegate。 - 将 URL 传给支付宝 SDK 的
processOrderWithPaymentResult:standbyCallback:。 - 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。如果业务允许并发发起支付,需要为每笔请求建立明确的状态管理。
备注:内容仅供参考。
