Android接入PayPal支付

前言

因业务需要,需要集成PayPal支付,故一边研究并顺便分享一下集成步骤。附上相关文档:
PayPal集成官方文档
老版本PayPal Android SDK(已停止维护,不推荐使用)
新版本PayPal Android SDK(推荐使用)

PayPal集成方式有以下几种:

1. Client-Side Integration(特点:订单在所接入的App端创建、支付以及捕获)

该方式是使用PayPal自带的支付按钮PayPalButton,调用payPalButton.setup()方法创建订单、支付并捕获订单

2. Server-Side Integration(特点:订单的实际创建和捕获是在服务端,只有支付在App端)

  • 该方式创建订单流程:
    App需从服务端获取到orderId,然后在payPalButton.setup()CreateOrder()回调方法的create(@NotNull CreateOrderActions createOrderActions)方法中调用createOrderActions.set(orderId)
  • 捕获订单流程:
    payPalButton.setup()OnApprove()回调的onApprove(@NotNull Approval approval)方法中先retrieve order details first(yourAppsCheckoutRepository.getEC(approval.getData().getOrderId())),再Send the order ID to your own endpoint to capture or authorize the order(yourAppsCheckoutRepository.captureOrder(approval.getData().getOrderId()))

3. Programmatically start the SDK(特点:同Client-Side Integration)

该方式和Client-Side集成方式大致相同,只是不在使用PayPal自带的PayPalButton,在拉起支付面板是通过调用PayPalCheckout.start()方法

说明:本文是使用新版PayPal SDK集成分享,集成方式为第3种。不过个人建议使用第2种方式结合服务端集成更安全可靠。

集成后的效果

注:::若第一次拉起支付时,个人付款账号未登录,会先跳转到一个登录页面,登录成功后会执行一下操作

PayPal支付.gif

开始集成前准备

1.登录/注册PayPal账号:登录注册
2.注册登录成功后,创建应用:demo,点击demo查看Client ID和Sandbox account(沙箱测试邮箱账号)

创建应用

3.创建沙箱账户
沙箱账户创建.png

4.应用中 minSdkVersion必须大于等于21

开始集成

一. 启用SDK

一、填写Return URL,两种方式:
1、使用 Android App Link
2、直接使用包名+“://paypalpay”。例如你的包名是com.paypal.app,此处就填写com.paypal.app://paypalpay
如下图所示:

输入返回URL

二、配置App feature options(注意:这里一定要配置,不然无法拉起支付页面,起初我这里对接的时候,未配置,结果在代码集成完后怎么都无法拉起支付,之后尝试配置了这些值后,拉起支付页面成功)
App feature options1.png

如上图所示,Log in with PayPal这一项一定要选中,并点击Advanced options展开其中的选项,我这里配置如下图所示,Privacy policy URLUser agreement URL必填项,沙箱环境随便填一个满足URL格式的地址就行,如:https://www.example.com;其余选项暂未知作用,故未勾选。
App feature options2.png

二. 集成SDK到应用中

1. 清单文件中配置网络权限

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
      <uses-permission android:name="android.permission.INTERNET" />
      ...
</manifest>

2. gradle配置

  • 在project root的build.gradle中
allprojects {
    repositories {
        mavenCentral()
        // This private repository is required to resolve the Cardinal SDK transitive dependency.
        maven {
            url  "https://cardinalcommerceprod.jfrog.io/artifactory/android"
            credentials {
                // Be sure to add these non-sensitive credentials in order to retrieve dependencies from
                // the private repository.
                username paypal_sgerritz
                password AKCp8jQ8tAahqpT5JjZ4FRP2mW7GMoFZ674kGqHmupTesKeAY2G8NcmPKLuTxTGkKjDLRzDUQ
            }
        }
    }
}

或:直接在App module中配置

android {
    ...
}
dependencies {
    ...
}
repositories {
  mavenCentral()
  maven {
    url  "https://cardinalcommerceprod.jfrog.io/artifactory/android"
    credentials {
      // Be sure to add these non-sensitive credentials in order to retrieve dependencies from
      // the private repository.
      username 'paypal_sgerritz'
      password 'AKCp8jQ8tAahqpT5JjZ4FRP2mW7GMoFZ674kGqHmupTesKeAY2G8NcmPKLuTxTGkKjDLRzDUQ'
    }
  }
}
  • 在App module的build.gradle中
android {
    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
  • 添加SDK依赖到App module的build.gradle中,PayPal SDK因是kotlin版本,故需添加core-ktx和kotlin-stdlib-jdk7:version(version需要和Android studio中的kotlin版本一致)支持
dependencies {
    implementation('com.paypal.checkout:android-sdk:0.5.1')
    implementation "androidx.core:core-ktx:1.6.0"
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10"
}

3. 初始化PayPal SDK

在Application中做如下初始化操作,其中YOUR_CLIENT_ID需要替换为自已应用的clientId

public class YourApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        CheckoutConfig config = new CheckoutConfig(
            this,
            YOUR_CLIENT_ID,//替换为自已应用的clientId
            Environment.SANDBOX,//正式上线后,改为Environment.LIVE
            String.format("%s://paypalpay", BuildConfig.APPLICATION_ID),//创建应用时填的RETURN_URL
            CurrencyCode.USD,//货币种类:CNY-人民币;HKD-港币;TWD-新台币;USD-美元...
            UserAction.PAY_NOW,
            PaymentButtonIntent.CAPTURE,
            new SettingsConfig(
                true,
                false
            )
        );
        PayPalCheckout.setConfig(config);
    }
}

