我喜欢的技术篇学习封装的demo材料常用方法

支付宝支付iOS集成与二次封装

2017-06-06  本文已影响380人  reyzhang

本以为像阿里这样的大公司,在细节处理上有独到之处。然而没想到在使用了支付宝的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
}

写在最后:

如果我的文章对你有所帮助,请帮忙点个赞👍,谢谢!

上一篇下一篇

猜你喜欢

热点阅读