Ask me anything
Ithy Logo
Sign in to save

通过 PHP 后端实现 PayPal 支付的完整流程

从沙盒测试到实时交易:构建无缝、安全的 PayPal 支付体验

paypal-php-integration-guide-wo39gtqd

主要亮点

  • 沙盒环境先行: 在实际部署前,务必在 PayPal 沙盒环境中充分测试支付流程,模拟真实交易,确保功能完善。
  • 后端 PHP 核心: PHP 后端负责处理支付请求、API 调用、交易验证以及与数据库的交互,是实现安全支付的关键。
  • IPN/Webhook 关键: 利用即时支付通知 (IPN) 或 Webhook 异步处理 PayPal 返回的支付状态,确保订单状态的准确更新和后端业务逻辑的执行。

PayPal 作为全球广泛认可的在线支付平台,为商家提供了安全便捷的收款方式。对于希望在 PHP 应用程序中集成 PayPal 支付的开发者来说,理解其完整的支付发起过程至关重要。这不仅涉及前端的用户交互,更包括后端 PHP 对支付流程的缜密控制、API 调用以及状态处理。本指南将详细阐述如何使用 PHP 后端来发起和管理 PayPal 支付,涵盖从账户设置到最终交易验证的每一个环节。


PayPal 支付集成概览

支付流程与核心组件

PayPal 支付集成通常涉及前端和后端两个主要部分。前端负责显示支付按钮、收集用户输入,并启动 PayPal 结账体验。后端(在此案例中为 PHP)则负责处理敏感数据、与 PayPal API 进行通信、验证交易并更新订单状态。了解这个交互流程是成功集成的基础。

PayPal E-commerce Flow

图 1: PayPal 电子商务支付流程概览

支付流程的关键阶段

一个典型的 PayPal 支付流程可以分为以下几个关键阶段:

  1. 用户发起支付: 顾客在网站上选择商品,点击 PayPal 支付按钮。
  2. 重定向至 PayPal: 网站将顾客重定向到 PayPal 页面,或通过弹窗/内嵌方式加载 PayPal 结账体验。
  3. 用户授权: 顾客登录 PayPal 账户或输入支付信息,并授权支付。
  4. 支付处理: PayPal 处理支付。
  5. 回调与通知: PayPal 将支付结果通知商家网站。
  6. 后端验证与完成: 商家后端验证支付结果,更新订单状态,完成交易。

PayPal 支付集成方式

PayPal 提供了多种集成方式,其中最常用且推荐的方式是使用 PayPal Checkout(以前称为 Smart Buttons)结合 REST API 或官方 PHP SDK。对于旧版集成,可能会涉及到 PayPal Payments Standard 或 PayPal Pro (DoDirectPayment API)。

  • PayPal Standard Checkout / Smart Buttons: 这是推荐的集成方式,它允许您在网站上渲染 PayPal 按钮,引导用户在 PayPal 页面上完成支付。前端使用 JavaScript SDK,后端使用 PHP 处理交易捕获和验证。
  • PayPal REST API / PHP SDK: 直接通过 API 调用来控制支付流程。PHP SDK 是对 REST API 的封装,简化了开发过程,允许您在服务器端创建支付、处理付款和管理退款。
  • PayPal Payments Pro / DoDirectPayment API (Legacy): 允许在商家网站上直接处理信用卡支付,用户无需离开网站。但这需要商家承担 PCI DSS 合规性。PayPal 推荐使用 Advanced Checkout 或 Braintree 进行新的信用卡集成。

考虑到安全性和易用性,本指南将重点介绍基于 PayPal Checkout (Smart Buttons) 和 PayPal REST API 的 PHP 后端集成。


PHP 后端集成 PayPal 的详细步骤

第一步:设置 PayPal 账户和获取沙盒凭证

在开始编码之前,您需要一个 PayPal 开发者账户,并获取用于测试的沙盒 (Sandbox) 凭证。

  1. 创建 PayPal 开发者账户: 访问 PayPal 开发者网站并注册或登录。
  2. 创建沙盒账户: 在开发者面板中,创建两个沙盒账户:一个买家账户 (Personal) 和一个商家账户 (Business)。这些账户将用于模拟测试环境中的支付。
  3. 获取 API 凭证: 针对您的沙盒商家账户,获取 REST API 凭证(Client ID 和 Secret Key)。这些凭证是您的 PHP 后端与 PayPal API 通信的“钥匙”。
