PHP项目如何实现支付回调处理?

wen PHP项目 4

本文目录导读:

PHP项目如何实现支付回调处理?

  1. 核心原则(必读)
  2. 方案一:通用支付回调处理流程(伪代码,适用于大多数场景)
  3. 方案二:基于SDK的推荐实现(更安全、更简洁)
  4. 关键注意事项
  5. 测试建议

实现PHP项目的支付回调处理,核心在于验证签名处理幂等性修改订单状态,不同支付平台(支付宝、微信支付)的细节略有不同,但整体流程一致。

下面是一个通用的、安全的PHP支付回调处理方案,以支付宝和微信支付为例,并包含关键注意事项。


核心原则(必读)

  1. 验签是第一步:必须验证回调通知是否来自支付平台,防止伪造请求。
  2. 幂等性设计(最重要):防止因网络重试导致同一订单被多次处理(多次发货、多次加款),通常使用订单号+交易状态作为唯一处理依据。
  3. 先验签,后处理业务:验签失败直接返回失败信息,不要执行任何业务逻辑。
  4. 处理成功需返回成功标识:支付宝返回success,微信返回SUCCESS(或<xml>...</xml>),否则支付平台会持续重试(通常3-7天)。

通用支付回调处理流程(伪代码,适用于大多数场景)

<?php
// 1. 接收支付平台的回调数据
// 支付宝:$_POST (同步) 或 file_get_contents('php://input') (异步)
// 微信支付: file_get_contents('php://input') 获取XML
$rawData = file_get_contents('php://input');
// 2. 解析数据
// 支付宝:直接是 $_POST 数组
// 微信支付:需要解析XML
$data = parseCallbackData($rawData); // 假设此函数已实现
// 3. 验签(最关键的步骤)
$isValid = verifySign($data, $paymentType); // 支付宝/微信的签名验证
if (!$isValid) {
    // 记录日志:签名验证失败
    error_log("支付回调签名验证失败: " . json_encode($data));
    // 返回失败标识
    echo $paymentType == 'alipay' ? 'failure' : '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名验证失败]]></return_msg></xml>';
    exit;
}
// 4. 获取核心参数
$outTradeNo = $data['out_trade_no']; // 商户订单号
$tradeNo = $data['trade_no'];       // 支付平台交易号(支付宝/微信)
$totalAmount = $data['total_amount']; // 支付金额(单位:元)
$tradeStatus = $data['trade_status']; // 交易状态
// 5. 幂等性处理(防止重复回调)
// 使用数据库锁或订单状态检查
// 启动数据库事务
$db->beginTransaction();
try {
    // 查询数据库中的订单
    $order = $db->query("SELECT * FROM orders WHERE order_no = ?", [$outTradeNo]);
    if (!$order) {
        throw new Exception("订单不存在");
    }
    // 关键:检查订单是否已经处理过
    if ($order['status'] == 'paid') {
        // 订单已支付成功,直接返回成功(幂等)
        $db->commit();
        echo $paymentType == 'alipay' ? 'success' : '<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>';
        exit;
    }
    // 6. 验证金额是否一致(防止篡改)
    // 数据库中的订单金额(单位:分或元,需统一)
    $orderAmount = $order['amount']; // 假设数据库存的是元
    if (abs(floatval($totalAmount) - floatval($orderAmount)) > 0.01) {
        throw new Exception("金额不匹配");
    }
    // 7. 验证交易状态
    // 支付宝:TRADE_SUCCESS
    // 微信:SUCCESS
    if ($tradeStatus != 'TRADE_SUCCESS' && $tradeStatus != 'SUCCESS') {
        // 未支付成功,记录但不报错
        // 支付宝其他状态如WAIT_BUYER_PAY是等待支付,不做处理
        if ($tradeStatus != 'WAIT_BUYER_PAY') {
            throw new Exception("非成功状态的回调: " . $tradeStatus);
        }
        $db->commit();
        echo $paymentType == 'alipay' ? 'success' : '<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>';
        exit;
    }
    // 8. 业务逻辑处理(修改订单状态,更新库存/余额等)
    $updateResult = $db->execute("UPDATE orders SET status='paid', pay_time=NOW(), trade_no=? WHERE order_no=?", [$tradeNo, $outTradeNo]);
    if (!$updateResult) {
        throw new Exception("订单状态更新失败");
    }
    // 9. 其他业务逻辑(如:积分增加、虚拟商品发货等)
    // ...
    // 10. 提交事务
    $db->commit();
    // 11. 返回成功标识给支付平台
    echo $paymentType == 'alipay' ? 'success' : '<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>';
} catch (Exception $e) {
    // 回滚事务
    $db->rollBack();
    error_log("支付回调处理失败: " . $e->getMessage());
    // 返回失败标识,让支付平台重试
    echo $paymentType == 'alipay' ? 'failure' : '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[' . $e->getMessage() . ']]></return_msg></xml>';
}

基于SDK的推荐实现(更安全、更简洁)

绝大多数情况推荐使用官方SDK,因为签名算法复杂且经常更新。

支付宝回调(使用官方SDK)

