前言
这是第三篇关于支付封装的文章了,前两篇是关于支付宝和微信支付的封装。如果您也对这两篇文章感兴趣可以移步到
中查阅。同时,如果你有更好的建议,可以在评论里给我留言,一起讨论。
Paypal 集成
Paypal iOS sdk的下载请到github的地址:https://github.com/paypal/PayPal-iOS-SDK 中查看。在项目的README.md中已经介绍了如何将Paypal sdk 集成到项目中去。
如果你用 cocoapods 来管理你的第三方库,那么你只需要以下两行代码:
platform :ios, '8.0'
pod 'PayPal-iOS-SDK'
如果是非cocoapods方式就可以参考上面的文档来进行,不再赘述。
但无论是使用cocoapods或其他方式来导入Paypal SDK, 都不要忘记设置LSApplicationQueriesSchemes,在配置中加入:
com.paypal.ppclient.touch.v1
com.paypal.ppclient.touch.v2
org-appextension-feature-password-management
支付流程
Paypal的SDK 的支付流程相比前面提到的两种支付方式要更简单,不需要提供密钥或签名什么的,只需要准备好PayPalPayment对象,并将之传给SDK中的PayPalPaymentViewController即可调起支付界面进行支付。 代码是最好的文档,多看下github上提供的Demo.
Paypal 需要做的
- 推出 paypal 支付视图展示支付信息
- 和 paypal后端合作完成支付
- 返回支付结果给你的 App
你需要做的
- 接收paypal回调结果
- 根据结果做相应处理
注意事项:Paypal在支付成功后除了将结果返回给你的App外,还会向你在Paypal管理后台配置的notificationurl 发送消息(国外服务器,会有延时),后端人员需要在这个通知url中接收到支付的最终结果信息,再根据这个信息来处理诸如:订单状态变更等业务上的操作。
集成时遇到的问题
-
使用信用卡时导致crash
在设置Paypal配置时,将acceptCreditCards 设置为YES, 表示开启信用卡支付的支持。但不知什么原因在使用时导致应用crash。 配置如下:
_payPalConfiguration = [[PayPalConfiguration alloc] init];
// See PayPalConfiguration.h for details and default values.
// Should you wish to change any of the values, you can do so here.
// For example, if you wish to accept PayPal but not payment card payments, then add:
_payPalConfiguration.acceptCreditCards = YES;
// Or if you wish to have the user choose a Shipping Address from those already
// associated with the user's PayPal account, then add:
_payPalConfiguration.payPalShippingAddressOption = PayPalShippingAddressOptionPayPal;
解决: 通过搜索找到了问题所在,原因是由于少引入了libCardIO.a库。 这个库是独立出来的,所以这里要注意一下。
-
支付信息在后台显示乱码
这个问题困扰了我好久,在网上也搜索了很长时间,但最终也没能找到解决方案。首先能想到的是编码问题,在管理后台已经设置了UTF-8的编码,遗憾的是支付后的信息还是乱码。看来配置出在客户端,支付的配置统一在 PayPalConfiguration这个对象中来配置,那就试着把配置项一个个注释,最终还真试出来原因出在了哪里
self.merchantPrivacyPolicyURL = [NSURL URLWithString:@"https://www.paypal.com/webapps/mpp/ua/privacy-full"];
self.merchantUserAgreementURL = [NSURL URLWithString:@"https://www.paypal.com/webapps/mpp/ua/useragreement-full"];
就出在这两上属性设置上,把这两个属性注释掉就可以了,这种问题也真是醉了。
二次封装
-
Paypal支付环境
Paypal在支付时需要先配置支付环境。 sdk中共提供了三种支付环境,如下
/// Production (default): Normal, live environment. Real money gets moved.
/// This environment MUST be used for App Store submissions.
extern NSString * _Nonnull const PayPalEnvironmentProduction;
/// Sandbox: Uses the PayPal sandbox for transactions. Useful for development.
extern NSString * _Nonnull const PayPalEnvironmentSandbox;
/// NoNetwork: Mock mode. Does not submit transactions to PayPal. Fakes successful responses. Useful for unit tests.
extern NSString * _Nonnull const PayPalEnvironmentNoNetwork;
如果是测试环境,在支付时指定类型为 PayPalEnvironmentSandbox, 生产环境设置成PayPalEnvironmentProduction。这个环境的设置最好是能被配置,所以在设计时,将环境的设置通过配置对象来管理。
-
Paypal支付配置
Paypal支付前需要对支付的参数做配置。Paypal的sdk提供了PayPalConfiguration对象,用来配置诸如: 是否开启信用卡支付,商户名称,本地化语言等信息。在使用时可以直接使用这个PayPalConfiguration对象,但为了二次封装时不让Paypal的sdk中的对象直接被使用,以及需要扩展对环境的配置,这里另外自定义了一个PayPalConfig的单例对象,供外部使用,这个对象中只提供实际支付时被需要的属性。
PayPalConfig.h 代码如下:
#import <Foundation/Foundation.h>
#import "PayPalConfiguration.h"
@interface PayPalConfig : NSObject
// -- 是否允许接受信用卡
// For your apps, you will need to link to the libCardIO and dependent libraries.
@property (nonatomic,assign) BOOL acceptCreditCards;
// -- 商家名称
@property (nonatomic,strong) NSString *merchantName;
// -- 支付环境
@property (nonatomic,strong) NSString *environment;
+ (instancetype)sharedInstance;
@end
PayPalConfig.m 代码如下:
#import "PayPalConfig.h"
#import "PayPalMobile.h"
@implementation PayPalConfig
static PayPalConfig *_sharedInstance;
+ (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) {
self.acceptCreditCards = NO;
self.merchantName = @"merchantName";
self.environment = PayPalEnvironmentSandbox;
}
return self;
}
- (void)setEnvironment:(NSString *)environment {
_environment = environment;
// 重点:-- 每次变更支付环境时,告知paypal sdk。 且需要在应用启动时先进行ClientID的初始化,不然此处会报错
[PayPalMobile preconnectWithEnvironment:_environment];
}
@end
注意:在配置支付环境时,当每次变更支付环境后,同时也需要告知Paypal的sdk变更支付环境. 且需要在应用启动时一定要先进行ClientID的初始化,不然会crash,且异常会定位到此处
上面包装的PayPalConfig的对象最终还是要为sdk中的PayPalConfiguration对象提供数据,为了以自定义的PayPalConfig对象来实例化PayPalConfiguration,特对PayPalConfiguration新增了一个扩展 PayPalConfiguration+PayPalConfig.h
// -- 定义 PayPalConfiguration扩展
@interface PayPalConfiguration (PayPalConfig)
- (instancetype)initWithCommonConfig;
@end
实现文件如下:
@implementation PayPalConfiguration (PayPalConfig)
- (instancetype)initWithCommonConfig {
if (self = [super init]) {
[self commonInit];
}
return self;
}
- (void)commonInit {
PayPalConfig *_payPalConfig = [PayPalConfig sharedInstance];
//For your apps, you will need to link to the libCardIO and dependent libraries.
self.acceptCreditCards = _payPalConfig.acceptCreditCards;
self.merchantName = _payPalConfig.merchantName;
// self.merchantPrivacyPolicyURL = [NSURL URLWithString:@"https://www.paypal.com/webapps/mpp/ua/privacy-full"];
// self.merchantUserAgreementURL = [NSURL URLWithString:@"https://www.paypal.com/webapps/mpp/ua/useragreement-full"];
// Setting the languageOrLocale property is optional.
//
// If you do not set languageOrLocale, then the PayPalPaymentViewController will present
// its user interface according to the device's current language setting.
//
// Setting languageOrLocale to a particular language (e.g., @"es" for Spanish) or
// locale (e.g., @"es_MX" for Mexican Spanish) forces the PayPalPaymentViewController
// to use that language/locale.
//
// For full details, including a list of available languages and locales, see PayPalPaymentViewController.h.
// self.languageOrLocale = [NSLocale preferredLanguages][0];
self.languageOrLocale = [LanguageManager sharedInstance].currentLanguage; // -- 与国际化语言集成 rey
// Setting the payPalShippingAddressOption property is optional.
//
// See PayPalConfiguration.h for details.
self.payPalShippingAddressOption = PayPalShippingAddressOptionNone; //PayPalShippingAddressOptionPayPal;
self.rememberUser = YES; // -- 记住用户
}
@end
-
Paypal支付服务类的设计
建议在看这篇文章前,先查阅前面已发表的支付宝与微信支付的封装,在文章中定义了在支付时的订单信息OrderInfo 对象,而PayPal的sdk在支付时,对于支付项的配置,是通过设置PayPalPayment, PayPalItem这两个对象来完成的。所以就需要我们通过OrderInfo的数据来对应构造出 PayPalItem 对象来,使用Category在不改变原有封装的情况下,扩展运行时对象的行为。
@interface PayPalItem (OrderInfo)
- (instancetype)initWithOrderInfo:(OrderInfo *)orderInfo;
@end
@implementation PayPalItem (OrderInfo)
- (instancetype)initWithOrderInfo:(OrderInfo *)orderInfo {
if (orderInfo) {
return [PayPalItem itemWithName:orderInfo.productName
withQuantity:1
withPrice:[NSDecimalNumber decimalNumberWithString:amountStr]
withCurrency:@"USD"
withSku:@""];
}
return nil;
}
@end
在看这篇文章时,最好是能把Paypal官方的Demo看明白,才能知道我为什么会这样设计。 必竟是基于原有sdk的基础上做的二次封装。接着讲解设计思路:
通过查阅Demo, 我们知道Paypal sdk最终是通过调起PayPalPaymentViewController来完成支付的。 所以我们的封装,在将支付前的工作准备好后(支付配置,支付对象构造)最终的目的是将PayPalPaymentViewController对象返回,以便客户端调起,并完成支付操作。�思考一下在服务类对象初始化时需要传递哪些信息 ?
- 其一、�支付配置
因为我们对sdk的PayPalConfiguration对象用自定义的PayPalConfig对象封装,且PayPalConfig是个单例对象,所以在服务类中可以省去传递
PayPalConfiguration *payPalConfig = [[PayPalConfiguration alloc] initWithCommonConfig];
- 其二、订单信息
我们需要将自定义的OrderInfo对象传递给服务类,以便构造PaypalItem对象。
PayPalItem *item = [[PayPalItem alloc] initWithOrderInfo:self.orderInfo];
- 其三、 支付回调
在支付成功后,需要回调支付成功与否的状态,根据回调结果再做相应的处理,如:跳转支付成功或失败展示页。
通过上面的分解我们大体可以设计出支付服务类的输入与输出参数。需要输入的是订单信息,与支付回调的被委托者。输出的则是最终需要用到的PayPalPaymentViewController对象
支付服务类 PaypalService.h 头文件定义如下:
// Created by reyzhang on 2016/12/26.
// Copyright © 2016年 hhkx. All rights reserved.
// paypal支付封装
#import <Foundation/Foundation.h>
#import "PayPalMobile.h"
#import "PayPalConfig.h"
@class OrderInfo;
@interface PayPalService : NSObject
@property (nonatomic,strong) OrderInfo *orderInfo;
@property (nonatomic,weak) id <PayPalPaymentDelegate> delegate;
// -- 输入:订单信息及支付回调被委托者
- (instancetype)initWithOrder:(OrderInfo *)orderInfo andDelegate:(id<PayPalPaymentDelegate>)delegate;
// -- 输出:PayPalPaymentViewController对象
- (void)startPayWithPayViewController:(void(^)(PayPalPaymentViewController *))block;
@end
实现代码:
// Created by reyzhang on 2016/12/26.
// Copyright © 2016年 hhkx. All rights reserved.
// paypal支付封装
#import "PayPalService.h"
#import "OrderInfo.h"
@implementation PayPalService
- (instancetype)initWithOrder:(OrderInfo *)orderInfo andDelegate:(id<PayPalPaymentDelegate>)delegate{
if (self = [super init]) {
self.orderInfo = orderInfo;
self.delegate = delegate;
}
return self;
}
- (void)startPayWithPayViewController:(void(^)(PayPalPaymentViewController *))block {
NSAssert(self.orderInfo != nil, @"未传递订单数据");
PayPalItem *item = [[PayPalItem alloc] initWithOrderInfo:self.orderInfo];
PayPalConfiguration *payPalConfig = [[PayPalConfiguration alloc] initWithCommonConfig];
// -- 订单只允许下单一个产品,所以支付项就是订单项
PayPalPayment *payment = [[PayPalPayment alloc] init];
payment.amount = item.price;
payment.currencyCode = item.currency;
payment.shortDescription = self.orderInfo.productDescription;
payment.items = @[item]; // if not including multiple items, then leave payment.items as nil
payment.custom = self.orderInfo.tradeNO; // -- 将系统生成的订单号,当作自定义参数传递,便于服务器端获取。 reyzhang
payment.intent = PayPalPaymentIntentSale;
// PayPalShippingAddress *shippingAddress = [PayPalShippingAddress shippingAddressWithRecipientName:@"zhangsan" withLine1:@"288 ningxia road qingdao software park" withLine2:nil withCity:@"qingdao" withState:nil withPostalCode:@"266071" withCountryCode:@"C2"];
// payment.shippingAddress = shippingAddress;
// PayPalPaymentDetails *paymentDetails = [PayPalPaymentDetails paymentDetailsWithSubtotal:subtotal
// withShipping:shipping
// withTax:tax];
payment.paymentDetails = nil; // if not including payment details, then leave payment.paymentDetails as nil
if (!payment.processable) {
// This particular payment will always be processable. If, for
// example, the amount was negative or the shortDescription was
// empty, this payment wouldn't be processable, and you'd want
// to handle that here.
}
PayPalPaymentViewController *paymentViewController = [[PayPalPaymentViewController alloc]
initWithPayment:payment
configuration:payPalConfig
delegate:self.delegate];
if (block) {
block(paymentViewController); // -- 将支付vc回调给使用者来跳转 reyzhang
}
}
@end
调用
在AppDelegate的应用启动方法中进行PayPal支付的全局配置
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self setupPayPal];
}
///paypal支付配置 reyzhang
- (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;
}
在支付页面的ViewController中支付操作被触发时,实例PaypalService对象,前提是已经准备好了支付的订单信息
PayPalService *paypalService = [[PayPalService alloc] initWithOrder:self.order
andDelegate:self];
[paypalService startPayWithPayViewController:^(PayPalPaymentViewController *payvc) {
/// -- 调起 PayPalPaymentViewController进行支付 reyzhang
[self presentViewController:payvc animated:YES completion:nil];
}];
#pragma mark paypal支付回调
- (void)payPalPaymentViewController:(PayPalPaymentViewController *)paymentViewController didCompletePayment:(PayPalPayment *)completedPayment {
NSLog(@"PayPal Payment Success! result:%@",completedPayment.confirmation);
NSString *resultText = [completedPayment description];
[self verifyCompletedPayment:completedPayment];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)payPalPaymentDidCancel:(PayPalPaymentViewController *)paymentViewController {
// NSLog(@"PayPal Payment Canceled");
[self dismissViewControllerAnimated:YES completion:nil];
NSError *error = [NSError errorWithDomain:@"" code:201 userInfo:@{@"memo":@"用户取消"}];
}
- (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,在这里你可以更新订单的支付状态
}
通过上面的封装,在使用Paypal支付时,是不是简单了很多。接下来我会把前面已经封装的支付宝、微信支付与Paypal、银联支付做一个统一封装组件。尽请期待~~
写在最后:
如果我的文章对你有所帮助,请帮忙点个赞👍,谢谢!
网友评论
看一下实例服务类的适配方法, 在哪个控制器中使用,就在哪个控制器中实现 PayPalPaymentDelegate 中的协议方法