PHP项目怎么对接支付接口?

wen PHP项目 10

本文目录导读:

PHP项目怎么对接支付接口?

  1. 核心流程(以用户付款为例)
  2. 准备工作:获取商户密钥
  3. PHP 支付模块封装示例(模块化设计)
  4. 处理异步通知(最关键的环节)
  5. 安全与规范避坑指南
  6. 常见问题与排查

在 PHP 项目中对接支付接口,虽然具体 API 会因支付服务商(如微信支付、支付宝、PayPal、Stripe 等)而异,但核心流程和逻辑是通用的

下面是一个标准的、模块化的对接指南,适合大多数 PHP 项目(无论是原生 PHP 还是 ThinkPHP、Laravel 等框架)。

核心流程(以用户付款为例)

  1. 用户在前端点击“支付”按钮。
  2. 前端请求后端 PHP(生成订单,获取支付参数)。
  3. 后端 PHP 处理逻辑:
    • 校验订单(金额、库存、状态)。
    • 生成订单记录(状态:未支付)。
    • 调用支付服务商 SDK,生成支付参数(如二维码链接、支付令牌、Form 表单)。
  4. PHP 返回支付参数给前端
  5. 前端调起支付组件(跳转支付页面、弹出二维码、调用 App 支付)。
  6. 用户完成支付。
  7. 支付服务商异步通知 PHP 后端最重要的一步)。
  8. PHP 后端验证签名 -> 更新订单状态为“已支付” -> 返回成功标识给支付服务商。

准备工作:获取商户密钥

在每个支付平台(支付宝/微信开放平台等)都需要完成:

  1. 注册商户账号。
  2. 创建应用,获取 AppID
  3. 配置密钥:
    • 支付宝:配置 RSA2 公钥/私钥(推荐使用沙箱环境测试)。
    • 微信支付:获取商户号 MchIDAPIv2APIv3 密钥,下载证书文件。
  4. 设置回调地址(Notify URL),也就是你的服务器接收支付结果通知的 URL。

PHP 支付模块封装示例(模块化设计)

建议创建一个 PaymentService 类,而不是把逻辑散落在各处。

依赖管理(强烈建议使用 Composer)

# 支付宝官方 SDK
composer require alipay/alipay-sdk-php
# 微信支付官方 SDK (推荐使用 APIv3)
composer require wechatpay/wechatpay-guzzle-middleware
# 通用支付网关 (如果你的项目对接多家)
# composer require hprose/hprose-php  # 或其他

支付服务类骨架 (PaymentService.php)

<?php
namespace App\Services;
use Alipay\EasySDK\Kernel\Factory;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use GuzzleHttp\Client;
class PaymentService
{
    // 支付宝配置
    private $alipayConfig;
    // 微信支付配置
    private $wechatConfig;
    public function __construct()
    {
        // 通常从 .env 文件读取,不要写死
        $this->alipayConfig = [
            'app_id' => env('ALIPAY_APP_ID'),
            'alipay_public_key' => env('ALIPAY_PUBLIC_KEY'),
            'merchant_private_key' => env('ALIPAY_MERCHANT_PRIVATE_KEY'),
            'notify_url' => env('APP_URL') . '/api/payment/alipay/notify',
        ];
        $this->wechatConfig = [
            'mchid' => env('WECHAT_MCHID'),
            'serial' => env('WECHAT_SERIAL'),
            'private_key_path' => env('WECHAT_PRIVATE_KEY_PATH'),
            'apiv3_key' => env('WECHAT_API_V3_KEY'),
        ];
    }
    // 支付宝 - 手机网站/PC网站支付
    public function alipayPagePay($orderId, $amount, $subject)
    {
        // 1. 初始化 SDK
        Factory::setOptions($this->alipayConfig);
        // 2. 调用 API 生成支付页面 form 或 URL
        $result = Factory::payment()->page()->pay(
            $subject,                    // 订单标题
            $orderId,                    // 商户订单号
            $amount,                     // 金额 (单位: 元)
            $this->alipayConfig['notify_url']   // 回调地址
        );
        // 3. 返回给前端的数据(一个自动提交的 Form 表单或者跳转链接)
        // 对于 PC 网站,一般是返回 HTML 代码
        return response($result->body);
        // 对于小程序/APP,会返回 trade_no (交易号)
    }
    // 微信支付 - JSAPI (H5/小程序) 或 Native (扫码)
    public function wechatNativePay($orderId, $amount, $description)
    {
        // 1. 构建支付参数
        $params = [
            'mchid' => $this->wechatConfig['mchid'],
            'out_trade_no' => $orderId,
            'appid' => env('WECHAT_APPID'),
            'description' => $description,
            'notify_url' => env('APP_URL') . '/api/payment/wechat/notify',
            'amount' => [
                'total' => (int)($amount * 100), // 微信金额单位为分
                'currency' => 'CNY'
            ],
        ];
        // 2. 发起 HTTP 请求 (使用官方 SDK 或 Guzzle)
        // ... 这里省略具体签名和请求代码
        // 3. 获取返回的 code_url(二维码链接)
        $codeUrl = $response['code_url'];
        // 4. 生成二维码,或直接返回链接给前端
        return [
            'code_url' => $codeUrl,
            'out_trade_no' => $orderId,
        ];
    }
    // 统一下单入口 (根据前端传递的 channel)
    public function unifyOrder($channel, $orderId, $amount, $subject)
    {
        switch ($channel) {
            case 'alipay_wap':
                return $this->alipayPagePay($orderId, $amount, $subject);
            case 'wechat_native':
                return $this->wechatNativePay($orderId, $amount, $subject);
            default:
                throw new \Exception("不支持的支付方式");
        }
    }
}

