本以为像阿里这样的大公司,在细节处理上有独到之处。然而没想到在使用了支付宝的sdk后,多少会有些改观。接下来介绍一下我在进行二次封装时的思路。
下载
不知道出于什么目的或原因,想找到支付宝sdk的下载地址不是那么容易。现在支付宝App支付的SDK已全面升级,具体的下载地址请“猛击些处”。
集成
官网上有关集成的文档写的还是比较清楚的,就不再赘述了。具体的集成流程看这里
支付宝sdk官方集成文档
二次封装
进行再次封装的目的很简单,就是更便于客户端使用及复用。
- 关于支付配置
支付宝的sdk在支付时需要提供商户的pid,收款账号,私钥,公钥等信息。对于这些配置,特设计了一个单例类来保存这些配置,这样做的好处是
- 便于对这些配置统一管理
- 参数被包装后,减少了参数的传递
- 非硬编码,运行时可以变更属性的值
AlipayConfig单例头文件
#import <Foundation/Foundation.h>
@interface AlipayConfig : NSObject
//商户PID
@property (nonatomic,strong) NSString *Partner;
//商户收款账号
@property (nonatomic,strong) NSString *Seller;
//商户私钥,pkcs8格式
@property (nonatomic,strong) NSString *RSA_Private;
//支付宝公钥
@property (nonatomic,strong) NSString *RSA_Public;
// 服务器异步通知页面路径
@property (nonatomic,strong) NSString *notifyURL;
//应用注册自定义scheme。支付成功后回调应用
@property (nonatomic,strong) NSString *appScheme;
////单例
+ (instancetype)sharedInstance;
@end
AlipayConfig单例实现
#import "AlipayConfig.h"
static AlipayConfig *sharedInstance;
@implementation AlipayConfig
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [super allocWithZone:zone];
});
return sharedInstance;
}
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (id)init {
self = [super init];
if (self) {
///支付宝公钥 可参见: http://doc.open.alipay.com/doc2/detail?treeId=58&articleId=103242&docType=1
self.RSA_Public = @"your RSA_Public";
self.appScheme = @"your appScheme";
////需要使用pkcs8的格式 注意: 私钥及公钥都有一方提供。
self.RSA_Private = @"your RSA_Private";
}
return self;
}
@end
- 关于订单信息
虽然支付宝的sdk提供了一个Order类, 但为了一些业务上的订单需要,特单独创建了一个OrderInfo类,用于保存业务订单中的数据,再最终依据这个自定义的OrderInfo类来构造支付宝支付时的Order类。
OrderInfo 类
#import <Foundation/Foundation.h>
@interface OrderInfo : NSObject
////订单id
@property (nonatomic,assign) NSInteger orderId;
////交易的订单号
@property(nonatomic, copy) NSString * tradeNO;
///产品名称
@property(nonatomic, copy) NSString * productName;
///产品描述
@property(nonatomic, copy) NSString * productDescription;
///支付金额
@property(nonatomic, copy) NSString * amount;
////[生成的订单校验信息]
@property(nonatomic,copy) NSString *infoSign;
- (id)initWithDictionary:(id)orderDic;
@end
- 支付服务类
准备好了支付的配置,及订单信息就可以通过调起支付宝的sdk来进行支付了。
上面我们将支付需要的配置,通过单例来存储,这样在支付服务封装时,支付的配置信息就可以不用再通过参数来传递,直接去单例对象中读取。
而订单信息是可变项,需要一个订单一个实例,所以支付服务类在封装时需要将订单信息传递进去,可以在服务类初始化时设计一个适配的实例方法来传递订单信息。
- (instancetype)initWithOrder:(OrderInfo *)orderInfo;
如果要回传支付成功与否的状态,最好再设计一个支付回调的delegate
////支付代理方法
@protocol AlipayServiceDelegate <NSObject>
- (void)Alipay:(AlipayService *)alipay paySuccess:(NSDictionary *)result;
- (void)Alipay:(AlipayService *)alipay payFailure:(NSError *)error;
@end
通过上面的封装最终完整的代码如下
AlipayService头文件 .h
// Created by reyzhang on 2017/3/31.
// Copyright © 2017年 hhkx All rights reserved.
// 支付宝支付服务封装
#import <Foundation/Foundation.h>
#import <AlipaySDK/AlipaySDK.h>
@class AlipayService;
@class AlipayConfig;
@class OrderInfo;
////支付代理方法
@protocol AlipayServiceDelegate <NSObject>
- (void)Alipay:(AlipayService *)alipay paySuccess:(NSDictionary *)result;
- (void)Alipay:(AlipayService *)alipay payFailure:(NSError *)error;
@end
////block方式的回调 reyzhang
typedef void(^CompletionHandlerBlock)(NSDictionary *result, NSError *error);
@interface AlipayService : NSObject
@property (nonatomic,strong) OrderInfo *orderInfo;
@property (weak,nonatomic) id <AlipayServiceDelegate> delegate;
@property (nonatomic,copy) CompletionHandlerBlock block;
- (instancetype)initWithOrder:(OrderInfo *)orderInfo;
- (void)setCompletionHandler:(CompletionHandlerBlock)block;
- (void)startPay;
@end
AlipayService实现文件 .m
// Created by reyzhang on 2017/3/31.
// Copyright © 2017年 hhkx All rights reserved.
// 支付宝支付服务封装
#import "AlipayService.h"
#import "APAuthV2Info.h"
#import "Order.h"
#import "DataSigner.h"
#import "AlipayConfig.h"
#import "OrderInfo.h"
#import <AlipaySDK/AlipaySDK.h>
@implementation AlipayService
- (instancetype)initWithOrder:(OrderInfo *)orderInfo {
if (self = [super init]) {
self.orderInfo = orderInfo;
}
return self;
}
- (void)setCompletionHandler:(CompletionHandlerBlock)block {
_block = block;
}
///开始支付
- (void)startPay {
NSAssert([AlipayConfig sharedInstance].Partner.length>0, @"未配置商户ID");
NSAssert([AlipayConfig sharedInstance].Seller.length>0, @"未配置商户收款账号");
NSAssert([AlipayConfig sharedInstance].RSA_Private.length>0, @"未配置商户私钥");
NSAssert(self.orderInfo, @"未找到订单信息");
// -- 解决模拟器调试时,出现 -canOpenURL: failed的问题
// UIWindow *window = [[UIApplication sharedApplication] windows][0];
// if (window.hidden) {
// [window setHidden:NO];
// }
Order *order = [self getOrder];
//将商品信息拼接成字符串
NSString *orderSpec = [order description];
NSLog(@"orderSpec = %@",orderSpec);
//获取私钥并将商户信息签名,外部商户可以根据情况存放私钥和签名,只需要遵循RSA签名规范,并将签名字符串base64编码和UrlEncode
NSString *kRSA_PRIVATE = [AlipayConfig sharedInstance].RSA_Private;
id<DataSigner> signer = CreateRSADataSigner(kRSA_PRIVATE);
NSString *signedString = [signer signString:orderSpec];
//将签名成功字符串格式化为订单字符串,请严格按照该格式
NSString *orderString = nil;
if (signedString != nil) {
orderString = [NSString stringWithFormat:@"%@&sign=\"%@\"&sign_type=\"%@\"",
orderSpec, signedString, @"RSA"];
NSString *appScheme = [AlipayConfig sharedInstance].appScheme;
[[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary *resultDic) {
// [window setHidden:YES]; // -- 无论成功失败与否,都要让window重新显示
NSLog(@"reslut = %@",resultDic);
NSError *error = nil;
if ([resultDic[@"resultStatus"] isEqualToString:@"9000"]) {
if ([self.delegate respondsToSelector:@selector(Alipay:paySuccess:)]) {
[self.delegate Alipay:self paySuccess:resultDic];
}
}else {
error = [NSError errorWithDomain:@"" code:[resultDic[@"resultStatus"] integerValue] userInfo:resultDic];
if ([self.delegate respondsToSelector:@selector(Alipay:payFailure:)]) {
[self.delegate Alipay:self payFailure:error];
}
}
////block块回调 reyzhang
if (self.block) {
self.block(resultDic,error);
}
}];
}else {
////签名时失败
// [self showAlert:@"警告" message:@"签名失败,请检查"];
}
}
- (Order *)getOrder {
/*
*生成订单信息及签名
*/
//将商品信息赋予AlixPayOrder的成员变量
Order *order = [[Order alloc] init];
// 签约合作者身份ID
order.partner = [AlipayConfig sharedInstance].Partner;
// 签约卖家支付宝账号
order.seller = [AlipayConfig sharedInstance].Seller;
// 商户网站唯一订单号
order.tradeNO = self.orderInfo.tradeNO; //[self generateTradeNO]; //订单ID(由商家自行制定)
order.productName = self.orderInfo.productName; //商品标题
order.productDescription = self.orderInfo.productDescription; //商品描述
//
double doubleAmount=[self.orderInfo.amount doubleValue];
order.amount = [NSString stringWithFormat:@"%.2f",doubleAmount]; //商品价格
// 服务器异步通知页面路径
order.notifyURL = [AlipayConfig sharedInstance].notifyURL; //回调URL
// 服务接口名称, 固定值
order.service = @"mobile.securitypay.pay";
// 支付类型, 固定值
order.paymentType = @"1";
// 参数编码, 固定值
order.inputCharset = @"utf-8";
// 设置未付款交易的超时时间
// 默认30分钟,一旦超时,该笔交易就会自动被关闭。
// 取值范围:1m~15d。
// m-分钟,h-小时,d-天,1c-当天(无论交易何时创建,都在0点关闭)。
// 该参数数值不接受小数点,如1.5h,可转换为90m。
order.itBPay = @"6h";
// 支付宝处理完请求后,当前页面跳转到商户指定页面的路径,可空
order.showUrl = @"m.alipay.com";
return order;
}
@end
调用
在应用启动时配置支付信息
///支付宝支付配置 reyzhang
- (void)setupAlipay {
AlipayConfig *config = [AlipayConfig sharedInstance];
config.Partner = @"your partner"; // -- 商户PID
config.Seller = @"your seller"; // -- 商户收款账号
config.notifyURL = @"http://xxx"; // -- 配置支付回调URL
config.appScheme = @"your appScheme"; // -- App 自定义url scheme
}
开始支付时,先获取到订单信息,并传递给支付服务,设置支付回调订阅者 ,并开始支付
OrderInfo *orderInfo = yourOrderInfo;
AlipayService *alipay = [[AlipayService alloc] initWithOrder:orderInfo];
alipay.delegate = self;
[alipay startPay]; /////所有信息准备完毕,开始支付 reyzhang
#pragma mark 支付宝支付回调
///支付成功
- (void)Alipay:(AlipayService *)alipay paySuccess:(NSDictionary *)result {
}
///支付失败
- (void)Alipay:(AlipayService *)alipay payFailure:(NSError *)error {
}
升级SDK后的重构
新版本的支付宝回调不再走
payOrder:fromScheme:callback:
这个sdk方法的回调(注意方法还是会被调用,但回调时不走该方法的callback),而是被转到了AppDelegate中
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;
//9.0以后使用新API接口
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options;
进行处理支付回调。
- (void)handleOpenURL:(NSURL*)url {
if ([url.host isEqualToString:@"safepay"]) { //跳转支付宝钱包进行支付,处理支付结果
[[AlipaySDK defaultService]
processOrderWithPaymentResult:url
standbyCallback:^(NSDictionary *resultDic) {
NSLog(@"result = %@",resultDic);
}];
}
}
如上的封装,需要做些改变。为了能将在AppDelegate中接收到的回调数据传递到实体对象AlipayService中且不破坏原有封装性,这里使用通知中心NSNotificationCenter来传递数据。
AlipayService.h的修改如下,在头文件中声明一个通知中心名称
FOUNDATION_EXTERN NSString *const ALIPAY_CALLBACK_NOTIFICATION;
AlipayService.m 的修改如下,添加通知中心的监听,及处理。
NSString *const ALIPAY_CALLBACK_NOTIFICATION = @"Alipay_callback_notification";
///开始支付
- (void)startPay {
NSAssert([AlipayConfig sharedInstance].Partner.length>0, @"未配置商户ID");
NSAssert([AlipayConfig sharedInstance].Seller.length>0, @"未配置商户收款账号");
NSAssert([AlipayConfig sharedInstance].RSA_Private.length>0, @"未配置商户私钥");
NSAssert(self.orderInfo, @"未找到订单信息");
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedNotification:) name:ALIPAY_CALLBACK_NOTIFICATION object:nil];
Order *order = [self getOrder];
//将商品信息拼接成字符串
NSString *orderSpec = [order description];
NSLog(@"orderSpec = %@",orderSpec);
//获取私钥并将商户信息签名,外部商户可以根据情况存放私钥和签名,只需要遵循RSA签名规范,并将签名字符串base64编码和UrlEncode
NSString *kRSA_PRIVATE = [AlipayConfig sharedInstance].RSA_Private;
id<DataSigner> signer = CreateRSADataSigner(kRSA_PRIVATE);
NSString *signedString = [signer signString:orderSpec];
//将签名成功字符串格式化为订单字符串,请严格按照该格式
NSString *orderString = nil;
if (signedString != nil) {
orderString = [NSString stringWithFormat:@"%@&sign=\"%@\"&sign_type=\"%@\"",
orderSpec, signedString, @"RSA"];
NSString *appScheme = [AlipayConfig sharedInstance].appScheme;
[[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary *resultDic) {
// [window setHidden:YES]; // -- 无论成功失败与否,都要让window重新显示
NSLog(@"reslut = %@",resultDic);
[self callbackHandler:resultDic];
}];
}else {
////签名时失败
// [self showAlert:@"警告" message:@"签名失败,请检查"];
}
}
- (void)receivedNotification:(NSNotification *)notif {
NSDictionary *resultDic = notif.object;
[self callbackHandler:resultDic];
}
- (void)callbackHandler:(NSDictionary *)resultDic {
NSError *error = nil;
if ([[resultDic stringForKey:@"resultStatus"] isEqualToString:@"9000"]) {
if ([self.delegate respondsToSelector:@selector(Alipay:paySuccess:)]) {
[self.delegate Alipay:self paySuccess:resultDic];
}
}else {
error = [NSError errorWithDomain:@"" code:[[resultDic stringForKey:@"resultStatus"] integerValue] userInfo:resultDic];
if ([self.delegate respondsToSelector:@selector(Alipay:payFailure:)]) {
[self.delegate Alipay:self payFailure:error];
}
}
////block块回调 reyzhang
if (self.block) {
self.block(resultDic,error);
}
// -- 移除监听
[[NSNotificationCenter defaultCenter] removeObserver:self name:ALIPAY_CALLBACK_NOTIFICATION object:nil];
}
在AppDelegate中的回调处理
- (void)handleOpenURL:(NSURL*)url {
if ([url.host isEqualToString:@"safepay"]) { //跳转支付宝钱包进行支付,处理支付结果
[[AlipaySDK defaultService]
processOrderWithPaymentResult:url
standbyCallback:^(NSDictionary *resultDic) {
NSLog(@"result = %@",resultDic);
// -- 发送通知
[[NSNotificationCenter defaultCenter] postNotificationName:ALIPAY_CALLBACK_NOTIFICATION object:resultDic];
}];
注意事项
为避免AlipayService在使用时被提前释放,导致通知无法被处理的情况,需要在使用AlipayService时声明为局部变量或属性使用。注意AlipayService的作用域,避免出现如下情况的调用
- (void)startPay {
OrderInfo *orderInfo = yourOrderInfo;
AlipayService *alipay = [[AlipayService alloc] initWithOrder:orderInfo];
alipay.delegate = self;
[alipay startPay]; /////所有信息准备完毕,开始支付 reyzhang
}
这样使用后,alipay对象随时可能会被释放,导致在接收通知时crash.
改成在控制器中使用前声明成属性或局部变量
@property(nonatomic,strong) AlipayService *payService;
- (void)startPay {
OrderInfo *orderInfo = yourOrderInfo;
self.payService = [[AlipayService alloc] initWithOrder:orderInfo];
self.payService.delegate = self;
[self.payService startPay]; /////所有信息准备完毕,开始支付 reyzhang
}
写在最后:
完整的支付组件代码请前往:RZPayManager
在README.md文件中有该组件的使用详解,如果喜欢,点关注支持一下。