1. 开发者账号准备

企业版paypal账号审核进度太慢,可以先注册自己的个人paypal账号然后创建沙箱应用,创建两个沙箱账号一个卖家(默认就有)一个买家。直接去测试支付流程。支付后的订单默认就到卖家沙箱账号中了。可以登录卖家的沙箱账号到订单查看

  • 注册 PayPal 开发者账号
  • 创建沙盒(Sandbox)测试账号(包括商家和买家账号)
  • 获取 API 凭证(Client ID 和 Secret),用于测试和生产环境

2、安装paypal依赖  

尝试很多依赖发现后续引用都会出现报错 后面示例是直接写的

 composer require paypal/paypal-checkout-sdk:1.0.0

3、示例

示例是fastadmin框架,根据实际使用的框架 安排配置文件和初始化文件的位置。代码中可以引用到就行

shop-project/  # 项目根目录
├─ app/
│  ├─ api/          
│  │  └─ controller
|  |            └─ Paypal.php      # 接口控制器(创建订单)
|  └─—— common/
|          └─library
|                └─ PayPalClient.php #初始化paypal客户端 
└── extra/                           # 扩展文件
  └─ paypal.php                      # paypal配置信息文件


1、paypal配置信息文件(paypal.php)
<?php
return [
    'client_id' => 'AWe1Ni_ArcgSLVQv3rsvTJ7VdauJiYnmHYOsBpWSRKTAyoXXjVUD_LF0rbmyKR3y7sZa7bNLtffxt_-p',
    'client_secret' => 'EO2bg_zKEgNl3fKT6V1r0By69sW-GjoGLNa7xIsGgdKu4CcQVHbTCQJGjqo_YVu27uZf43PxrEv4wA9d',
    'success_url' => 'http://你的域名/回调地址', // 支付成功回调地址
    'cancel_url' => 'http://你的域名/回调地址',  // 支付取消回调地址
];
2、初始化paypal客户端(PayPalCilent.php)

这里是直接写的,安装很多的依赖都用不上(以后发现好办法了会更新)

沙箱环境 需要切换( $this->baseUrl = self::SANDBOX_URL;)

生产环境 需要切换( $this->baseUrl = self::LIVE_URL;)

<?php
namespace app\common\library;

class PayPalClient
{
    // PayPal API基础地址(沙盒环境)
    const SANDBOX_URL = 'https://api-m.sandbox.paypal.com';
    // 生产环境地址
    const LIVE_URL = 'https://api-m.paypal.com';

    private $clientId;
    private $clientSecret;
    private $baseUrl;
    private $accessToken;

    public function __construct()
    {
        // 从配置获取凭证
        $this->clientId = config('paypal.client_id');
        $this->clientSecret = config('paypal.client_secret');

        // 验证配置
        if (empty($this->clientId) || empty($this->clientSecret)) {
            throw new \Exception("请配置PayPal的client_id和client_secret");
        }

        // 默认使用沙盒环境
        $this->baseUrl = self::LIVE_URL;

        // 获取访问令牌
        $this->accessToken = $this->getAccessToken();
    }