PayPal Developer Account Settings

图 2: PayPal 开发者账户中的 API 凭证管理

第二步:搭建 PHP 项目结构与配置

创建一个清晰的项目结构,并配置必要的常量和数据库连接。

文件结构示例:


/your_project/
├── index.php             // 购买页面,包含 PayPal 按钮
├── create_order.php      // 后端脚本:创建 PayPal 订单
├── capture_order.php     // 后端脚本:捕获 PayPal 订单
├── success.php           // 支付成功页面
├── cancel.php            // 支付取消页面
├── notify.php            // IPN 监听器 (可选,但推荐用于异步通知)
├── config.php            // 配置文件,存放 API 凭证和数据库信息
└── vendor/               // Composer 依赖 (如 PayPal PHP SDK)
    

config.php 配置:

在该文件中定义您的 PayPal API 凭证以及数据库连接信息。


<?php
define('PAYPAL_CLIENT_ID', 'YOUR_SANDBOX_CLIENT_ID');
define('PAYPAL_CLIENT_SECRET', 'YOUR_SANDBOX_CLIENT_SECRET');
define('PAYPAL_API_BASE_URL', 'https://api-m.sandbox.paypal.com'); // 沙盒环境
// define('PAYPAL_API_BASE_URL', 'https://api-m.paypal.com'); // 生产环境

// 数据库配置 (示例)
define('DB_HOST', 'localhost');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_NAME', 'your_database');

// 错误报告
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

// 自动加载 Composer 依赖 (如果使用 SDK)
require __DIR__ . '/vendor/autoload.php';

// 简单数据库连接 (请根据实际情况使用 PDO 或 mysqli 封装)
$conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}
?>
    

第三步:前端集成 PayPal 智能按钮 (Smart Buttons)

在您的产品页面 (index.php) 中添加 PayPal 智能按钮。这需要引入 PayPal JavaScript SDK。


<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>PayPal 支付示例</title>
    <script src="https://www.paypal.com/sdk/js?client-id=YOUR_SANDBOX_CLIENT_ID&currency=USD" data-sdk-integration-source="integrationbuilder"></script>
</head>
<body>
    <h2>购买商品</h2>
    <p>商品名称:演示产品</p>
    <p>价格:$10.00 USD</p>

    <div id="paypal-button-container"></div>

    <script>
        paypal.Buttons({
            // 设置创建订单的 API 调用
            createOrder: function(data, actions) {
                return fetch('/create_order.php', {
                    method: 'post',
                    headers: {
                        'content-type': 'application/json'
                    },
                    body: JSON.stringify({
                        items: [{
                            name: '演示产品',
                            quantity: '1',
                            price: '10.00',
                            currency: 'USD'
                        }],
                        total: '10.00'
                    })
                }).then(function(res) {
                    return res.json();
                }).then(function(orderData) {
                    return orderData.id; // 返回 PayPal 订单 ID
                });
            },
            // 设置支付审批后的 API 调用
            onApprove: function(data, actions) {
                return fetch('/capture_order.php', {
                    method: 'post',
                    headers: {
                        'content-type': 'application/json'
                    },
                    body: JSON.stringify({
                        orderID: data.orderID
                    })
                }).then(function(res) {
                    return res.json();
                }).then(function(orderData) {
                    // 支付成功后重定向到成功页面
                    if (orderData.status === 'COMPLETED') {
                        alert('支付成功!');
                        window.location.href = '/success.php?orderID=' + data.orderID;
                    } else {
                        alert('支付未完成或出现问题。');
                        window.location.href = '/cancel.php';
                    }
                });
            },
            // 设置取消支付后的行为
            onCancel: function (data) {
                alert('支付已取消!');
                window.location.href = '/cancel.php';
            },
            // 设置支付错误后的行为
            onError: function (err) {
                console.error(err);
                alert('支付过程中发生错误。');
                window.location.href = '/cancel.php';
            }
        }).render('#paypal-button-container');
    </script>
