PayPal 接入指南

老牛浏览 606评论 0发表于

1. 介绍

PayPal 是备受用户追捧的国际贸易支付工具,即时支付,即时到账,在本地化方面也做的不错,支持中文,就是手续费费率有点高。本文主要介绍,如何使用 PHP 在网站中接入 PayPal 支付,使网站拥有支付能力。

2. 准备工作

  1. 首先要去 PayPal 官网 申请一个 PayPal 账号。

  2. 申请完成后登录,进入到 PayPal 开发者平台 的 Sandbox 选项下即可看到申请账号自动分配的两个测试账号,账号类型分别是 BUSINESSPERSONAL。PayPal 给开发者提供了沙盒测试环境,所有流程和 API 接口(测试环境根域名:api.sandbox.paypal.com;线上环境根域名:api.paypal.com)调用都和真实环境一样,唯一不同的区别是,仅供测试使用,货币都是无效的,所以你可以充值任意金额。事实上这两个账号的状态一直是 processing 状态,无法使用,所以我们需要点击 sandbox 里的创建账户,另外建立两个账号:一个是商家账号(BUSINESS,用于收款和付款),一个是买家账号(PERSONAL,用于付款和转账收款)。

  3. 点击对应账号下的 Profile 就能看到账号相关的信息,比如账号、密码、余额等等。

  4. My Apps & Credentials 选项下创建一个 APP,然后选择一个测试账号绑定到该 APP 上。Client IDSecret 用于 API 调用的身份认证,我们在后面会用到。

3. 引入 SDK

PayPal 官方提供了基于 RESTful 风格实现的 API 接口,供 PHP 开发者使用。

项目地址:https://github.com/paypal/PayPal-PHP-SDK

我们用 composer 即可方便安装:

bash
composer require paypal/rest-api-sdk-php:*

接口请求和返回的数据都是 JSON 格式,SDK 为我们封装了常用的接口,根据文档说明调用对应方法即可。

由于支付是异步进行的,所以支付结果无法在支付流程结束后立即返回,需要在支付操作完成后再去 PayPal 查询相应订单信息,以获取支付最终的支付结果来判断是否继续支付成功后的业务流程。即时支付一般在用户支付后几秒内订单就会更新状态;而转账往往需要十几秒到数十秒不等,所以如何有效的查询订单支付状态,这个比较重要。常见的是,在即将支付时,原页面显示支付成功或支付遇到问题的按钮,然后新开一个页面跳转到第三方支付平台,用户支付成功后点击支付成功按钮来查询支付结果。

4. 即时支付(收款)

4.1 公共起始文件

由于每次请求 API 时都需要初始化必要的参数,所以我们把这部分代码抽离出来放到一个单独文件中,在后续使用时引入该文件即可。

php
//file: start.php
require 'vendor/autoload.php'; // 引入 composer 自动加载

use PayPal\Rest\ApiContext;
use PayPal\Auth\OAuthTokenCredential;

$apiContext = new ApiContext(new OAuthTokenCredential(YOUR_CLIENT_ID, YOUR_CLIENT_SECRET));

4.2 创建支付

php
//file: create.php
require 'start.php';

use PayPal\Api\Amount;
use PayPal\Api\Details;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Payer;
use PayPal\Api\Payment;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Transaction;

$payer = new Payer(); // 实例化支付者
$payer->setPaymentMethod('paypal'); // 使用 PayPal 进行支付

$item = new Item(); // 实例化一个物品
$item->setName('product_name') // 名称
    ->setCurrency('USD') // 币种
    ->setQuantity(1) // 数量
    ->setSku('product_123456') // 编号
    ->setPrice(12.00); // 价格

$itemList = new ItemList(); // 实例化一个物品列表,即支付清单
$itemList->setItems([$item]); // 将物品加入到列表中

$details = new Details(); // 支付详情
$details->setShipping(2) // 运输费
    ->setTax(2.5) // 税费
    ->setSubtotal(12); // 合计,即上面的 物品单价 * 物品数量

$amount = new Amount(); // 实例化总计
$amount->setCurrency('USD')
    ->setTotal(16.5) // 总金额 = 运输费 + 税费 + 合计
    ->setDetails($details);

$transaction = new Transaction(); // 实例化交易
$transaction->setAmount($amount)
    ->setItemList($itemList)
    ->setDescription('payment description') // 交易描述
    ->setInvoiceNumber(uniqid());