    /**
     * 获取访问令牌
     */
    private function getAccessToken()
    {
        $url = $this->baseUrl . '/v1/oauth2/token';

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
        curl_setopt($ch, CURLOPT_USERPWD, $this->clientId . ':' . $this->clientSecret);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        // 关键修复:处理SSL证书问题
        // 生产环境建议开启验证,仅测试环境临时关闭
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);  // 关闭证书验证
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);      // 关闭主机验证

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        // 捕获CURL错误
        if(curl_errno($ch)) {
            throw new \Exception("CURL错误:" . curl_error($ch));
        }
        curl_close($ch);

        $result = json_decode($response, true);

        if ($httpCode != 200 || empty($result['access_token'])) {
            throw new \Exception("获取访问令牌失败:" . json_encode($result));
        }

        return $result['access_token'];
    }

    /**
     * 创建PayPal订单
     */
    public function createOrder($amount, $currency, $returnUrl, $cancelUrl, $referenceId)
    {
        $url = $this->baseUrl . '/v2/checkout/orders';

        $data = [
            'intent' => 'CAPTURE',
            'purchase_units' => [[
                'reference_id' => $referenceId,
                'amount' => [
                    'currency_code' => $currency,
                    'value' => number_format($amount, 2)
                ]
            ]],
            'application_context' => [
                'return_url' => $returnUrl,
                'cancel_url' => $cancelUrl
            ]
        ];

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $this->accessToken
        ]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        // 同样添加SSL证书处理
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if(curl_errno($ch)) {
            throw new \Exception("CURL错误:" . curl_error($ch));
        }
        curl_close($ch);

        $result = json_decode($response, true);

        if ($httpCode != 201 || empty($result['id'])) {
            throw new \Exception("创建订单失败:" . json_encode($result));
        }

        return $result;
    }

    /**
     * 捕获支付
     */
    public function capturePayment($orderId)
    {
        $url = $this->baseUrl . '/v2/checkout/orders/' . $orderId . '/capture';

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $this->accessToken
        ]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        // SSL证书处理
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if(curl_errno($ch)) {
            throw new \Exception("CURL错误:" . curl_error($ch));
        }
        curl_close($ch);

        $result = json_decode($response, true);

        if ($httpCode != 201 || $result['status'] != 'COMPLETED') {
            throw new \Exception("捕获支付失败:" . json_encode($result));
        }

        return $result;
    }

    /**
     * 获取PayPal订单状态(新增方法)
     * 用于验证订单是否真的支付成功,避免前端伪造数据
     */
    public function getOrderStatus($orderId)
    {
        $url = $this->baseUrl . '/v2/checkout/orders/' . $orderId;

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HTTPGET, true); // GET请求
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $this->accessToken
        ]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        // 测试环境关闭SSL验证(生产环境需开启)
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if (curl_errno($ch)) {
            throw new \Exception("获取订单状态CURL错误:" . curl_error($ch));
        }
        curl_close($ch);

        $result = json_decode($response, true);

        if ($httpCode != 200) {
            throw new \Exception("获取订单状态失败:" . json_encode($result));
        }

        return $result;
    }
}
3、接口控制器(创建订单)
<?php
namespace app\api\controller;

use app\common\library\PayPalClient;
use app\common\controller\Api;
use think\Log;

class Paypal extends Api
{
    protected $noNeedLogin = ['*'];
    protected $noNeedRight = ['*'];
    public function createOrder()
    {
        // 生成唯一错误追踪ID(与返回给用户的一致)
        $errorTrackId = uniqid();

        // 写入初始日志
        Log::error("===== 开始创建订单 [追踪ID: {$errorTrackId}] =====");

        try {
            $orderId = $this->request->param('order_id');
            Log::error("接收的本地订单ID: {$orderId}");
            $order = \app\admin\model\shop\Order::find($orderId);
            Log::error("查询到的订单数据: " . json_encode($order ? $order->toArray() : '无'));
            if (!$order || $order['paystate'] == 1) {
                $msg = "订单不存在或已支付";

                Log::error($msg);
                Log::error("===== 结束创建订单 [追踪ID: {$errorTrackId}] =====");
                // 使用json助手函数返回响应
                return json([
                    'code' => 1,
                    'msg' => $msg,
                    'data' => null,
                    'time' => time()
                ]);
            }

            $paypal = new PayPalClient();

            Log::error("PayPal客户端初始化完成");
            // 准备API参数
            $successUrl = config('paypal.success_url') . '?order_id=' . $orderId;
            $cancelUrl = config('paypal.cancel_url') . '?order_id=' . $orderId;
            $params = [
                'amount' => $order['amount'],
                'currency' => 'USD',
                'success_url' => $successUrl,
                'cancel_url' => $cancelUrl,
                'order_sn' => $order['order_sn']
            ];

            Log::error("准备调用API的参数: " . json_encode($params));
            // 调用PayPal API
            $result = $paypal->createOrder(
                $order['amount'],
                'USD',
                $successUrl,
                $cancelUrl,
                $order['order_sn']
            );

            Log::error("PayPal API返回结果: " . json_encode($result));
            // 验证返回结果
            if (!is_array($result)) {
                throw new \Exception("API返回格式错误,不是数组");
            }
            if (empty($result['id'])) {
                throw new \Exception("API返回缺少订单ID字段");
            }

            // 保存到本地
            $order->payinfo = json_encode(['paypal_order_id' => $result['id'], 'track_id' => $errorTrackId]);
            $saveResult = $order->save();

            Log::error("本地订单保存结果: " . ($saveResult ? '成功' : '失败') . json_encode($order->getError()));
            if (!$saveResult) {
                throw new \Exception("本地订单保存失败");
            }


            Log::error("===== 订单创建成功 [追踪ID: {$errorTrackId}] =====");
            // 直接返回JSON响应,定义明确的结构
            // 使用json助手函数返回成功响应
            return json([
                'code' => 1,
                'msg' => '订单创建成功',
                'data' => [
                    'paypal_order_id' => $result['id'],
                    'local_order_id' => $order->id,
                    'track_id' => $errorTrackId
                ],
                'time' => time()
            ]);

        } catch (\Exception $e) {
            // 记录完整异常信息
            $errorInfo = [
                'message' => $e->getMessage() ?: '无错误消息',
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'trace' => $e->getTraceAsString()
            ];


            Log::error("捕获异常: " . json_encode($errorInfo));

            Log::error("===== 订单创建失败 [追踪ID: {$errorTrackId}] =====");
            // 使用json助手函数返回错误响应
            return json([
                'code' => 0,
                'msg' => "创建订单失败,请稍后重试(错误ID: {$errorTrackId})",
                'data' => null,
                'time' => time()
            ]);
        }
    }

