本文章将介绍如何将支付宝App支付功能,接入到React Native应用中,包含Android、iOS,还有JS的调用示例三大模块。

Android

一、目录结构

文章所述将需要改动以下文件:

app/
├── build.gradle
└── proguard-rules.pro
app/libs/
└── alipaySdk-20180601.jar
app/src/main/
└── AndroidManifest.xml
app/src/main/java/com/xxx/mobile
└── App.kt
app/src/main/java/com/xxx/mobile/alipay
├── AlipayModule.kt
└── AlipayPackage.kt

二、集成SDK

SDK下载链接:iOS&Android版资源 v15.5.5

PS: 支付宝官方在2019-04-18刚刚更新了文档,提供了aar的SDK集成方式,看样子集成起来会更方便一些,目前我还没进行验证,因此这里的SDK下载链接其实是上一个版本的、已验证过的SDK。

集成步骤:

  1. 将SDK的文件alipaySdk-20180601.jar拷贝至app/libs目录下

  2. build.gradle中,配置dependencies

    1
    2
    3
    4
    
    dependencies {
        implementation fileTree(dir: "libs", include: ["*.jar"])
        // ... more ...
    }
    

三、AndroidManifest.xml

AndroidManifest.xml中添加支付宝SDK运行所需的权限和Activities:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    <!-- 支付宝所需要的权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
    <!-- 支付宝 activities -->
    <activity
        android:name="com.alipay.sdk.app.H5PayActivity"
        android:configChanges="orientation|keyboardHidden|navigation|screenSize"
        android:exported="false"
        android:screenOrientation="behind"
        android:windowSoftInputMode="adjustResize|stateHidden" >
    </activity>

    <activity
        android:name="com.alipay.sdk.app.H5AuthActivity"
        android:configChanges="orientation|keyboardHidden|navigation"
        android:exported="false"
        android:screenOrientation="behind"
        android:windowSoftInputMode="adjustResize|stateHidden" >
    </activity>

四、新建alipay的package

在工程中创建一个package,如com.xxx.mobile.alipay

app/src/main/java/com/xxx/mobile/alipay
├── AlipayModule.kt
└── AlipayPackage.kt

五、AlipayModule.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.xxx.mobile.alipay

import android.util.Log
import com.alipay.sdk.app.PayTask
import com.facebook.react.bridge.*

class AlipayModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    companion object {
        val TAG = "AlipayModule"
    }

    override fun getName(): String {
        return "Alipay"
    }

    @ReactMethod
    fun pay(orderInfo: String, promise: Promise) {
        Thread(Runnable {
            Log.d(TAG, "alipay pay start")
            val map = Arguments.createMap()
            val alipay = PayTask(currentActivity)
            val result = alipay.payV2(orderInfo, true)
            for ((key, value) in result) {
                map.putString(key, value)
            }
            Log.d(TAG, "alipay pay finish$map")
            promise.resolve(map)
        }).start()
    }
}

六、AlipayPackage.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.xxx.mobile.alipay

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class AlipayPackage : ReactPackage {

    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList()
    }

    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return listOf<NativeModule>(AlipayModule(reactContext))
    }
}

七、App.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import com.xxx.mobile.alipay.AlipayPackage // ← here
class App : Application(), ReactApplication {

    private val mReactNativeHost = object : ReactNativeHost(this) {
        override fun getUseDeveloperSupport(): Boolean {
            return BuildConfig.DEBUG
        }
        override fun getPackages(): List<ReactPackage> {
            return listOf(
                    MainReactPackage(),
                    // ...
                    AlipayPackage() // ← here
            )
        }
        override fun getJSMainModuleName(): String {
            return "index"
        }
    }
    // ...
}

PS: 此App.kt实际是由MainApplication.java转为Kotlin语言的文件

八、proguard-rules.pro

app/proguard-rules.pro文件中,添加以下内容:

# 支付宝
-keep class com.alipay.android.app.IAlixPay{*;}
-keep class com.alipay.android.app.IAlixPay$Stub{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;}
-keep class com.alipay.sdk.app.PayTask{ public *;}
-keep class com.alipay.sdk.app.AuthTask{ public *;}
-keep class com.alipay.sdk.app.H5PayCallback {
    <fields>;
    <methods>;
}
-keep class com.alipay.android.phone.mrpc.core.** { *; }
-keep class com.alipay.apmobilesecuritysdk.** { *; }
-keep class com.alipay.mobile.framework.service.annotation.** { *; }
-keep class com.alipay.mobilesecuritysdk.face.** { *; }
-keep class com.alipay.tscenter.biz.rpc.** { *; }
-keep class org.json.alipay.** { *; }
-keep class com.alipay.tscenter.** { *; }
-keep class com.ta.utdid2.** { *;}

iOS

一、目录结构

文章所述将涉及以下文件的改动:

├── AppDelegate.m
└── Info.plist
Alipay/
├── AlipayModule.h
└── AlipayModule.m
thirdparty/alipay/
├── AlipaySDK.bundle
└── AlipaySDK.framework

二、集成SDK

  1. 将下载的SDK文件,拷贝到ios/thirdparty/alipay/目录下,如图所示:
  2. 然后通过XcodeProjectAdd Files to "<Project>" ...,把AlipaySDK.bundleAlipaySDK.framework添加至项目中,如下图:
  3. XcodeProjectTargetGeneralLinked Frameworks and Libraries,添加以下项

