PHP支付回调验签步骤

wen PHP项目 2

本文目录导读:

PHP支付回调验签步骤

  1. 数据准备(原始报文)
  2. 参数排序与拼接(核心环节)
  3. 平台公钥验证(关键安全点)
  4. 响应支付平台(业务逻辑)
  5. 完整示例(整合微信v2 + 支付成功逻辑)
  6. 关键注意事项(安全防护)
  7. 各平台差异速查表
  8. 推荐工具库(提高安全性)

在PHP中实现支付回调验签,主要涉及数据接收签名验证幂等性处理三个核心步骤,以下是通用且安全的实现流程(以微信支付/支付宝为例,原理相同):


数据准备(原始报文)

支付平台会以 POST 方式发送回调数据,你需要获取原始的请求体(注意:不是 $_POST,而是 php://input)。

// 获取原始请求体(字符串)
$rawContent = file_get_contents('php://input');
// 如果是JSON格式(微信支付v3、支付宝新接口)
$data = json_decode($rawContent, true);
// 如果是表单格式(传统支付宝/旧微信支付)
// $data = $_POST;

参数排序与拼接(核心环节)

无论哪种支付平台,验签的核心逻辑都是: 将收到的参数(除 sign 字段外)按照规则排序,拼接成字符串,然后使用平台公钥进行签名验证。

微信支付(v2 普通商户版)

// 1. 过滤掉空值和 sign 字段
$params = $data;
unset($params['sign']);
// 2. 按字段名 ASCII 码升序排序
ksort($params);
// 3. 拼接成 key=value&key2=value2 格式(URL编码)
$signStr = urldecode(http_build_query($params));
// 4. 拼接商户key(v2是使用MD5或HMAC-SHA256)
$signStr .= '&key=' . $merchantKey; // 商户平台设置的API密钥
// 5. 生成签名(MD5大写)
$calculatedSign = strtoupper(md5($signStr));

微信支付(v3 新版,推荐)

// 1. 获取请求头中的签名信息
$timestamp = $_SERVER['HTTP_WECHATPAY_TIMESTAMP'];
$nonce     = $_SERVER['HTTP_WECHATPAY_NONCE'];
$signature = $_SERVER['HTTP_WECHATPAY_SIGNATURE'];
$serialNo  = $_SERVER['HTTP_WECHATPAY_SERIAL'];
// 2. 构造验签串
$message = $timestamp . "\n" . $nonce . "\n" . $rawContent . "\n";
// 3. 使用微信支付平台公钥(证书中获取)进行RSA验签
$publicKey = openssl_pkey_get_public(file_get_contents('wechatpay_public.pem'));
$result = openssl_verify(
    $message,
    base64_decode($signature),
    $publicKey,
    OPENSSL_ALGO_SHA256
);

平台公钥验证(关键安全点)

使用支付平台提供的公钥证书(不是商户私钥!)进行 RSA 验证。

// 支付宝验签示例(RSA2)
$alipayPublicKey = file_get_contents('alipay_public_key.pem');
$res = openssl_verify(
    $signStr,                          // 待验证的签名原文
    base64_decode($data['sign']),      // 对方传来的签名(base64解码)
    $alipayPublicKey,
    OPENSSL_ALGO_SHA256
);
if ($res === 1) {
    // 验签成功
} elseif ($res === 0) {
    // 验签失败
} else {
    // 证书错误
}

⚠️ 重要安全建议:

  • 使用平台公钥,不要使用商户自己的私钥验签。
  • 公钥从支付平台官方下载,不要通过回调数据中传输的证书获取(防止中间人攻击)。
  • 建议使用 cURLGuzzle 等 HTTP 客户端,不要直接用 file_get_contents(存在 SSL 漏洞风险)。

响应支付平台(业务逻辑)

验签成功后,需要处理业务逻辑(更新订单状态、减库存等),然后回复支付平台成功,避免重复回调。

// 验签 + 业务处理成功后
http_response_code(200);
echo '{"code":"SUCCESS","message":"成功"}';  // 微信v3格式
// echo 'success';                         // 支付宝/微信v2格式
// 业务失败时
http_response_code(500);
echo '{"code":"FAIL","message":"处理失败"}';

完整示例(整合微信v2 + 支付成功逻辑)

public function handleWechatCallback()
{
    $rawContent = file_get_contents('php://input');
    $data = json_decode($rawContent, true);
    // 1. 验签
    $params = $data;
    unset($params['sign']);
    ksort($params);
    $signStr = urldecode(http_build_query($params));
    $signStr .= '&key=' . $this->merchantKey;
    $calculatedSign = strtoupper(md5($signStr));
    if ($calculatedSign !== $data['sign']) {
        http_response_code(400);
        return '验签失败';
    }
    // 2. 幂等性检查(防止重复通知)
    if (Order::where('order_no', $data['out_trade_no'])->where('status', 'paid')->exists()) {
        return 'success';  // 已处理过,直接返回成功
    }
    // 3. 业务处理(事务)
    DB::transaction(function() use ($data) {
        // 更新订单状态
        // 减库存等
    });
    // 4. 返回成功给微信
    return 'success';
}

关键注意事项(安全防护)

  1. SSL 校验:回调请求应使用 HTTPS,并对证书进行校验。
  2. 幂等性:必须保证同一订单只处理一次(使用数据库唯一约束或 Redis 锁)。
  3. 时间戳校验:检查回调时间与服务器时间差是否在合理范围(如 5 分钟),防止重放攻击。
  4. 金额校验:将回调金额与订单金额比对(注意分/元单位换算)。
  5. 日志记录:记录原始请求和验签结果,便于排查问题。
  6. 超时限制:业务处理应尽快完成(建议 < 5 秒),长时间处理可先返回成功,异步处理。

各平台差异速查表

平台 签名算法 签名参数 拼接格式 公钥来源
微信支付v2 MD5/HMAC-SHA256 所有非空参数(除 sign) a=1&b=2&key=商户key 自己生成商户key
微信支付v3 RSA-SHA256 时间戳+随机串+请求体 时间戳\n随机串\n请求体\n 微信支付平台证书
支付宝 RSA2 所有非空参数(除 sign) a=1&b=2 支付宝公钥
支付宝海外 RSA 同上 同上 支付宝公钥

推荐工具库(提高安全性)

use WechatPay\GuzzleMiddleware\WechatPayMiddleware;  // 微信官方SDK
// 或使用 EasyWeChat:https://easywechat.com/

使用官方 SDK 可以避免很多底层细节错误,但理解验签原理仍然是排查问题的关键技能。

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