</body>
</html>
    

请将 YOUR_SANDBOX_CLIENT_ID 替换为您实际的 PayPal 沙盒客户端 ID。

第四步:后端 PHP 处理订单创建 (create_order.php)

当用户点击 PayPal 按钮时,前端会调用此 PHP 脚本来创建 PayPal 订单。此脚本将向 PayPal REST API 发送请求。


<?php
require_once __DIR__ . '/config.php';

header('Content-Type: application/json');

$input = json_decode(file_get_contents('php://input'), true);
$total = $input['total'] ?? '0.00';
$items = $input['items'] ?? [];

// 获取 PayPal 访问令牌
function getAccessToken($clientId, $clientSecret, $baseUrl) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $baseUrl . '/v1/oauth2/token');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_USERPWD, $clientId . ':' . $clientSecret);
    curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Accept: application/json',
        'Accept-Language: en_US'
    ]);

    $response = curl_exec($ch);
    if (curl_errno($ch)) {
        throw new Exception(curl_error($ch));
    }
    curl_close($ch);
    $data = json_decode($response, true);
    return $data['access_token'] ?? null;
}

try {
    $accessToken = getAccessToken(PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_API_BASE_URL);

    if (!$accessToken) {
        throw new Exception("无法获取 PayPal 访问令牌。");
    }

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

    $purchase_units = [[
        'amount' => [
            'currency_code' => 'USD',
            'value' => $total
        ],
        'items' => array_map(function($item) {
            return [
                'name' => $item['name'],
                'quantity' => $item['quantity'],
                'unit_amount' => [
                    'currency_code' => 'USD',
                    'value' => $item['price']
                ]
            ];
        }, $items)
    ]];

    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'intent' => 'CAPTURE', // 立即捕获支付
        'purchase_units' => $purchase_units,
        'application_context' => [
            'return_url' => 'http://yourdomain.com/success.php', // 替换为您的域名
            'cancel_url' => 'http://yourdomain.com/cancel.php'   // 替换为您的域名
        ]
    ]));

    $response = curl_exec($ch);
    if (curl_errno($ch)) {
        throw new Exception(curl_error($ch));
    }
    curl_close($ch);

    $order = json_decode($response, true);
    echo json_encode($order);

} catch (Exception $e) {
    http_response_code(500);
    echo json_encode(['error' => $e->getMessage()]);
}
?>
    

第五步:后端 PHP 捕获订单 (capture_order.php)

在用户在 PayPal 页面上授权支付后,前端会调用此 PHP 脚本来捕获(完成)交易。


<?php
require_once __DIR__ . '/config.php';

header('Content-Type: application/json');

$input = json_decode(file_get_contents('php://input'), true);
$orderID = $input['orderID'] ?? '';

if (empty($orderID)) {
    http_response_code(400);
    echo json_encode(['error' => '缺少订单 ID。']);
    exit();
}

// 获取 PayPal 访问令牌
function getAccessToken($clientId, $clientSecret, $baseUrl) {
    // 与 create_order.php 中的函数相同,可优化为公共函数
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $baseUrl . '/v1/oauth2/token');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_USERPWD, $clientId . ':' . $clientSecret);
    curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Accept: application/json',
        'Accept-Language: en_US'
    ]);

    $response = curl_exec($ch);
    if (curl_errno($ch)) {
        throw new Exception(curl_error($ch));
    }
    curl_close($ch);
    $data = json_decode($response, true);
    return $data['access_token'] ?? null;
}