$baseUrl = 'https://example.com'; // 支付成功或者取消的回调地址
$redirectUrls = new RedirectUrls();
$redirectUrls->setReturnUrl($baseUrl . '/execute.php?success=true')
    ->setCancelUrl($baseUrl . '/execute.php?success=false');

$payment = new Payment(); // 实例化支付
$payment->setIntent('sale')
    ->setPayer($payer)
    ->setRedirectUrls($redirectUrls)
    ->setTransactions([$transaction]);

$request = clone $payment;

try {
    $payment->create($apiContext); // 创建支付,相当于微信、支付宝里的统一下单
} catch (Exception $e) {
    echo $e->getMessage();
}

提交的信息会在 PayPal 结算界面看到:

7aef509b-ff57-47ba-b5eb-6584818e635e

在创建支付之前,$payment 是什么呢,或者说我们做了什么?其实就是拼凑了一个 JSON 格式的请求体,调用 toJSON 方法可以得到下面的信息:

json
{
    "intent": "sale",
    "payer": {
        "payment_method": "paypal"
    },
    "redirect_urls": {
        "return_url": "https://example.com/execute.php?success=true",
        "cancel_url": "https://example.com/execute.php?success=false"
    },
    "transactions": [
        {
            "amount": {
                "currency": "USD",
                "total": "16.50",
                "details": {
                    "shipping": "2",
                    "tax": "2.50",
                    "subtotal": "12"
                }
            },
            "item_list": {
                "items": [
                    {
                        "name": "product_name",
                        "currency": "USD",
                        "quantity": "1",
                        "sku": "PRODUCT_bb42cd6e00065b6a611c9466fc84a2e7",
                        "price": "12"
                    }
                ]
            },
            "description": "payment description",
            "invoice_number": "5a7c69645b2e1"
        }
    ]
}

后续执行 create 方法就是在请求 API 接口,成功后返回下单后的数据。再次调用 toJSON 方法,新增的数据如下:

