本文目录导读:

- 数据准备(原始报文)
- 参数排序与拼接(核心环节)
- 平台公钥验证(关键安全点)
- 响应支付平台(业务逻辑)
- 完整示例(整合微信v2 + 支付成功逻辑)
- 关键注意事项(安全防护)
- 各平台差异速查表
- 推荐工具库(提高安全性)
在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 {
// 证书错误
}
⚠️ 重要安全建议:
- 使用平台公钥,不要使用商户自己的私钥验签。
- 公钥从支付平台官方下载,不要通过回调数据中传输的证书获取(防止中间人攻击)。
- 建议使用 cURL 或 Guzzle 等 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';
}
关键注意事项(安全防护)
- SSL 校验:回调请求应使用 HTTPS,并对证书进行校验。
- 幂等性:必须保证同一订单只处理一次(使用数据库唯一约束或 Redis 锁)。
- 时间戳校验:检查回调时间与服务器时间差是否在合理范围(如 5 分钟),防止重放攻击。
- 金额校验:将回调金额与订单金额比对(注意分/元单位换算)。
- 日志记录:记录原始请求和验签结果,便于排查问题。
- 超时限制:业务处理应尽快完成(建议 < 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 可以避免很多底层细节错误,但理解验签原理仍然是排查问题的关键技能。