处理异步通知(最关键的环节)

支付成功后的回调必须由服务端接收,因为这是安全的(通过签名验证)。绝对不能只依赖前端跳转。

支付宝异步通知示例 (alipayNotify)

支付宝会 POST 一个表单到你的 notify_url

// 路由: POST /api/payment/alipay/notify
public function alipayNotify(Request $request)
{
    $postData = $request->post(); // 支付宝发来的数据
    // 1. 签名验证 (必须做! 防止伪造通知)
    Factory::setOptions($this->alipayConfig);
    $isVerify = Factory::payment()->common()->verifyNotify($postData);
    if (!$isVerify) {
        // 签名失败,记录日志
        return 'fail'; // 必须返回 fail,以便支付宝重发
    }
    // 2. 验证业务参数
    $outTradeNo = $postData['out_trade_no']; // 我们的订单号
    $tradeNo = $postData['trade_no'];        // 支付宝交易号
    $totalAmount = $postData['total_amount'];
    $tradeStatus = $postData['trade_status'];
    // 3. 只处理交易成功状态
    if ($tradeStatus === 'TRADE_SUCCESS' || $tradeStatus === 'TRADE_FINISHED') {
        // 4. 查询本地订单,防止重复回调
        $order = OrderModel::where('order_no', $outTradeNo)->first();
        // 重要: 检查订单状态,防止重复更新
        if (!$order || $order->status === 'paid') {
            return 'success'; // 已处理,直接返回成功
        }
        // 重要: 验证金额是否一致 (防止坏人修改金额)
        if (abs(floatval($order->total_amount) - floatval($totalAmount)) > 0.01) {
            // 金额不匹配,记录日志并报警
            return 'fail';
        }
        // 5. 更新订单状态 (使用事务或锁)
        \DB::transaction(function () use ($order, $tradeNo) {
            $order->status = 'paid';
            $order->transaction_id = $tradeNo;
            $order->paid_at = now();
            $order->save();
            // 6. 触发后续业务逻辑 ( 给用户加积分, 发短信, 更新库存)
            event(new OrderPaid($order));
        });
        // 7. 返回成功字符串(必须是 success,不能是其他)
        return 'success';
    }
    // 其他状态,暂时忽略,但返回 success 防止重试
    return 'success';
}

微信支付异步通知示例 (wechatNotify)

微信的通知是加密的 XMLJSON(APIv3 是 JSON)。

// 路由: POST /api/payment/wechat/notify
public function wechatNotify(Request $request)
{
    // 微信 APIv3 通知是 JSON 格式, 且 body 是加密的
    // 1. 需要先解密 (使用 APIv3 密钥)
    // $decrypted = WechatPayDecrypt::decrypt($request->getContent(), $this->wechatConfig['apiv3_key']);
    // 2. 验证签名 (微信平台证书签名)
    // $isValid = WechatPayVerify::verify(...);
    // 3. 解析数据
    // $resource = $decrypted['resource'];
    // $outTradeNo = $resource['out_trade_no'];
    // $transactionId = $resource['transaction_id'];
    // $tradeSuccess = $resource['trade_state'] === 'SUCCESS';
    // 4. 业务逻辑 (同支付宝,检查订单、更新状态)
    if ($tradeSuccess) {
        // ... 更新订单逻辑 ...
        // 更新库存等
    }
    // 5. 返回给微信: 必须返回 HTTP 200 和 特定格式的 JSON
    return response()->json([
        'code' => 'SUCCESS',
        'message' => '成功'
    ]);
    // 如果返回错误,微信会重试 3-7 天
}

安全与规范避坑指南

  1. 绝对不能在前端拼接支付逻辑! 所有请求支付服务商的动作必须在后端完成,前端只负责展示后端返回的结果并调起支付组件。
  2. 校验金额:在异步通知中,必须拿回调中的 total_amount 与你数据库中的 order.total_amount 做比对,防止被篡改。
  3. 幂等性处理:支付回调可能会重复收到(网络抖动、重试机制),务必判断订单是否已经是 paid 状态,如果已支付则直接 return 'success' 并跳出,不要重复发积分或发货。
  4. 签名验证:支付回调是唯一可信的通知方式,必须验证签名,验证通过后才更新数据库。
  5. 日志记录:记录原始回调数据 $request->all() 和签名验证结果,方便以后排查问题。
  6. 订单号唯一性:使用 uniqid + recorder_id 或 UUID 生成订单号,防止重复调用。
  7. 使用 HTTPS:前后端通信、支付回调地址必须使用 HTTPS,防止中间人攻击。
  8. 备好退款接口:支付功能上线前,建议先写好退款逻辑(PHP 调用服务商退款 API)。

常见问题与排查

  • 验签失败:大概率是密钥配置不正确,或公钥/私钥搞反了,重新检查支付宝或微信的密钥设置页面。
  • 收不到回调
    • 检查服务器防火墙,确保端口 80/443 开放。
    • 检查 notify_url 是否能被外网访问,且不要有 等参数(支付服务商会覆盖)。
    • 检查代码是否返回了正确的 success(支付宝)或 {"code":"SUCCESS"}(微信),如果是 fail 会被重试但仍可能失败。
  • 重复通知:正常现象,支付服务商会自动重试,你的代码必须能处理同一个通知多次执行,并且结果一致(幂等)。

如果项目对安全性要求较高,或希望快速接入多种支付方式,推荐使用 Laravel 的 laravel/cashierYansongda/pay 这类成熟的 Composer 包,它们封装了底层的签名、解密和很多安全细节。

抱歉,评论功能暂时关闭!