微信、支付宝、银联、Paypal 支付组件封装
前言
移动支付为人们的生活提供了便捷,越来越多的应用也都开始集成了移动支付功能。国内目前的几大支付平台分别有微信、支付宝、银联,国际的有Paypal, ApplePay等。 大部分的电商应用基本上对时下这几个支付平台都有集成,为用户下单支付提供更多的便利及选择。
本人目前所从事的旅游行业也是要与电商结合,会在线销售全球不同目的地的景点门票,酒店等产品。所以也免不了涉及到移动支付这一块。 在使用各家平台的iOS sdk时必然会碰到很多集成上的问题,但所幸都一一解决。早就想找个时间把集成时碰到的问题写下来,一来算是做个笔记,备忘。二来,方便后来者。如果我的文章真的帮助到了你,甚幸。
我在写这篇文章之前已经发表了有关微信,支付宝,Paypal支付的集成与二次封装的文章。在阅读本文前需要先将其他三篇文章都阅读一遍,才会对今天所讲的内容有个串连。
分析
本组件的目的是要将微信,支付宝,银联,Paypal的调用统一接口。其中,微信与银联的支付流程都需要在调起支付控件前向后端提交数据并拿着返回的结果去调用支付的sdk方法。微信调用后端接口返回PrepayData, 银联调用后端接口返回交易的流水号tn。 而支付宝与Paypal则不需要去请求后端接口,就可直接调起sdk来完成支付。提这个的目的就是需要我们的组件在统一接口时,对每种不同的情况都要考虑到。
上面提到的三篇文章已经在原有sdk的基础上做了二次封装,为了统一调用接口,思考一下,这个组件需要接收哪些数据 ?
- 调用哪个支付平台?需要告知组件当前要调起的支付平台
- *对于Paypal组件,二次封装后最终返回的是PayPalPaymentViewController,我们需要在支付组件的内部来完成PayPalPaymentViewController 的转场显示,那么就需要通过一个控制器推出来,所以这个控制器就是我们需要传递的参数之一。 *
- 无论是哪个支付平台,都需要传递订单信息,这个是必要参数。
- 支付回调, 各平台的回调方法不一,但为了统一调用接口,支付回调的方法我们也应该进行统一
封装
通过上面的分析,组件的职责已经很明确了。这里我们使用外观模式来对支付组件做进一步的封装
- 首先定义支付平台类型,使用枚举再合适不过了
/**
* 定义支付方式
*/
typedef NS_ENUM(NSInteger,PayMethod) {
WXPAY = 0, // -- 微信支付
ALIPAY = 1, // -- 支付宝支付
UPPAY = 2, // -- 银联支付
PAYPAL = 3, // -- PAYPAL支付
};
- 统一支付回调接口
typedef NS_ENUM(NSInteger,PayStatus) {
PaySuccess,
PayCancel,
PayFail,
PayNotInstalled,
};
@protocol PayManagerDelegate <NSObject>
- (void)paymanager:(PayManager *)manager paySuccess:(NSDictionary *)result;
- (void)paymanager:(PayManager *)manager payFailure:(NSError *)error;
@end
分析中提到了组件需要接收的参数,支付方式,订单信息,组件的委托者。 多个参数都是必须要提供的,为了更便于使用,这里将传递参数设计成链式写法来处理。
- 完整的PayManager组件的头文件如下:
// Created by reyzhang on 2017/6/3.
// Copyright © 2017年 hhkx. All rights reserved.
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger,PayStatus) {
PaySuccess,
PayCancel,
PayFail,
PayNotInstalled,
};
/**
* 定义支付方式
*/
typedef NS_ENUM(NSInteger,PayMethod) {
WXPAY = 0, // -- 微信支付
ALIPAY = 1, // -- 支付宝支付
UPPAY = 2, // -- 银联支付
PAYPAL = 3, // -- PAYPAL支付
};
`
@class PayManager;
@protocol PayManagerDelegate <NSObject>
- (void)paymanager:(PayManager *)manager paySuccess:(NSDictionary *)result;
- (void)paymanager:(PayManager *)manager payFailure:(NSError *)error;
@end
@class OrderInfo;
@interface PayManager : NSObject
- (instancetype)initWithDelegate:(id<PayManagerDelegate>) delegate;
// -- 设置支付方式
- (PayManager * (^)(PayMethod))method;
// -- 设置支付订单数据
- (PayManager * (^)(OrderInfo *))order;
// -- 设置支付管理者所在的控制器对象
- (PayManager * (^)(UIViewController *))controller;
// -- 开始支付 Action
- (void)startPay;
@end
- 实现文件,不多说,上代码
// Created by reyzhang on 2017/6/3.
// Copyright © 2017年 hhkx. All rights reserved.
#import "PayManager.h"
#import "OrderInfo.h"
// pay method
#import <AlipaySDK/AlipaySDK.h>
#import "AlipayService.h"
#import <UPPayPlugin.h>
#import "WXApi.h"
#import "WXPayService.h"
#import "PayPalService.h"
@interface PayManager() <AlipayServiceDelegate,UPPayPluginDelegate,PayPalPaymentDelegate>
@property (nonatomic,strong) id<PayManagerDelegate> delegate;
@end
@implementation PayManager {
PayMethod _payMethod;
OrderInfo *_order;
UIViewController *_controller;
NSMutableDictionary *_params;
AlipayService *_alipay;
PayPalService *_paypalService;
}
- (instancetype)initWithDelegate:(id<PayManagerDelegate>) delegate {
self = [super init];
if (self) {
self.delegate = delegate;
_params = @{}.mutableCopy;
}
return self;
}
// -- 设置支付方式
- (PayManager * (^)(PayMethod))method {
return ^PayManager *(PayMethod method) {
_payMethod = method;
return self;
};
}
// -- 设置支付订单数据
- (PayManager * (^)(OrderInfo *))order {
return ^PayManager * (OrderInfo *order) {
_order = order;
return self;
};
}
// -- 设置支付管理者所在的控制器对象
- (PayManager * (^)(UIViewController *))controller {
return ^PayManager * (UIViewController *controller) {
_controller = controller;
return self;
};
}
// -- 开始支付动作
- (void)startPay {
NSAssert(_module.length > 0, @"未传递支付模块");
NSAssert(_order!=nil, @"未传递订单");
NSAssert(self.delegate != nil, @"未指定回调代理");
// -- 组装调用后端接口需要传递的数据
[_params setObject:@(_order.orderId) forKey:@"id"];
// ..... 等等,根据业务接口中实际需要传递
// -- 根据接收的支付方式,选择支付方法
if (_payMethod == WXPAY) {
[self WXPay];
}else if (_payMethod == ALIPAY) {
[self Alipay];
}else if (_payMethod == UPPAY) {
[self UPPay];
}else if (_payMethod == PAYPAL) {
[self payPal];
}
}
#pragma mark 支付
////银联支付
//银联支付目测只需要2个参数 1-- tn 其实就是订单号 2-- mode 是测试环境还是线上环境 "00" 表示线上环境"01"表示测试环境
- (void)UPPay {
// -- 需要调用后台接口返回交易流水
[HttpTools �getUPPayTNRequestWithParams:_params callback:^(id result) {
NSString *tn = [result stringForKey:@"tn"];
if (![tn isEqualToString:@""]) {
[UPPayPlugin startPay:tn mode:kUPPayMode viewController:_controller delegate:self];
}
}];
}
////支付宝支付
- (void)Alipay {
//包含支付的一些配置 reyzhang
// AlipayConfig *config = [AlipayConfig sharedInstance];
// config.notifyURL =[BASEURL stringByAppendingString:@"sales-notify.html"]; ///配置回调的url,各模块回调的url也不同
_alipay = [[AlipayService alloc] initWithOrder:_order];
_alipay.delegate = self;
[_alipay startPay]; /////所有信息准备完毕,开始支付 reyzhang
}
- (void)WXPay {
// -- 同银联支付,微信支付也需要向后端接口请求预支付数据
[HttpTools �getPrepayDataWithParams:_params callback:^(id result) {
NSDictionary *prepayData = [result objectForKey:@"data"];
PayReq *req = [WXPayService getPayRequest:prepayData];
[[WXPayService sharedInstance] startPayWithReq:req callback:^(BaseResp *resp) {
if (!resp) {
return ;
}
if (resp.errCode == WXSuccess) {
NSString *message = Localized(@"pay.success", @"");
// -- 成功回调
if ([self.delegate respondsToSelector:@selector(paymanager:paySuccess:)]) {
[self.delegate paymanager:self paySuccess:@{@"memo":message}];
}
}else {
NSString *message = Localized(@"pay.fail", @"");
NSError *error = [NSError errorWithDomain:@"" code:200 userInfo:@{@"memo":message}];
// -- 失败回调
if ([self.delegate respondsToSelector:@selector(paymanager:payFailure:)]) {
[self.delegate paymanager:self payFailure:error];
}
}
}];
}
/// paypal支付
- (void)payPal {
_paypalService = [[PayPalService alloc] initWithOrder:_order
andDelegate:self];
[_paypalService startPayWithPayViewController:^(PayPalPaymentViewController *payvc) {
[_controller presentViewController:payvc animated:YES completion:nil];
}];
}
#pragma mark 支付宝支付回调
///支付成功
- (void)Alipay:(AlipayService *)alipay paySuccess:(NSDictionary *)result {
if ([self.delegate respondsToSelector:@selector(paymanager:paySuccess:)]) {
[self.delegate paymanager:self paySuccess:result];
}
}
///支付失败
- (void)Alipay:(AlipayService *)alipay payFailure:(NSError *)error {
if ([self.delegate respondsToSelector:@selector(paymanager:payFailure:)]) {
[self.delegate paymanager:self payFailure:error];
}
}
#pragma mark 银联支付回调
///真坑爹。返回的result结果 success、fail、cancel,分别代表:支付成功、支付失败、用户取消支付
- (void)UPPayPluginResult:(NSString*)result {
MyLog(@"UPPay result:%@",result);
NSString *message = @"";
NSError *error = nil;
if ([result isEqualToString:@"fail"]) {
message = Localized(@"pay.fail", @"");
error = [NSError errorWithDomain:@"" code:200 userInfo:@{@"memo":message}];
// -- 失败回调
if ([self.delegate respondsToSelector:@selector(paymanager:payFailure:)]) {
[self.delegate paymanager:self payFailure:error];
}
}else if ([result isEqualToString:@"success"]) {
message = Localized(@"pay.success", @"");
// -- 成功回调
if ([self.delegate respondsToSelector:@selector(paymanager:paySuccess:)]) {
[self.delegate paymanager:self paySuccess:@{@"memo":message}];
}
}else if ([result isEqualToString:@"cancel"]) {
message = Localized(@"pay.cancel", @"");
error = [NSError errorWithDomain:@"" code:201 userInfo:@{@"memo":message}];
// -- 失败回调
if ([self.delegate respondsToSelector:@selector(paymanager:payFailure:)]) {
[self.delegate paymanager:self payFailure:error];
}
}
}
#pragma mark paypal支付回调
- (void)payPalPaymentViewController:(PayPalPaymentViewController *)paymentViewController didCompletePayment:(PayPalPayment *)completedPayment {
MyLog(@"PayPal Payment Success! result:%@",completedPayment.confirmation);
NSString *resultText = [completedPayment description];
[self verifyCompletedPayment:completedPayment];
[_controller dismissViewControllerAnimated:YES completion:nil];
// -- 成功回调
if ([self.delegate respondsToSelector:@selector(paymanager:paySuccess:)]) {
[self.delegate paymanager:self paySuccess:@{@"memo":resultText}];
}
}
- (void)payPalPaymentDidCancel:(PayPalPaymentViewController *)paymentViewController {
// NSLog(@"PayPal Payment Canceled");
[_controller dismissViewControllerAnimated:YES completion:nil];
NSError *error = [NSError errorWithDomain:@"" code:201 userInfo:@{@"memo":@"用户取消"}];
// -- 失败回调
if ([self.delegate respondsToSelector:@selector(paymanager:payFailure:)]) {
[self.delegate paymanager:self payFailure:error];
}
}
- (void)verifyCompletedPayment:(PayPalPayment *)completedPayment {
// Send the entire confirmation dictionary
// NSData *confirmation = [NSJSONSerialization dataWithJSONObject:completedPayment.confirmation
// options:0
// error:nil];
// Send confirmation to your server; your server should verify the proof of payment
// and give the user their goods or services. If the server is not reachable, save
// the confirmation and try again later.
// 在Paypal企业管理后台中配置回调url, 支付成功后paypal会自动发送IPN请求到这个回调url,在这里你可以更新订单的支付状态
}
@end
思考重构
分析中我们也提到,微信与银联都需要先去请求后端接口,拿到相应的数据后再去调起支付。在上面的代码中我们将网络请求写进了组件里,这样做的坏处是我们需要另外依赖HttpTools工具,以及扩展性也不强。这就导致,当我们移植组件到另外一个项目中时,HttpTools工具需要一块添加。
所以在请求后端数据的位置,需要考虑重构。我们更希望是通过插件的形式来获取外部传递过来的数据,再完成支付。
调用
各组件独立的配置还是要设置的
// -- 配置微信支付
- (void)setupWXPay {
[WXApi registerApp:@"your appkey" withDescription:@"description"];
}
///支付宝支付配置 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
}
// -- 配置Paypal支付
- (void)setupPayPal {
[PayPalMobile initializeWithClientIdsForEnvironments:@{PayPalEnvironmentProduction : @"your production_key",
PayPalEnvironmentSandbox : @"your sandbox_key"}];
PayPalConfig *config = [PayPalConfig sharedInstance];
config.acceptCreditCards = YES; // -- 配置是否接受信用卡
config.merchantName = @"merchantName"; // -- 配置商户名称
// -- 配置支付环境 PayPalEnvironmentSandbox, PayPalEnvironmentProduction
config.environment = PayPalEnvironmentProduction;
}
所有的支付配置设置好后,在支付时就可以通过PayManager来调起了
self.manager = [[PayManager alloc] initWithDelegate:self];
self.manager.method(paymethod)
.order(self.orderInfo)
.controller(self);
[self.manager startPay];
#pragma mark PayManagerDelegate
- (void)paymanager:(PayManager *)manager paySuccess:(NSDictionary *)result {
// -- 支付成功
}
- (void)paymanager:(PayManager *)manager payFailure:(NSError *)error {
// -- 支付失败
}
支付组件的封装算是完结了,后续如果有新的支付方式加入进来,还会更新的。有些地方在设计时考虑的可能也不尽完美。如果你有更好的想法,可以给我评论里留言。
写在最后:
如果我的文章对你有所帮助,请帮忙点个赞👍,谢谢!