# 如何通过 PayPal 实现循环扣款（订阅功能）？

By [woduni](https://paragraph.com/@woduni) · 2025-01-15

---

介绍
--

在业务开发中，经常需要集成 **PayPal** 支持循环扣款功能。然而，市面上鲜有详细的开发教程。本文将结合实际案例，详解如何使用 PayPal 的支付接口实现订阅功能，并探讨可能遇到的问题及优化方案。

PayPal 接口分类
-----------

PayPal 提供了多套支付接口，开发者可以根据需求选择适合的方式：

1.  **Braintree 接口**：提供支付、升级计划、信用卡管理等功能。
    
2.  **REST API 接口**：主流接口，采用 OAuth 2.0 认证，支持多种支付模式。
    
3.  **NVP/SOAP API 接口**：旧版接口，不推荐使用。
    

### 为什么选择 REST API？

REST API 是主流的开发接口，支持 OAuth 2.0 认证和现代化的支付方式，适合构建订阅功能。PayPal 官方提供了详细的 [API 文档](https://developer.paypal.com/docs/api/overview/)，开发者可以参考快速上手。

☞ [WildCard | 一分钟注册，轻松订阅海外线上服务](https://bit.ly/bewildcard)

使用 WildCard 虚拟卡可轻松绑定 PayPal，开通订阅服务。支持微信、支付宝支付，邀请码：**ACCPAY**，享 0 手续费，减免开卡费用。

* * *

实现循环扣款的四个步骤
-----------

### 1\. 创建升级计划（Billing Plan）

升级计划用于定义订阅周期、费用等关键信息。以下是创建计划时的关键点：

*   **状态激活**：计划创建后默认处于 `CREATED` 状态，需将其设置为 `ACTIVE`。
    
*   **费率定义**：包括周期、金额、货币等信息。
    
*   **首次扣款**：通过 `setSetupFee` 设置订阅后的首次扣款费用。
    

#### 示例代码

    $param = [
        "name" => "standard_monthly",
        "desc" => "Standard Plan for one month",
        "type" => "REGULAR",
        "frequency" => "MONTH",
        "frequency_interval" => 1,
        "cycles" => 0,
        "amount" => 20,
        "currency" => "USD"
    ];
    
    public function createPlan($param)
    {
        $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']]));
    
        $merchantPreferences = new MerchantPreferences();
        $merchantPreferences->setReturnUrl("https://yourdomain.com/success")
                            ->setCancelUrl("https://yourdomain.com/cancel")
                            ->setSetupFee(new Currency(['value' => $param['amount'], 'currency' => 'USD']));
    
        $plan->setPaymentDefinitions([$paymentDefinition]);
        $plan->setMerchantPreferences($merchantPreferences);
    
        try {
            $plan->create($this->apiContext);
        } catch (Exception $ex) {
            // 错误处理
        }
        return $plan;
    }
    

### 2\. 创建订阅协议（Agreement）

订阅协议链接用户与升级计划，为用户生成支付链接以完成订阅。

#### 示例代码

    $param = [
        'id' => 'P-123456789', // 计划 ID
        'name' => 'Standard',
        'desc' => 'Standard Plan for one month'
    ];
    
    public function createAgreement($param)
    {
        $agreement = new Agreement();
        $agreement->setName($param['name'])
                  ->setDescription($param['desc'])
                  ->setStartDate(Carbon::now()->addMonth()->toIso8601String());
    
        $plan = new Plan();
        $plan->setId($param['id']);
        $agreement->setPlan($plan);
    
        $payer = new Payer();
        $payer->setPaymentMethod('paypal');
        $agreement->setPayer($payer);
    
        try {
            $agreement->create($this->apiContext);
            return $agreement->getApprovalLink();
        } catch (Exception $ex) {
            // 错误处理
        }
    }
    

### 3\. 执行订阅

用户同意后，需要调用 `Agreement::execute` 方法完成订阅。

#### 示例代码

    public function executeAgreement($token)
    {
        $agreement = new Agreement();
        try {
            $agreement->execute($token, $this->apiContext);
        } catch (Exception $ex) {
            // 错误处理
        }
        return $agreement;
    }
    

### 4\. 查询交易记录

获取用户订阅后的扣款记录，用于统计或展示。

#### 示例代码

    public function getTransactions($agreementId)
    {
        $params = [
            'start_date' => '2025-01-01',
            'end_date' => '2025-12-31'
        ];
    
        try {
            $result = Agreement::searchTransactions($agreementId, $params, $this->apiContext);
        } catch (Exception $ex) {
            // 错误处理
        }
        return $result->getAgreementTransactionList();
    }
    

* * *

需要考虑的问题
-------

在实际开发中需特别注意以下问题：

1.  **网络超时**：国内连接 PayPal Sandbox 环境较慢，建议处理用户中途退出场景。
    
2.  **Webhook 监听**：需实现 Webhook 接口以接收用户取消订阅的通知。
    
3.  **升级计划切换**：若用户切换订阅计划，需取消旧协议并创建新协议。
    
4.  **原子性操作**：订阅的创建、切换和支付流程应设计为原子性操作，建议使用队列实现。
    

* * *

通过以上步骤，即可实现 PayPal 的循环扣款功能。如果您需要便捷的虚拟卡服务完成绑定和支付，推荐使用 WildCard 提供的一站式支付解决方案。

☞ [WildCard | 一分钟注册，轻松订阅海外线上服务](https://bit.ly/bewildcard)

---

*Originally published on [woduni](https://paragraph.com/@woduni/paypal)*