    /**
     * 确认支付结果
     */
    public function confirm()
    {
        $paypalOrderId = $this->request->param('order_id');
        $localOrderId = $this->request->param('local_order_id');
        $tradeNo = $this->request->param('trade_no'); // 接收前端传递的交易号

        try {
            $order = \app\admin\model\shop\Order::find($localOrderId);
            if (!$order) {
                return json(['code' => 0, 'msg' => '订单不存在']);
            }
            // 2. 防止重复处理(如果已支付,直接返回成功)
            if ($order['paystate'] == 1) {
                return json([
                    'code' => 1,
                    'msg' => '订单已支付',
                ]);
            }

            $paypal = new PayPalClient();
            $orderStatus = $paypal->getOrderStatus($paypalOrderId);

            if ($orderStatus['status'] != 'COMPLETED') {
                throw new \Exception("PayPal订单未完成支付,状态:{$orderStatus['status']}");
            }

            // 5. 更新本地订单状态(使用前端传递的交易号)
            $order->paystate = 1;
            $order->paytime = time();
            $order->trade_no = $tradeNo;
            $order->payinfo = json_encode([
                'paypal_order_id' => $paypalOrderId,
                'trade_no' => $tradeNo,
                'paypal_status' => $orderStatus['status']
            ]);
            $order->save();

            // 触发支付成功事件
//            event('OrderPaySuccess', $order);

            return json([
                'code' => 1,
                'msg' => '支付成功',
                'data' => ['redirect' => 'order/detail']
            ]);
        } catch (\Exception $e) {
            return json(['code' => 0, 'msg' => '支付确认失败:' . $e->getMessage()]);
        }
    }

}

后端到这里就算结束了。下方记录一下相对应的前端页面调用

实际生产环境 订单号和应付金额以及订单id 都需要通过 createOrder接口获取 示例是写了一个默认的订单号  

注意:这里也要区分生产环境和沙箱环境 

生产环境用 script.src = `https://www.sandbox.paypal.com/sdk/js?client-id=${paypalClientId}&currency=USD`;
测试环境用 script.src = `https://www.sandbox.paypal.com/sdk/js?client-id=${paypalClientId}&currency=USD`;
<!DOCTYPE html>
<html>

<head>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>paypal支付测试页</title>
    <link rel="shortcut icon" href="__CDN__/assets/img/favicon.ico"/>
    <link href="__CDN__/assets/css/index.css" rel="stylesheet">
</head>

<body>
<div class="pay-container" style="max-width: 600px; margin: 50px auto; padding: 0 20px;">
    <!-- 订单基本信息 -->
    <div class="order-info" style="border-bottom: 1px solid #eee; padding-bottom: 20px; margin-bottom: 30px;">
        <h3 style="margin-bottom: 15px;">订单支付 <small class="text-gray">订单号:20250828042007000041831752</small></h3>
        <p style="font-size: 16px; color: #e64340;">应付金额:<span style="font-size: 24px;">114.00</span> 元</p>
    </div>

    <!-- PayPal 按钮容器(默认隐藏,加载成功后显示) -->
    <div id="paypal-button-container" style="display: none; margin: 30px 0;"></div>

    <!-- 加载状态提示(默认显示) -->
    <div id="loading-tip" style="text-align: center; padding: 50px 0; color: #666;">
        <i class="fa fa-spinner fa-spin" style="font-size: 24px; margin-bottom: 10px;"></i>
        <p>正在加载 PayPal 支付组件...</p>
    </div>

    <!-- 错误提示(默认隐藏) -->
    <div id="error-tip" style="display: none; text-align: center; padding: 50px 0; color: #e64340;">
        <i class="fa fa-exclamation-circle" style="font-size: 24px; margin-bottom: 10px;"></i>
        <p id="error-message">支付组件加载失败,请刷新页面重试</p>
    </div>

    <!-- 隐藏的订单ID(供JS使用) -->
    <input type="hidden" id="local-order-id" value="191">
    <input type="hidden" id="paypal-client-id" value="{$Think.config.paypal.client_id}">
