本文目录导读:

在PHP项目中处理支付回调验签,核心目的是验证收到的回调通知确实来自支付平台(如微信、支付宝),且数据未被篡改。
以下是通用且标准的验签步骤,并以支付宝和微信支付为例说明具体实现。
通用验签核心逻辑
无论哪种支付平台,验签的核心流程都是:
- 接收数据:获取支付平台POST过来的参数(通常是
$_POST或原始输入流php://input)。 - 去除签名本身:从参数中取出签名值(通常是
sign或sign_type字段),剩下的参数用于验签。 - 排序并拼接字符串:将剩余参数按照一定的规则(通常是ASCII码升序或指定顺序)拼接成待签名字符串。
- 获取平台公钥:使用支付平台提供的公钥(不是商户私钥)。
- 验证签名:使用对应的加密算法(如RSA2、MD5)对拼接好的字符串和签名值进行比对验证。
具体实现案例
案例1:支付宝回调验签(RSA2加密)
支付宝的回调数据通常通过 $_POST 直接传入。
<?php
/**
* 支付宝异步通知验签
*/
// 1. 获取支付宝回调的所有参数
$params = $_POST;
// 2. 获取签名和签名类型,并从参数中移除它们
$sign = $params['sign'] ?? '';
$signType = $params['sign_type'] ?? '';
unset($params['sign'], $params['sign_type']);
// 3. 对待验签参数进行排序并拼接
// 注意:支付宝要求按照字段名的ASCII码从小到大排序
ksort($params);
$stringToSign = '';
foreach ($params as $key => $value) {
// 注意:需要过滤掉空值、数组等(支付宝通常传递字符串)
if ($value !== '' && !is_array($value)) {
$stringToSign .= $key . '=' . $value . '&';
}
}
// 去掉最后一个多余的 & 符号
$stringToSign = rtrim($stringToSign, '&');
// 4. 获取支付宝公钥(从配置文件或数据库中读取)
$alipayPublicKey = file_get_contents('/path/to/alipay_public_key.pem'); // 或直接定义字符串
// 5. 使用 openssl 验证签名
// 支付宝签名算法通常是 RSA2(即 SHA256withRSA)
$result = openssl_verify(
$stringToSign,
base64_decode($sign),
$alipayPublicKey,
OPENSSL_ALGO_SHA256 // RSA2 对应这个常量,如果是 RSA 则用 OPENSSL_ALGO_SHA1
);
if ($result) {
// 验签成功!更新订单状态(注意检查金额、订单号、app_id 等)
echo 'success'; // 必须返回 'success',否则支付宝会重复通知
} else {
// 验签失败,记录日志,返回失败信息
echo 'fail';
}
?>
案例2:微信支付回调验签(HMAC-SHA256 或 MD5)
微信支付的回调数据是 XML 格式,通过 php://input 获取,签名结果需要在本地重新计算后比对。
<?php
/**
* 微信支付异步通知验签
*/
// 1. 获取原始输入流(XML格式)
$inputXml = file_get_contents('php://input');
$data = simplexml_load_string($inputXml, 'SimpleXMLElement', LIBXML_NOCDATA);
$params = json_decode(json_encode($data), true);
// 2. 获取签名并移除
$sign = $params['sign'] ?? '';
unset($params['sign']);
// 3. 对待签名字符串进行排序并拼接
ksort($params);
$stringToSign = '';
foreach ($params as $key => $value) {
// 微信要求:参数值为空时不参与签名
if ($value !== '' && $key !== 'sign') {
$stringToSign .= $key . '=' . $value . '&';
}
}
// 拼接商户 API 密钥(注意:是API密钥,不是商户证书)
$apiKey = 'your_api_key_here'; // 在微信商户平台设置的32位密钥
$stringToSign .= 'key=' . $apiKey;
// 4. 根据签名类型生成签名
$signType = $params['sign_type'] ?? 'MD5';
if ($signType === 'HMAC-SHA256') {
$localSign = strtoupper(hash_hmac('sha256', $stringToSign, $apiKey));
} else {
// 默认 MD5
$localSign = strtoupper(md5($stringToSign));
}
// 5. 比对签名
if ($localSign === $sign) {
// 验签成功!处理业务逻辑(更新订单状态等)
// 注意:还需要检查返回的 result_code 和 return_code 是否为 SUCCESS
echo '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
} else {
// 验签失败
echo '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>';
}
?>
关键注意事项(易错点)
-
参数过滤:
- 支付宝:需要过滤掉
sign和sign_type,且拼接时过滤掉空值。 - 微信:需要过滤掉
sign,空值不参与签名。
- 支付宝:需要过滤掉
-
编码问题:
拼接前请确保所有字段是 UTF-8 编码,避免中文或其他字符导致签名不一致。
-
微信 API 证书 vs 商户密钥:
- 微信验签用的是商户API密钥(32位字符串),不是商户证书。
- 微信退款、转账等操作才需要用到证书(
apiclient_cert.pem)。
-
重复通知处理:
- 支付平台可能会多次回调,必须确保业务处理逻辑是幂等的(例如通过订单号+状态判断是否已处理)。
-
金额比对:
- 验签通过后,一定还要比对
total_fee(订单金额)和out_trade_no(商户订单号)是否和数据库一致,防止恶意伪造订单。
- 验签通过后,一定还要比对
-
响应格式:
- 支付宝:成功输出
success(无空格),失败输出fail。 - 微信:成功输出
<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>。
- 支付宝:成功输出
何时使用 SDK?
对于生产环境,推荐直接使用官方 SDK(如 Alipay SDK for PHP 和 WeChatPay SDK),它们会自动处理以下细节:
- 签名算法选择(RSA1 vs RSA2、MD5 vs SHA256)
- 参数排序规则(不同版本可能有变化)
- Base64 编码/解码处理
// 支付宝 SDK 示例 use Alipay\EasySDK\Kernel\Factory; Factory::setOptions($config); $response = Factory::payment()->common()->verifyNotify($_POST); // 直接返回bool
但理解底层原理(即上述通用步骤)对于排查坑点、自定义处理非常有用。