require_once 'vendor/autoload.php'; // 引入composer自动加载
use Alipay\EasySDK\Kernel\Factory;
use Alipay\EasySDK\Kernel\Util\ResponseChecker;
use Alipay\EasySDK\Kernel\Config;
// 配置(通常在项目初始化时配置一次)
$options = new Config();
$options->protocol = 'https';
$options->gatewayHost = 'openapi.alipay.com'; // 或沙箱地址
$options->signType = 'RSA2';
$options->appId = '你的APPID';
$options->merchantPrivateKey = '你的商户私钥(PEM格式)';
$options->alipayPublicKey = '支付宝公钥(PEM格式)';
Factory::setOptions($options);
// 处理异步通知
$result = Factory::payment()->common()->verifyNotify($_POST); // 验证签名
if ($result) {
    // 签名验证通过
    $outTradeNo = $_POST['out_trade_no'];
    $tradeNo = $_POST['trade_no'];
    $totalAmount = $_POST['total_amount'];
    // 幂等性和金额验证(同上方案一中的步骤5-8)
    // ...
    // 处理成功
    echo 'success';
} else {
    // 验签失败
    echo 'failure';
}

微信支付回调(使用官方SDK)

require_once 'vendor/autoload.php';
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use Psr\Http\Message\ResponseInterface;
// 配置(通常在项目初始化时)
$merchantId = '你的商户号';
$merchantSerialNumber = '你的商户证书序列号';
$merchantPrivateKey = '你的商户私钥(PEM格式)';
$wechatpayCertificate = '微信支付平台证书(PEM格式)';
// 创建实例
$instance = Builder::factory([
    'mchid' => $merchantId,
    'serial' => $merchantSerialNumber,
    'privateKey' => $merchantPrivateKey,
    'certs' => [$wechatpayCertificate],
]);
// 处理回调
$inWechatpaySignature = $_SERVER['HTTP_WECHATPAY_SIGNATURE'];
$inWechatpayTimestamp = $_SERVER['HTTP_WECHATPAY_TIMESTAMP'];
$inWechatpaySerial = $_SERVER['HTTP_WECHATPAY_SERIAL'];
$inWechatpayNonce = $_SERVER['HTTP_WECHATPAY_NONCE'];
$inbody = file_get_contents('php://input');
// 1. 验签
$verified = $instance->get('v3/certificates')->verify($inWechatpaySerial, $inWechatpaySignature, $inbody, $inWechatpayNonce, $inWechatpayTimestamp);
if (!$verified) {
    // 验签失败
    echo '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名验证失败]]></return_msg></xml>';
    exit;
}
// 2. 解析body
$data = json_decode($inbody, true);
$resource = $data['resource'];
$ciphertext = $resource['ciphertext'];
$associatedData = $resource['associated_data'];
$nonce = $resource['nonce'];
// 3. 解密数据(微信支付回调内容对称加密)
$decrypted = Rsa::decrypt($ciphertext, $associatedData, $nonce, $merchantPrivateKey);
// $decrypted 现在是明文的JSON字符串
$paymentData = json_decode($decrypted, true);
$outTradeNo = $paymentData['out_trade_no'];
$tradeNo = $paymentData['transaction_id'];
$totalAmount = $paymentData['amount']['total'] / 100; // 微信单位是分,转为元
$tradeStatus = $paymentData['trade_state']; // 应为 'SUCCESS'
// 4. 幂等性、金额验证(同上)
// ...
echo '<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>';

关键注意事项

  1. 日志记录:所有回调数据(原始数据+处理后数据)必须记录到日志,便于排查问题。
  2. 异步与同步
    • 异步通知(最重要):由支付平台服务器发起的,必须在php://input读取。
    • 同步通知(Return URL):用户支付完成后浏览器跳转到的页面,不能作为最终支付成功的依据(用户可能关闭页面或掉线),同步通知可作为“支付中”的页面展示,但业务处理依赖异步通知。
  3. 服务器时间:回调中常会验证notify_timetime_expire,确保服务器时间准确(使用NTP同步)。
  4. 超时设置:回调脚本不要有set_time_limit(0)等阻塞操作,应快速返回success,对于耗时业务(如发货),建议返回成功给支付平台后,再异步处理(通过消息队列、定时任务或单独进程)。
  5. 签名算法版本:确保你的SDK或实现代码与支付平台最新签名规则一致(例如支付宝RSA2)。
  6. 金额单位:支付宝金额是(浮点),微信支付金额是(整数),处理时务必统一单位比较,使用intbccomp(PHP高精度计算函数)比较金额,避免浮点数误差。
  7. 退款回调:同样需要验签和幂等处理,但逻辑是退款的业务处理。

测试建议

  1. 使用沙箱环境:支付宝、微信都有沙箱(测试)环境,使用测试账号免费调试。
  2. 本地调试:使用 ngrokfrp 将公网地址映射到本地,方便断点调试回调代码。
  3. 模拟回调:很多支付平台支持在后台手动触发回调(模拟通知),用于测试幂等性。

最核心的三步:验签 -> 幂等性检查 -> 修改订单状态。 千万不要为了省事跳过验签或幂等性检查。

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