</div>
<!-- 直接在页面内写JS,避免外部文件依赖 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
    $(function() {
        // 1. 获取页面基础参数
        const localOrderId = $('#local-order-id').val(); // 本地订单ID
        const paypalClientId = $('#paypal-client-id').val(); // PayPal客户端ID
        let paypalOrderId = ''; // PayPal创建的订单ID

        // 2. 第一步:调用后端接口创建 PayPal 订单
        $.ajax({
            url: '/api/paypal/createOrder', // 对应第三步的创建订单接口
            type: 'GET',
            data: { order_id: localOrderId },
            dataType: 'json',
            success: function(res) {
                console.log(res)
                if (res.code === 1) {
                    paypalOrderId = res.data.paypal_order_id; // 拿到PayPal订单ID
                    loadPayPalSdk(); // 加载PayPal SDK
                } else {
                    // 订单创建失败,显示错误
                    showError(res.msg || '创建支付订单失败,请联系客服');
                }
            },
            error: function(xhr, status, error) {
                // AJAX请求失败(如接口不存在、网络问题)
                showError('调用支付接口失败,错误信息:' + error);
            }
        });

        // 3. 第二步:加载 PayPal SDK(加载成功后渲染按钮)
        function loadPayPalSdk() {
            // 动态创建SDK脚本标签
            const script = document.createElement('script');
            // SDK地址带客户端ID,确保正确识别商户
            script.src = `https://www.sandbox.paypal.com/sdk/js?client-id=${paypalClientId}&currency=USD`;
            // 加载成功回调
            script.onload = function() {
                renderPayPalButton(); // 渲染支付按钮
            };
            // 加载失败回调
            script.onerror = function() {
                showError('PayPal支付组件加载失败,请检查网络或稍后重试');
            };
            // 插入到页面中
            document.body.appendChild(script);
        }

        // 4. 第三步:渲染 PayPal 支付按钮
        function renderPayPalButton() {
            // 检查PayPal对象是否存在(防止SDK加载异常)
            if (typeof paypal === 'undefined') {
                showError('PayPal组件初始化失败,请刷新页面重试');
                return;
            }

            // 渲染按钮
            paypal.Buttons({
                // 按钮样式(可选,适配页面风格)
                style: {
                    layout: 'vertical', // 垂直布局(vertical)或水平布局(horizontal)
                    color: 'blue',      // 按钮颜色(blue/black/silver/gold)
                    shape: 'rect',      // 按钮形状(rect/pill)
                    label: 'pay'        // 按钮文字(pay/checkout/buy-now/pay-with-paypal)
                },

                // 【核心】确认订单(直接使用已创建的PayPal订单ID)
                createOrder: function(data, actions) {
                    return paypalOrderId; // 无需重新创建,直接返回已有的订单ID
                },

                // 【核心】用户支付成功后的回调
                onApprove: function(data, actions) {
                    // 捕获支付结果(确认资金已到账)
                    return actions.order.capture().then(function(details) {
                        // 支付成功,跳转到后端确认接口(更新订单状态)

                        window.location.href = `/api/paypal/confirm?order_id=${data.orderID}&trade_no=${details.purchase_units[0].payments.captures[0].id}&local_order_id=${localOrderId}`;
                    });
                },

                // 支付过程中出错的回调
                onError: function(err) {
                    console.error('PayPal支付错误:', err); // 控制台打印错误(调试用)
                    showError('支付过程中发生错误:' + (err.message || '未知错误'));
                },

                // 用户取消支付的回调
                onCancel: function(data) {
                    // 跳转到支付取消页面(可自定义)
                    window.location.href = `/order/paypal/cancel?order_id=${localOrderId}`;
                }
            }).render('#paypal-button-container') // 渲染到指定容器
                .then(function() {
                    // 按钮渲染成功,显示按钮、隐藏加载提示
                    $('#loading-tip').hide();
                    $('#paypal-button-container').show();
                })
                .catch(function(err) {
                    // 按钮渲染失败
                    showError('支付按钮渲染失败:' + err.message);
                });
        }

        // 5. 通用错误提示方法(统一控制显示状态)
        function showError(message) {
            $('#loading-tip').hide();
            $('#paypal-button-container').hide();
            $('#error-message').text(message);
            $('#error-tip').show();
        }
    });
</script>
</body>

</html>

更多推荐