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

wen java案例 2

本文目录导读:

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

  1. 通用安全处理流程(关键)
  2. 支付宝回调处理(示例)
  3. 微信支付回调处理(示例)
  4. 幂等性处理(重点)
  5. 日志记录(调试必备)
  6. 常见问题
  7. 生产环境建议

在PHP项目中实现支付回调处理是一个常见需求,这里以支付宝微信支付为例,给出完整的安全处理流程和代码示例,核心原则是:验签、幂等、更新订单状态


通用安全处理流程(关键)

支付回调是异步通知,必须严格按以下步骤处理,否则极易出现安全漏洞或重复发货:

  1. 验证签名:必须使用官方SDK或验证方法确认回调数据来自支付宝/微信,而不是伪造的请求。
  2. 检查订单状态:查询本地数据库,确认该笔订单处于“未支付”状态,避免重复处理。
  3. 验证金额:对比回调中的支付金额与数据库中订单的应付金额是否一致。
  4. 更新订单状态:将订单标记为“已支付”,并存入支付宝/微信的交易流水号。
  5. 返回成功响应:必须向支付平台返回字符串 success(或特定格式),否则平台会持续重试通知(通常持续数小时)。

支付宝回调处理(示例)

使用支付宝官方SDK(alibabacloud/client 或旧版 aop)。

接收回调数据

// notify.php
<?php
require_once 'vendor/autoload.php';
// 使用支付宝官方SDK
use Alipay\EasySDK\Kernel\Factory;
use Alipay\EasySDK\Kernel\Config;
// 初始化配置(建议从配置文件读取)
$config = new Config();
$config->protocol = 'https';
$config->gatewayHost = 'openapi.alipay.com';
$config->appId = 'your_app_id';
$config->signType = 'RSA2';
$config->alipayPublicKey = '支付宝公钥字符串';
$config->merchantPrivateKey = '商户私钥字符串';
Factory::setOptions($config);
// 获取POST过来的通知参数(支付宝是POST请求)
$params = $_POST;
try {
    // 1. 验证签名
    $result = Factory::payment()->common()->verifyNotify($params);
    if (!$result) {
        http_response_code(400);
        echo 'fail'; // 支付宝收到fail会继续重试
        exit;
    }
    // 2. 获取关键参数
    $outTradeNo = $params['out_trade_no'];      // 商户订单号
    $tradeNo = $params['trade_no'];             // 支付宝交易号
    $totalAmount = $params['total_amount'];     // 支付金额
    $tradeStatus = $params['trade_status'];     // 交易状态
    // 3. 检查订单状态(从数据库查询)
    $order = getOrderByOutTradeNo($outTradeNo); // 自定义函数
    if (!$order) {
        // 订单不存在
        echo 'fail';
        exit;
    }
    if ($order['status'] == 'paid') {
        // 已经处理过,直接返回成功(幂等)
        echo 'success';
        exit;
    }
    // 4. 验证金额
    if (abs($totalAmount - $order['amount']) > 0.01) {
        // 金额不一致,可能存在风险
        echo 'fail';
        exit;
    }
    // 5. 只处理交易成功状态 (TRADE_SUCCESS 或 TRADE_FINISHED)
    if ($tradeStatus == 'TRADE_SUCCESS' || $tradeStatus == 'TRADE_FINISHED') {
        // 6. 更新数据库订单状态
        updateOrderStatus($outTradeNo, 'paid', $tradeNo); // 自定义函数
    }
    // 7. 返回成功
    echo 'success';
} catch (\Exception $e) {
    // 记录日志
    error_log('Alipay notify error: ' . $e->getMessage());
    echo 'fail';
}

重要提示

  • 支付宝异步通知只有 POST,同步通知(return_url)是 GET不要在同步通知里修改订单状态,因为同步通知不可靠(用户可能关闭页面)。
  • verifyNotify 方法已自动处理签名验证。

