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

作者: reyzhang | 来源:发表于2017-06-12 09:45 被阅读601次

前言

这是第三篇关于支付封装的文章了,前两篇是关于支付宝和微信支付的封装。如果您也对这两篇文章感兴趣可以移步到

中查阅。同时,如果你有更好的建议,可以在评论里给我留言,一起讨论。

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、银联支付做一个统一封装组件。尽请期待~~

写在最后:

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

相关文章

  • Paypal 贝宝支付iOS集成与二次封装

    前言 这是第三篇关于支付封装的文章了,前两篇是关于支付宝和微信支付的封装。如果您也对这两篇文章感兴趣可以移步到 支...

  • iOS Alipay -支付宝

    iOS 集成Alipay -支付宝支付 集成 AliPay - 支付宝

  • iOS支付宝功能集成

    iOS快速集成支付宝详解/支付宝集成获取私钥与公钥 在集成支付宝功能前,我们必须了解下什么支付宝流程,下图是支付宝...

  • 支付宝支付流程(I)

    参考资料:支付宝支付集成支付宝支付集成实现蚂蚁金服iOS支付集成

  • ios支付宝签名

    支付宝: iOS 支付功能概述 - 简书 1.iOS集成支付宝 - 简书 2.iOS 集成支付宝 - 简书 3.i...

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

    本以为像阿里这样的大公司,在细节处理上有独到之处。然而没想到在使用了支付宝的sdk后,多少会有些改观。接下来介绍一...

  • 文章汇总

    ios iOS 网络篇 网络基础总结 支付宝集成 iOS支付宝支付集成 - demo例子集 - 博客园 理解 iO...

  • Paypal支付集成 iOS

    集成流程: 1.去paypal开发者网站注册开发账号,在Dashboard里新增sandbox测试账号,并注册ap...

  • iOS集成支付宝-Swift版

    iOS集成支付宝-Swift版 如果要了解整个支付流程可以阅读文章《手机App集成微信支付&支付宝-iOS&And...

  • Android:支付宝支付封装

    集成支付宝APP支付 直接按照官方文档逐步集成官方文档:支付宝APP支付官方文档 Android客户端封装支付宝支...

网友评论

  • 正在优化中:SDK不是最新的吧,我今天的SDK完全没有这些方法,全BT前缀
    reyzhang:@正在优化中 我说呢,你这是使用的braintree的sdk。
    正在优化中:@reyzhang https://developers.braintreepayments.com/guides/client-sdk/setup/ios/v4 github上的paypalSDK也是推荐使用这个, 已经完美集成了, paypalSDK公司服务器说有点问题, java没有对应的配置, 貌似是这样, 主要现在每次交易都是需要从服务器获取token然后生成nonce, 而paypal并没有.
    reyzhang:能把部分类截图发一下吗 ? 在github上的demo中没看到有更新
  • 那一片阳光:成功失败回调在哪
    reyzhang:回调的处理已在文章中完善
    reyzhang:- (instancetype)initWithOrder:(OrderInfo *)orderInfo andDelegate:(id<PayPalPaymentDelegate>)delegate;

    看一下实例服务类的适配方法, 在哪个控制器中使用,就在哪个控制器中实现 PayPalPaymentDelegate 中的协议方法

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

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