4.创建并捕获订单

直接上代码

  @RequiresApi(api = Build.VERSION_CODES.M)
  public void invokePayPalPay() {
    PayPalCheckout.start(new CreateOrder() {
      @Override
      public void create(CreateOrderActions createOrderActions) {
        //所订购商品详情列表
        List<Items> itemsList = new ArrayList<>();
        Items items = new Items.Builder().name("演唱会门票")//商品名称
            .category(ItemCategory.DIGITAL_GOODS)//商品类型
            .description("这是一张演唱会门票的订单")//商品描述
            .quantity("1")//商品数量
            .sku("sku是什么?")//TODO 待更新
            .unitAmount(
                new UnitAmount.Builder().value("9.00").currencyCode(CurrencyCode.USD).build())//商品单价
            .tax(new UnitAmount.Builder().value("1.00").currencyCode(CurrencyCode.USD).build())//税
            .build();
        itemsList.add(items);

        ArrayList<PurchaseUnit> purchaseUnits = new ArrayList<>();
        PurchaseUnit purchaseUnit =
            new PurchaseUnit.Builder().referenceId(UUID.randomUUID().toString())
                .amount(new Amount.Builder().currencyCode(CurrencyCode.USD)
                    .value("12.00")//订单总价=itemTotalAmount + taxTotalAmount + handleTotalAmount + shippingTotalAmount - shippingDiscount - discount
                    .breakdown(new BreakDown.Builder().itemTotal(
                        new UnitAmount.Builder().currencyCode(CurrencyCode.USD)
                            .value("9.00")
                            .build())//商品总价=商品单价(unitAmount) * 商品数量(quantity)
                        .taxTotal(new UnitAmount.Builder().currencyCode(CurrencyCode.USD)
                            .value("1.00")
                            .build())//税总费用
                        .handling(new UnitAmount.Builder().currencyCode(CurrencyCode.USD)
                            .value("1.00")
                            .build())//手续费
                        .shipping(new UnitAmount.Builder().currencyCode(CurrencyCode.USD)
                            .value("1.00")
                            .build())//运费
                        .shippingDiscount(new UnitAmount.Builder().currencyCode(CurrencyCode.USD)
                            .value("0")
                            .build())//运费折扣价格
                        .discount(new UnitAmount.Builder().currencyCode(CurrencyCode.USD)
                            .value("0")
                            .build())//商品折扣价格
                        .build())
                    .build())
                .description("这里是订单的描述")
                .invoiceId("invoiceId是什么?")
                .customId("customId是什么?")
                .softDescriptor("softDescriptor是什么?")
                .items(itemsList)
                .payee(new Payee("sb-vbvol6893948@business.example.com", "25FYB5AYNYZHA"))//收款人信息:收款人邮箱和账户id
                .build();
        purchaseUnits.add(purchaseUnit);
        Order order = new Order(OrderIntent.CAPTURE,
            new AppContext.Builder().userAction(UserAction.PAY_NOW).build(), purchaseUnits,
            ProcessingInstruction.NO_INSTRUCTION);
        //创建订单
        createOrderActions.create(order, (CreateOrderActions.OnOrderCreated) null);
      }
    }, new OnApprove() {
      @Override
      public void onApprove(Approval approval) {
        //捕获订单
        approval.getOrderActions().capture(new OnCaptureComplete() {
          @Override
          public void onCaptureComplete(CaptureOrderResult captureOrderResult) {
            Log.e(TAG, String.format("CaptureOrderResult: %s", captureOrderResult));
            if (captureOrderResult instanceof CaptureOrderResult.Success) {
                //TODO 订单捕获成功
            } else if (captureOrderResult instanceof CaptureOrderResult.Error) {
                //TODO 订单处理失败
            }
          }
        });
      }
    }, new OnShippingChange() {
      @Override
      public void onShippingChanged(ShippingChangeData shippingChangeData,
          ShippingChangeActions shippingChangeActions) {
        Log.e(TAG, "onShippingChanged");
      }
    }, new OnCancel() {
      @Override
      public void onCancel() {
        //TODO 取消支付
        Log.e(TAG, "onCancel:::Buyer cancelled the PayPal experience.");
      }
    }, new OnError() {
      @Override
      public void onError(ErrorInfo errorInfo) {
        //TODO 支付出错
        Log.e(TAG, "OnError:::" + String.format("Error: %s", errorInfo));
      }
    });
  }

至此,PayPal支付SDK已经集成完成。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容