微信支付回调处理(示例)

微信支付回调使用XML格式,需要解析XML并验签(官方SDK已封装)。

使用官方SDK(推荐)

// wx_notify.php
<?php
require_once 'vendor/autoload.php';
use EasyWeChat\Factory;
$config = [
    'app_id' => 'your_app_id',
    'mch_id' => 'your_mch_id',
    'key'    => 'your_api_v3_key',   // 注意:微信支付V3使用APIv3密钥
    'cert_path' => 'path/to/cert.pem', // 证书路径
    'key_path'  => 'path/to/key.pem',
    'notify_url' => 'https://yourdomain.com/wx_notify.php',
];
$app = Factory::payment($config);
// 微信支付回调处理
$response = $app->handlePaidNotify(function ($message, $fail) {
    // 1. 从$message中获取数据(已经验签过)
    $outTradeNo = $message['out_trade_no'];  // 商户订单号
    $transactionId = $message['transaction_id']; // 微信支付订单号
    $totalFee = $message['total_fee'];       // 支付金额(单位:分)
    $openid = $message['openid'];
    // 2. 查询本地订单
    $order = getOrderByOutTradeNo($outTradeNo);
    if (!$order || $order['status'] == 'paid') {
        // 订单不存在或已支付,返回true告诉微信不再重复通知
        return true;
    }
    // 3. 验证金额(注意比较单位:微信传的是分)
    if ($totalFee != $order['amount'] * 100) {
        // 金额异常,记录日志
        return true; // 或者 return $fail('金额不一致');
    }
    // 4. 更新订单状态
    updateOrderStatus($outTradeNo, 'paid', $transactionId);
    // 5. 返回 true 表示处理成功
    return true;
});
// 输出响应给微信服务器
$response->send();

说明

  • handlePaidNotify 自动完成签名验证和XML解析。
  • 如果回调函数返回 true,SDK会自动返回成功给微信;返回 $fail('错误原因') 则微信会重试。
  • 务必注意金额单位:微信使用,数据库一般用,需要转换。

幂等性处理(重点)

支付平台可能会重复发送多次通知(尤其网络波动时),必须保证重复通知不会导致多次发货或多次入账。

推荐做法

  • 在更新订单状态时,使用 SQL 条件更新UPDATE orders SET status='paid' WHERE order_id=xxx AND status='unpaid',如果影响行数为0,说明已经处理过,直接返回成功。
  • 或为订单新增一个 notify_id 字段,用数据库唯一索引保证同一通知只处理一次。

日志记录(调试必备)

所有回调都应当记录完整日志,用于排查问题。

function logNotify($type, $params, $result) {
    $log = [
        'time' => date('Y-m-d H:i:s'),
        'type' => $type,
        'params' => json_encode($params),
        'result' => $result,
    ];
    file_put_contents('/var/log/payment_notify.log', json_encode($log) . PHP_EOL, FILE_APPEND);
}

常见问题

问题 解决方案
签名验证失败 检查支付宝公钥/微信API密钥是否正确,注意区分应用公钥和支付宝公钥
回调不触发 检查服务器外网可访问性、防火墙、nginx配置(不限制POST大小)
重复通知 确保订单状态更新的幂等性
金额比较错误 注意微信是分,支付宝是元;比较时允许微小浮点误差(如abs($a-$b) < 0.01

生产环境建议

  1. 使用队列:回调处理中如果有短信、邮件等耗时操作,应放入消息队列异步处理,避免回调超时。
  2. 配置安全:公钥/密钥存储在环境变量或配置文件中,不要硬编码。
  3. 测试环境:使用支付宝沙箱、微信沙箱测试回调。
  4. 通知重试:支付平台重试间隔为15s/3min/10min等,确保你的接口能快速响应(< 5s)。

如果需要更详细的某个步骤(如生成订单、验签源码),可以提供具体支付渠道和版本再展开说明。

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