如何通过PayPal实现循环扣款(订阅)功能?

背景

在业务开发中,需要集成PayPal以实现循环扣款功能。然而,经过在百度和Google上搜索,除了PayPal官网之外,几乎没有找到相关的开发教程。经过两天的摸索,终于成功实现了这一功能。以下是我对PayPal支付接口的使用总结。

PayPal的支付接口

PayPal目前提供多套接口:

  1. 通过Braintree实现Express Checkout;
  2. 创建App,通过REST API的接口方式(目前的主流接口方式);
  3. 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账号,并正确配置以下信息:

  1. Client ID
  2. Client Secret
  3. Webhook API(必须以https开头并使用443端口,本地调试建议结合ngrok反向代理生成地址)
  4. 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。

实现循环扣款的步骤

实现循环扣款功能主要分为以下四个步骤:

  1. 创建升级计划并激活;
  2. 创建订阅(创建Agreement),然后跳转到PayPal网站等待用户同意;
  3. 用户同意后,执行订阅;
  4. 获取扣款账单。

1. 创建升级计划

升级计划对应Plan类。需要注意以下几点:

  • 升级计划创建后处于CREATED状态,必须修改为ACTIVE状态才能正常使用。
  • Plan包含PaymentDefinitionMerchantPreferences两个对象,这两个对象都不能为空。
  • 如果想创建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,此时可以通过AgreementgetApprovalLink方法得到的URL中的token作为识别方式。

以下是创建Agreement的代码示例:

php
$param = [
‘id’ => ‘P-26T36113JT475352643KGIHY’, //上一步创建Plan时生成的ID
‘name’ => ‘Standard’,
‘desc’ => ‘Standard Plan for one month’
];

3. 用户同意后,执行订阅

用户同意后,订阅还未完成,必须执行Agreementexecute方法才能真正完成订阅。需要注意的是:

  • 完成订阅后,并不等于立刻扣款,可能会延迟几分钟。
  • 如果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_dateend_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();
}

需要注意的问题

功能实现后,我还发现了以下几点需要注意:

  1. 国内使用Sandbox测试时连接特别慢,经常提示超时或出错,因此必须考虑用户中途关闭页面的情况。
  2. 必须实现webhook,否则当用户在PayPal取消订阅时,你的网站将得不到通知。
  3. 一旦生成订阅(Agreement),除非主动取消,否则将一直生效。因此,如果你的网站设计了多个升级计划(如BasicStandardAdvanced),当用户切换计划时,必须先取消前一个升级计划。
  4. 用户同意订阅、取消旧订阅、完成新订阅签约、修改用户信息等操作应该是原子操作,且耗时较长,因此建议将这些操作放到队列中执行。

👉 WildCard | 一分钟注册,轻松订阅海外线上服务

上一篇 12小时前
下一篇 8小时前

相关推荐