json
{
    "id": "PAY-AAAAAAAA",
    "state": "created",
    "create_time": "2018-02-07T03:04:09Z",
    "links": [
        {
            "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-AAAAA",
            "rel": "self",
            "method": "GET"
        },
        {
            "href": "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=XXXXX",
            "rel": "approval_url",
            "method": "REDIRECT"
        },
        {
            "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-AAAAA/execute",
            "rel": "execute",
            "method": "POST"
        }
}

approval_url 即用户登录确认支付订单的地址,若确认支付,跳转到 return_url;若不同意或取消,则跳转到 cancel_url。这里需要注意的是页面跳转时,可能会由于用户手动关闭或其他特殊原因而失败,此时就需要根据支付订单 id 去获取用户确认状态。

4.3 进行支付

用户确认支付订单后,跳转到支付页面,paymentIdPayerID 会以 GET 参数的形式传过去,根据 paymentID 获取支付订单,调用 execute 方法进行支付。

php
//file: execute.php
require 'start.php';

use PayPal\Api\Payment;
use PayPal\Api\PaymentExecution;

if (isset($_GET['success']) && $_GET['success'] == 'true') {
    $paymentId = $_GET['paymentId']; // 接收传过来的支付 id
    $payment = Payment::get($paymentId, $apiContext); // 根据支付 id 获取支付对象

    $execution = new PaymentExecution();
    $execution->setPayerId($_GET['PayerID']);

    try {
        $result = $payment->execute($execution, $apiContext); // 进行支付
        try {
            $payment = Payment::get($paymentId, $apiContext); // 获取支付后的支付状态
        } catch (Exception $e) {
            echo $e->getMessage();
        }
    } catch (Exception $e) {
        echo $e->getMessage();
    }
}

如果成功进行了支付,支付状态 status 会由原来的 created 修改为 approved 。即时支付的手续费费率参考 Paypal 官网。

5. 转账(付款)

转账演示里面,官方文档里面提供了同步通知的创建单个转账接口[Create Single Payout (Synchronously)],但由于同步模式下存在问题,将不再被支持,所以转账需要使用异步或者批量转账接口。同时生产环境下转账接口默认是不开放的,需要联系 Paypal 客服手动开启。

5.1 公共起始文件

同上述 4.1 。

5.2 进行转账

php
//file: payouts.php
require 'start.php';

use PayPal\PayPal\Api\PayoutSenderBatchHeader;
use PayPal\Api\PayoutItem;
use PayPal\Api\Currency;
use PayPal\Api\Payout;

$senderBatchHeader = new PayoutSenderBatchHeader();
$senderBatchHeader->setSenderBatchId(uniqid()) // senderBatchId 标识订单的
    ->setEmailSubject("You have a payment"); // 转账主题

$senderItem1 = new PayoutItem();
$senderItem1->setRecipientType('Email') // Email 方式收款
    ->setNote('Thanks you') // 转账说明
    ->setReceiver('email@example.com') // 对应的收款账号
    ->setSenderItemId('item1_' . uniqid()) // 物品编号
    ->setAmount(new Currency('{"value:"0.99","currency":"USD"}')); // 转账金额及币种

$payouts = new Payouts(); // 实例化转账对象
$payouts->setSenderBatchHeader($senderBatchHeader)
    ->addItem($senderItem1);

$request = clone $payouts;

try {
    $payouts->create(null, $apiContext); // 进行转账
} catch (Exception $e) {
    echo $e->getMessage();
}

转账和支付不一样,不需要用户进行支付确认,直接进行转账。

在创建转账之前,$payouts 是什么呢?同样只是拼凑了一个 JSON 格式的请求体,我们调用 toJSON 方法可以得到下面的信息:

php
{
    "sender_batch_header": {
        "sender_batch_id": "5abb54011de20",
        "email_subject": "You have a payment"
    },
    "items": [
        {
            "recipient_type": "Email",
            "note": "Thanks you",
            "receiver": "email@example.com",
            "sender_item_id": "item_15abb54011de20",
            "amount": {
                "value": "0.99",
                "currency": "USD"
            }
        }
    ]
}

后续执行 create 方法就是在请求 API 接口,成功后返回下单后的数据。再次调用 toJSON 方法,新增的数据如下:

json
{
    "batch_header": {
        "payout_batch_id": "9CRKTWPDJT9U8",
        "batch_status": "PENDING",
        "time_created": "2018-03-28T08:28:46Z",
        "time_completed": "2018-03-28T08:29:02Z",
        "sender_batch_header": {
            "email_subject": "You have a payment",
            "sender_batch_id": "5abb54011de20"
        },
        "amount": {
            "currency": "USD",
            "value": "0.99"
        },
        "fees": {
            "currency": "USD",
            "value": "0.25"
        }
    },
    "items": [
        {
            "payout_item_id": "4ESXTXACP455W",
            "transaction_id": "20W42029061726645",
            "transaction_status": "PENDING",
            "payout_item_fee": {
                "currency": "USD",
                "value": "0.25"
            },
            "payout_batch_id": "9CRKTWPDJT9U8",
            "payout_item": {
                "amount": {
                    "currency": "USD",
                    "value": "0.99"
                },
                "note": "Thanks you.",
                "receiver": "1829964321@qq.com",
                "recipient_type": "EMAIL",
                "sender_item_id": "item_15abb5237c3a78"
            },
            "time_processed": "2018-03-28T08:28:56Z",
            "links": [
                {
                    "href": "https://api.sandbox.paypal.com/v1/payments/payouts-item/4ESXTXACP455W",
                    "rel": "item",
                    "method": "GET"
                }
            ]
        }
    ],
    "links": [
        {
            "href": "https://api.sandbox.paypal.com/v1/payments/payouts/9CRKTWPDJT9U8",
            "rel": "self",
            "method": "GET"
        }
    ]
}

这里要注意,查询订单信息使用的是 payout_batch_idsender_batch_id 只是转账这边用于标识订单的,30 天内同一 sender_batch_id 的订单只会被处理一次。转账成功后,订单状态由 PENDING 变为 SUCCESS。手续费好像是固定的每笔 0.25 美元。

6. 其他说明

我们常用的就是收付款了,其他还有通知、发票等可以参考官方 API,用法都差不多。

点赞
收藏
暂无评论,快来发表评论吧~
私信
老牛@ilaoniu
老牛,俗称哞哞。单纯的九零后理工小青年。喜欢折腾,爱玩,爱音乐,爱游戏,爱电影,爱旅游...
最后活跃于