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

作者: reyzhang | 来源:发表于2017-06-06 15:09 被阅读380次

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

    写在最后:

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

    相关文章

      网友评论

        本文标题:支付宝支付iOS集成与二次封装

        本文链接:https://www.haomeiwen.com/subject/nivgfxtx.html