try {
    $accessToken = getAccessToken(PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_API_BASE_URL);

    if (!$accessToken) {
        throw new Exception("无法获取 PayPal 访问令牌。");
    }

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, PAYPAL_API_BASE_URL . '/v2/checkout/orders/' . $orderID . '/capture');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $accessToken
    ]);

    $response = curl_exec($ch);
    if (curl_errno($ch)) {
        throw new Exception(curl_error($ch));
    }
    curl_close($ch);

    $result = json_decode($response, true);

    // 假设您有一个名为 'orders' 的数据库表
    // 并且在 create_order 阶段已经记录了订单(例如,状态为 'PENDING')
    // 在这里更新订单状态为 'COMPLETED' 或 'FAILED'
    if (isset($result['status']) && $result['status'] === 'COMPLETED') {
        $stmt = $conn->prepare("UPDATE orders SET status = ?, transaction_id = ? WHERE paypal_order_id = ?");
        $transaction_id = $result['purchase_units'][0]['payments']['captures'][0]['id'] ?? 'N/A';
        $status = 'COMPLETED';
        $stmt->bind_param("sss", $status, $transaction_id, $orderID);
        $stmt->execute();
        $stmt->close();
    } else {
        // 处理非成功状态,例如 "PENDING", "DECLINED" 等
        $status = $result['status'] ?? 'UNKNOWN';
        $stmt = $conn->prepare("UPDATE orders SET status = ? WHERE paypal_order_id = ?");
        $stmt->bind_param("ss", $status, $orderID);
        $stmt->execute();
        $stmt->close();
    }

    echo json_encode($result);

} catch (Exception $e) {
    http_response_code(500);
    echo json_encode(['error' => $e->getMessage()]);
} finally {
    $conn->close();
}
?>
    

第六步:处理支付结果页面 (success.php 和 cancel.php)

这些页面用于向用户显示支付结果。

success.php 示例:


<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>支付成功</title>
</head>
<body>
    <h2>恭喜!支付成功!</h2>
    <p>您的订单 ID: <?php echo htmlspecialchars($_GET['orderID'] ?? 'N/A'); ?></p>
    <p>感谢您的购买!</p>
    <a href="/">返回首页</a>
</body>
</html>
    

cancel.php 示例:


<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>支付取消</title>
</head<
<body>
    <h2>支付已取消</h2>
    <p>您取消了支付过程。如果您有任何问题,请联系我们。</p>
    <a href="/">返回首页</a>
</body>
</html>
    

第七步:数据库集成和 IPN/Webhook 处理(强烈推荐)

为了确保订单状态的准确性和系统的健壮性,您需要将 PayPal 支付信息存储到数据库中,并实现 IPN(Instant Payment Notification)或 Webhook 监听器。

数据库设计示例:

假设您的 orders 表可能包含以下字段:

字段名 数据类型 描述
id INT (Primary Key) 订单的唯一标识
user_id INT 用户 ID
amount DECIMAL(10,2) 订单总金额
currency VARCHAR(3) 货币类型 (如 USD)
status VARCHAR(50) 订单状态 (PENDING, COMPLETED, CANCELED, REFUNDED等)
paypal_order_id VARCHAR(255) PayPal 返回的订单 ID
transaction_id VARCHAR(255) PayPal 返回的交易 ID (捕获后)
created_at DATETIME 订单创建时间
updated_at DATETIME 订单更新时间

在 create_order.php 成功创建 PayPal 订单后,您应该立即在数据库中创建一个新订单记录,状态设置为 'PENDING',并保存 paypal_order_id。

在 capture_order.php 成功捕获订单后,您应该更新数据库中对应订单的状态为 'COMPLETED',并保存 transaction_id。

IPN / Webhook 监听器 (notify.php):

虽然前端的 onApprove 回调可以更新订单状态,但考虑到网络延迟、用户关闭浏览器等不确定性,强烈建议实现 IPN (Instant Payment Notification) 或 Webhook 来接收 PayPal 的异步通知。这是最可靠的支付状态更新方式。

配置 IPN 或 Webhook 在 PayPal 开发者面板中进行。PayPal 会在交易状态变更时向您配置的 URL 发送 POST 请求,您的 notify.php 脚本将负责处理这些请求。

IPN 处理流程:

  1. 接收 PayPal 发送的 POST 请求。
  2. 将接收到的所有数据原封不动地回传给 PayPal 进行验证 (在请求头部添加 cmd=_notify-validate)。
  3. PayPal 返回 VERIFIED 或 INVALID。
  4. 如果验证为 VERIFIED,则根据 PayPal 发送的交易状态(例如 payment_status=Completed)更新您数据库中的订单状态。

