背景
在业务开发中,需要集成PayPal以实现循环扣款功能。然而,经过在百度和Google上搜索,除了PayPal官网之外,几乎没有找到相关的开发教程。经过两天的摸索,终于成功实现了这一功能。以下是我对PayPal支付接口的使用总结。
PayPal的支付接口
PayPal目前提供多套接口:
- 通过Braintree实现Express Checkout;
- 创建App,通过REST API的接口方式(目前的主流接口方式);
- NVP/SOAP API应用接口(旧接口)。
Braintree接口
Braintree是PayPal收购的一家公司,除了支持PayPal支付外,还提供了升级计划、信用卡管理、客户信息等一系列功能。这些功能在PayPal的REST接口中也集成了大部分,但PayPal的Dashboard无法直接管理这些信息,而Braintree可以。此外,我使用的后端框架是Laravel,其Cashier解决方案默认支持Braintree,因此这套接口是我的首选。然而,Braintree在国内并不支持,最终无法使用。
REST API
REST API是顺应时代发展的产物。如果你之前使用过OAuth 2.0和REST API,那么理解这套接口应该不会有什么困难。
旧接口
除非REST API无法满足某些特殊需求(如政策限制),否则不推荐使用旧接口。全球都在向OAuth 2.0认证方式和REST API迁移,因此没有必要逆势而行。
REST API简介
PayPal官方的API参考文档PayPal API参考对其API和使用方式有详细的介绍。但如果直接调用这些API,操作会非常繁琐。为了快速完成业务需求,建议直接使用官方提供的PayPal-PHP-SDK,并通过其Wiki作为起点。
在完成首个示例之前,请确保你已经拥有Sandbox账号,并正确配置以下信息:
Client ID
Client Secret
Webhook API
(必须以https
开头并使用443
端口,本地调试建议结合ngrok
反向代理生成地址)Returnurl
(注意事项同上)
完成首个示例后,理解接口分类有助于完成你的业务需求。以下是对接口的分类介绍:
- Payments:一次性支付接口,不支持循环扣款。主要支付方式包括PayPal支付、信用卡支付、通过已保存的信用卡支付(需要使用Vault接口)。
- Payouts:未使用,忽略。
- Authorization and Capture:支持直接通过PayPal账号登录你的网站并获取相关信息。
- Sale:与商城相关,未使用,忽略。
- Order:与商城相关,未使用,忽略。
- Billing Plan & Agreements:升级计划和签约功能,也是实现订阅功能的关键,这是本文的重点。
- Vault:存储信用卡信息。
- Payment Experience:未使用,忽略。
- Notifications:处理Webhook信息,重要但不属本文关注内容。
- Invoice:票据处理。
- Identity:认证处理,实现OAuth 2.0登录,获取对应token以请求其他API。
实现循环扣款的步骤
实现循环扣款功能主要分为以下四个步骤:
- 创建升级计划并激活;
- 创建订阅(创建Agreement),然后跳转到PayPal网站等待用户同意;
- 用户同意后,执行订阅;
- 获取扣款账单。
1. 创建升级计划
升级计划对应Plan
类。需要注意以下几点:
- 升级计划创建后处于
CREATED
状态,必须修改为ACTIVE
状态才能正常使用。 Plan
包含PaymentDefinition
和MerchantPreferences
两个对象,这两个对象都不能为空。- 如果想创建
TRIAL
类型的计划,必须配套一个REGULAR
的支付定义,否则会报错。 setSetupFee
方法设置了完成订阅后首次扣款的费用,而Agreement
对象的循环扣款方法设置的是第2次开始时的费用。
以下是一个创建Standard
计划的代码示例:
php
$param = [
“name” => “standard_monthly”,
“display_name” => “Standard Plan”,
“desc” => “Standard Plan for one month”,
“type” => “REGULAR”,
“frequency” => “MONTH”,
“frequency_interval” => 1,
“cycles” => 0,
“amount” => 20,
“currency” => “USD”
];
2. 创建订阅(创建Agreement)
Plan
创建后,如何让用户订阅呢?其实就是创建Agreement
。以下是需要注意的几点:
Plan
对象的setSetupFee
方法设置了完成订阅后首次扣款的费用,而Agreement
对象的循环扣款方法设置的是第2次开始时的费用。setStartDate
方法设置的是第2次扣款的时间,因此如果你按月循环,应该是当前时间加一个月,同时该方法要求时间格式为ISO8601
。- 在创建
Agreement
时,尚未生成唯一ID,此时可以通过Agreement
的getApprovalLink
方法得到的URL中的token
作为识别方式。
以下是创建Agreement
的代码示例:
php
$param = [
‘id’ => ‘P-26T36113JT475352643KGIHY’, //上一步创建Plan时生成的ID
‘name’ => ‘Standard’,
‘desc’ => ‘Standard Plan for one month’
];
3. 用户同意后,执行订阅
用户同意后,订阅还未完成,必须执行Agreement
的execute
方法才能真正完成订阅。需要注意的是:
- 完成订阅后,并不等于立刻扣款,可能会延迟几分钟。
- 如果
setSetupFee
费用设置为0,则必须等到循环扣款的时间到了才会产生订单。
以下是执行订阅的代码片段:
php
public function onPay($request)
{
$apiContext = $this->getApiContext();
if ($request->has(‘success’) && $request->success == ‘true’) {
$token = $request->token;
$agreement = new \PayPal\Api\Agreement();
try {
$agreement->execute($token, $apiContext);
} catch(\Exception $e) {
return null;
}
return $agreement;
}
return null;
}
4. 获取交易记录
订阅后,可能不会立刻产生交易扣费的记录。如果为空,可以过几分钟再次尝试。需要注意的是:
start_date
与end_date
不能为空。- 实际测试时,
AgreementTransactions
的API可能返回空的JSON对象,因此需要手动取出对应参数。
以下是获取交易记录的代码示例:
php
public function transactions($id)
{
$apiContext = $this->getApiContext();
$params = [‘start_date’ => date(‘Y-m-d’, strtotime(‘-15 years’)), ‘end_date’ => date(‘Y-m-d’, strtotime(‘+5 days’))];
try {
$result = Agreement::searchTransactions($id, $params, $apiContext);
} catch(\Exception $e) {
Log::error(“get transactions failed” . $e->getMessage());
return null;
}
return $result->getAgreementTransactionList();
}
需要注意的问题
功能实现后,我还发现了以下几点需要注意:
- 国内使用Sandbox测试时连接特别慢,经常提示超时或出错,因此必须考虑用户中途关闭页面的情况。
- 必须实现
webhook
,否则当用户在PayPal取消订阅时,你的网站将得不到通知。 - 一旦生成订阅(
Agreement
),除非主动取消,否则将一直生效。因此,如果你的网站设计了多个升级计划(如Basic
、Standard
、Advanced
),当用户切换计划时,必须先取消前一个升级计划。 - 用户同意订阅、取消旧订阅、完成新订阅签约、修改用户信息等操作应该是原子操作,且耗时较长,因此建议将这些操作放到队列中执行。