三、AlipayModule.h

1
2
3
4
5
#import <React/RCTBridgeModule.h>
#import <React/RCTLog.h>

@interface AlipayModule : NSObject <RCTBridgeModule>
@end

四、AlipayModule.m

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#import <Foundation/Foundation.h>
#import "AlipayModule.h"
#import <AlipaySDK/AlipaySDK.h>

@implementation AlipayModule
{
  RCTPromiseResolveBlock resolveBlock;
}

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

- (void)handleAliPay:(NSNotification *)notification
{
  NSDictionary *resultDic =  [notification userInfo];
  NSLog(@"alipay pay finish: result: %@", resultDic);
  resolveBlock(resultDic);
}

RCT_EXPORT_METHOD(pay:(NSString *)orderInfo
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
  resolveBlock = resolve;
  NSLog(@"alipay pay start");
  NSString *appScheme = @"alipay<appname>"; // 注意,此处应填写的是Info.plist文件中的URL Schemes!
  // 调用支付结果开始支付
  [[AlipaySDK defaultService] payOrder:orderInfo fromScheme:appScheme callback:^(NSDictionary *resultDic) {
    NSLog(@"alipay pay finish: result: %@", resultDic);
  }];
}

RCT_EXPORT_MODULE(Alipay);
@end

PS: 别指望AlipayModule.m中的callback会真正的执行,实际上支付结果是通过AppDelegate.m(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options来获得的。AppDelegate.m收到支付结果之后,通过发送通知的形式来达到这个文件中的(void)handleAliPay:(NSNotification *)notification函数,再回调给JS前端。

五、AppDelegate.m

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#import <AlipaySDK/AlipaySDK.h>

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options
{
  NSLog(@"%s url: %@", __PRETTY_FUNCTION__, url);
  // 支付宝
  if ([url.host isEqualToString:@"safepay"]) {
    [self handleAlipayResult:url];
    return YES;
  }
  return NO;
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
  NSLog(@"%s url: %@, sourceApplication: %@", __PRETTY_FUNCTION__, url, sourceApplication);
  // 支付宝 com.alipay.iphoneclient
  if ([url.host isEqualToString:@"safepay"]) {
    [self handleAlipayResult:url];
    return YES;
  }
  return NO;
}

// 处理支付宝支付结果
- (void)handleAlipayResult:(NSURL *)url
{
  // 支付跳转支付宝钱包进行支付,处理支付结果
  [[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
    NSLog(@"alipy result = %@", resultDic);
    [self postAlipayNotify:resultDic];
  }];

  // 授权跳转支付宝钱包进行支付,处理支付结果
  [[AlipaySDK defaultService] processAuth_V2Result:url standbyCallback:^(NSDictionary *resultDic) {
    NSLog(@"alipay result via auth = %@", resultDic);
    [self postAlipayNotify:resultDic];
  }];
}

// 发送支付宝结果通知
- (void)postAlipayNotify:(NSDictionary *)resultDic
{
  NSNotification *notification = [NSNotification notificationWithName:@"AliPay" object:nil userInfo:resultDic];
  [[NSNotificationCenter defaultCenter] postNotification:notification];
}

六、Info.plist

XcodeProjectTargetInfoURL Types,添加alipay<appname>

PS: URL Schemes填写的内容,建议跟商户App有一定的标识度,要做到和其他的商户App不重复,否则可能会导致支付宝返回的结果无法正确跳回商户App。提示来源:官网: iOS集成流程

JavaScript

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
_aliPay = () => {
  this.setState({ payType: 1 });
  let params = {
    sid: this.props.sid,
    good_id: this.state.selectedid,
    pay_type: PayType.ALIPAY
  };

  this.props.purchase(
    params, // 第一步:去后台下单
    async result => {
      if (!TextUtil.isEmpty(result.data)) { // 第二步:下单成功后,返回调用支付宝App所需的参数信息
        let orderId = result.data.order_id;
        let payRes = await alipay.pay(result.data.pay_info); // 第三步:调用支付宝App支付
        console.log('payresult: ', payRes);
        if (!TextUtil.isEmpty(payRes)) {
          let payStatus = parseInt(payRes.resultStatus);
          switch (payStatus) {
            case 9000:
              this._queryPurchaseStatus(orderId); // 第四步:去后台查询订单状态、发货状态
              ToastUtil.showToast('订单支付成功');
              break;
            case 8000:
              this._queryPurchaseStatus(orderId);
              ToastUtil.showToast('正在处理中,支付结果未知');
              break;
            case 5000:
              ToastUtil.showToast('重复请求');
              break;
            case 4000:
              ToastUtil.showToast('订单支付失败');
              break;
            case 6001:
              ToastUtil.showToast('用户中途取消');
              break;
            case 6002:
              ToastUtil.showToast('网络连接出错');
              break;
            case 6004:
              ToastUtil.showToast('支付结果未知');
              break;
            default:
              ToastUtil.showToast('支付异常');
              break;
          }
        }
      }
    },
    err => {
      ToastUtil.showToast('支付失败!'); // TODO 判断失败类型
      console.warn('_aliPay: 支付失败,错误信息:', err);
    }
  );
};

参考链接:

  1. 一步一步将支付宝集成到 react-native 应用
  2. 官网:客户端 Android 集成流程
  3. 官网:App支付iOS集成流程
  4. 官网: resultStatus结果码含义