Webhook 是更现代和推荐的异步通知方式,提供了更结构化的 JSON 数据和更灵活的事件订阅。


安全性与最佳实践

确保 PayPal 支付集成的安全性

在集成 PayPal 支付时,安全性是至关重要的。以下是一些最佳实践:

  • 永远不要在前端处理敏感数据: 价格、产品 ID 等敏感信息应始终在后端验证。客户端 JavaScript 只应传递 PayPal 订单 ID。
  • 使用 HTTPS: 您的整个网站,尤其是支付相关的页面和 API 端点,必须使用 HTTPS 加密,保护数据传输安全。
  • 验证 IPN/Webhook: 每次收到 PayPal 通知时,都必须对其进行验证,以确保其确实来自 PayPal 且未被篡改。
  • 防范重复处理: 在处理 IPN 或 Webhook 通知时,务必检查交易 ID 是否已在您的数据库中处理过,以避免重复发货或重复更新订单。
  • 错误日志: 记录所有支付相关的错误和异常,以便调试和故障排除。
  • 沙盒测试: 在将集成部署到生产环境之前,务必在 PayPal 沙盒环境中进行全面的测试。

性能与可扩展性考量

图 3: PayPal PHP 集成关键成功因素雷达图

此雷达图直观地展示了 PayPal PHP 集成中的几个关键成功因素。例如,安全性和可靠性得分最高,表明它们在支付系统中是不可妥协的。开发速度和可扩展性也相对较高,反映了现代 SDK 和 API 设计的优势。而易用性和维护成本则在中等水平,意味着虽然 PayPal 提供了丰富的文档,但完整的集成仍需要一定的技术投入和持续的维护。

除了安全性,支付系统的性能和可扩展性也需要考虑:

  • 异步处理: 使用 IPN 或 Webhook 进行异步通知,避免用户等待 PayPal 响应而导致页面加载缓慢。
  • API 速率限制: 了解 PayPal API 的速率限制,并适当处理。
  • 数据库优化: 确保您的数据库能够高效处理订单和支付状态的更新。
  • 错误处理和重试机制: 为 API 调用实现健壮的错误处理和重试机制,以应对网络问题或临时性 API 故障。

常见问题 (FAQ)

如何区分 PayPal 沙盒环境和生产环境?
通过使用不同的 API 端点(如 https://api-m.sandbox.paypal.com 用于沙盒,https://api-m.paypal.com 用于生产)以及相应的 Client ID 和 Secret Key 来区分。在开发和测试阶段,始终使用沙盒环境。
什么是 IPN,为什么它很重要?
IPN (Instant Payment Notification) 是 PayPal 向您的服务器发送的异步通知,告知您交易状态的实时更新。它非常重要,因为它确保即使在用户浏览器关闭或网络中断的情况下,您的系统也能可靠地接收并处理支付结果,从而避免订单状态不同步的问题。
是否可以使用旧的 PayPal Payments Standard 或 PayPal Pro 进行集成?
虽然这些旧版集成仍然可用,但 PayPal 推荐使用新的 PayPal Checkout(智能按钮)或更高级的解决方案(如 Braintree 或新的 Checkout/Orders API)。旧版集成可能在功能、安全性和用户体验方面有所欠缺,且可能面临未来弃用的风险。
如何处理退款?
退款操作通常通过 PayPal REST API 发起。您需要调用 PayPal 的退款 API,并提供原始交易 ID 和退款金额。后端 PHP 可以集成此功能,允许管理员从您的系统中发起退款。

结论

通过 PHP 后端集成 PayPal 支付是一项涉及前端用户体验、后端 API 交互、数据存储和安全考量的多方面任务。遵循 PayPal 推荐的集成方式(如 PayPal Checkout 和 REST API),并严格遵守安全最佳实践,可以构建一个可靠、高效且用户友好的支付系统。沙盒环境的充分测试以及 IPN/Webhook 的异步处理是确保集成成功的关键。掌握这些核心概念和步骤,您将能够为您的 PHP 应用程序无缝添加 PayPal 支付功能。


推荐阅读


搜索结果

AI-assisted research. Sources, methodology and corrections · Source-checked guides and tools · Explore published answers

Ask Ithy AI