由于业务需求需要集成 PayPal,实现循环扣款功能,经过多次在 百度 和 Google 搜索,除了官方网站外,并未找到相关的开发教程。于是我花了两天时间深入了解并成功集成。本文将总结如何使用 PayPal 的支付接口。
Braintree 接口:通过 Braintree 实现 Express Checkout,后面会详细讲解。
REST API 接口:当前主流的接口方式。
NVP/SOAP API 接口:较旧的接口方式,已不推荐使用。
Braintree 是 PayPal 收购的一家公司,除了支持 PayPal 支付外,它还提供了升级计划、信用卡和客户信息等全套管理功能。虽然 PayPal 的 REST API 接口也支持大部分功能,但 Braintree 可以直接通过后台管理这些信息,而 PayPal 的 Dashboard 并不能。因此,个人推荐使用 Braintree,特别是我使用的后端框架 Laravel,其 Cashier 解决方案默认支持 Braintree,这使得集成更加方便。
然而,问题也随之而来:Braintree 在国内并不支持,这让我只能转向其他接口。
REST API 是顺应时代发展的接口,如果你熟悉 OAuth 2.0 和 REST API,使用起来应该不会有什么困惑。
除非 REST API 无法满足特定需求(例如政策限制),否则不推荐使用旧的 NVP/SOAP API。全世界都在向 OAuth 2.0 和 REST API 迁移,使用最新的接口将带来更好的体验和支持。
PayPal 提供了官方的 API 参考文档,详尽地介绍了 API 的使用方式。不过,如果直接使用这些 API 会比较繁琐,我们更希望快速完成业务需求。建议直接安装 PayPal-PHP-SDK,并通过其 Wiki 作为起点进行集成。
在完成示例之前,请确保已配置好以下内容:
Client ID
Client Secret
Webhook API(必须是 HTTPS 开头且使用 443 端口,本地调试可结合 ngrok)
Return URL(确保符合要求)
在了解了 REST API 后,理解接口分类对于完成业务需求非常重要。以下是一些常见的接口分类:
Payments:一次性支付接口,不支持循环扣款。主要支持 PayPal 支付、信用卡支付等。
Payouts:本项目中未使用。
Authorization and Capture:支持通过 PayPal 账号登录并获取相关信息。
Billing Plan & Agreements:用于实现 订阅 功能,支持循环扣款,这是本文的重点。
Vault:用于存储信用卡信息。
Notifications:处理 Webhook 信息。
Invoice:用于票据处理。
实现循环扣款功能需要四个步骤:
创建并激活升级计划;
创建订阅(Agreement),并跳转到 PayPal 网站等待用户同意;
用户同意后,执行订阅;
获取扣款账单。
升级计划对应 Plan 类。在创建时需要注意以下几点:
Plan 创建后默认为 CREATED 状态,必须修改为 ACTIVE 才能正常使用。
PaymentDefinition 和 MerchantPreferences 两个对象是必填项。
如果计划为 TRIAL 类型,必须有配套的 REGULAR 支付定义,否则会报错。
以下是一个 Standard 计划的示例:
$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"
];
创建并激活计划的代码:
public function createPlan($param)
{
$apiContext = $this->getApiContext();
$plan = new Plan();
$plan->setName($param->name)
->setDescription($param->desc)
->setType('INFINITE'); // 设置为无限循环
$paymentDefinition = new PaymentDefinition();
$paymentDefinition->setName($param->name)
->setType($param->type)
->setFrequency($param->frequency)
->setFrequencyInterval((string)$param->frequency_interval)
->setCycles((string)$param->cycles)
->setAmount(new Currency(['value' => $param->amount, 'currency' => $param->currency]));
$chargeModel = new ChargeModel();
$chargeModel->setType('TAX')
->setAmount(new Currency(['value' => 0, 'currency' => $param->currency]));
$returnUrl = config('payment.returnurl');
$merchantPreferences = new MerchantPreferences();
$merchantPreferences->setReturnUrl("$returnUrl?success=true")
->setCancelUrl("$returnUrl?success=false")
->setAutoBillAmount("yes")
->setInitialFailAmountAction("CONTINUE")
->setMaxFailAttempts("0")
->setSetupFee(new Currency(['value' => $param->amount, 'currency' => 'USD']));
$plan->setPaymentDefinitions([$paymentDefinition]);
$plan->setMerchantPreferences($merchantPreferences);
try {
$output = $plan->create($apiContext);
} catch (Exception $ex) {
return false;
}
$patch = new Patch();
$value = new PayPalModel('{"state":"ACTIVE"}');
$patch->setOp('replace')
->setPath('/')
->setValue($value);
$patchRequest = new PatchRequest();
$patchRequest->addPatch($patch);
$output->update($patchRequest, $apiContext);
return $output;
}
创建订阅的过程如下:
public function createPayment($param)
{
$apiContext = $this->getApiContext();
$agreement = new Agreement();
$agreement->setName($param['name'])
->setDescription($param['desc'])
->setStartDate(Carbon::now()->addMonths(1)->toIso8601String());
$plan = new Plan();
$plan->setId($param['id']);
$agreement->setPlan($plan);
$payer = new Payer();
$payer->setPaymentMethod('paypal');
$agreement->setPayer($payer);
try {
$agreement = $agreement->create($apiContext);
$approvalUrl = $agreement->getApprovalLink();
} catch (Exception $ex) {
return "创建支付失败,请重试或联系商家。";
}
return $approvalUrl; // 跳转到 PayPal 网站
}
用户同意后,必须调用 Agreement 的 execute 方法才能完成订阅。
订阅后,可能不会立刻产生交易记录,若为空可稍后再次尝试。获取交易记录的代码如下:
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("获取交易记录失败:" . $e->getMessage());
return null;
}
return $result->getAgreementTransactionList();
}
实现功能后,仍有以下注意事项:
国内使用 Sandbox 测试时连接较慢,需考虑用户关闭页面的情况。
必须实现 Webhook,否则无法接收到用户取消订阅的通知。
用户在切换订阅计划时,必须取消之前的订阅。
订阅过程应当作为原子操作,以确保长时间的操作不会影响用户体验。
使用门槛极低,微信支付宝均可开通使用。支持开通各类海外平台:ChatGPT、Claude、Google Play、Apple Store、OpenAI、X、Patreon、MidJourney、Amazon、POE、Microsoft、Facebook、GitHub、Telegram、PayPal等各类海淘订阅平台。使用邀请码:ACCPAY,立享消费0手续费,减免开卡费用。
