PHP项目支付回调验签步骤

wen PHP项目 2

本文目录导读:

PHP项目支付回调验签步骤

  1. 通用验签核心逻辑
  2. 具体实现案例
  3. 关键注意事项(易错点)
  4. 何时使用 SDK?

在PHP项目中处理支付回调验签,核心目的是验证收到的回调通知确实来自支付平台(如微信、支付宝),且数据未被篡改

以下是通用且标准的验签步骤,并以支付宝微信支付为例说明具体实现。


通用验签核心逻辑

无论哪种支付平台,验签的核心流程都是:

  1. 接收数据:获取支付平台POST过来的参数(通常是 $_POST 或原始输入流 php://input)。
  2. 去除签名本身:从参数中取出签名值(通常是 signsign_type 字段),剩下的参数用于验签。
  3. 排序并拼接字符串:将剩余参数按照一定的规则(通常是ASCII码升序或指定顺序)拼接成待签名字符串。
  4. 获取平台公钥:使用支付平台提供的公钥(不是商户私钥)。
  5. 验证签名:使用对应的加密算法(如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>';
}
?>

关键注意事项(易错点)

  1. 参数过滤

    • 支付宝:需要过滤掉 signsign_type,且拼接时过滤掉空值。
    • 微信:需要过滤掉 sign,空值不参与签名。
  2. 编码问题

    拼接前请确保所有字段是 UTF-8 编码,避免中文或其他字符导致签名不一致。

  3. 微信 API 证书 vs 商户密钥

    • 微信验签用的是商户API密钥(32位字符串),不是商户证书。
    • 微信退款、转账等操作才需要用到证书(apiclient_cert.pem)。
  4. 重复通知处理

    • 支付平台可能会多次回调,必须确保业务处理逻辑是幂等的(例如通过订单号+状态判断是否已处理)。
  5. 金额比对

    • 验签通过后,一定还要比对 total_fee(订单金额)和 out_trade_no(商户订单号)是否和数据库一致,防止恶意伪造订单。
  6. 响应格式

    • 支付宝:成功输出 success(无空格),失败输出 fail
    • 微信:成功输出 <xml><return_code><![CDATA[SUCCESS]]></return_code></xml>

何时使用 SDK?

对于生产环境,推荐直接使用官方 SDK(如 Alipay SDK for PHPWeChatPay SDK),它们会自动处理以下细节:

  • 签名算法选择(RSA1 vs RSA2、MD5 vs SHA256)
  • 参数排序规则(不同版本可能有变化)
  • Base64 编码/解码处理
// 支付宝 SDK 示例
use Alipay\EasySDK\Kernel\Factory;
Factory::setOptions($config);
$response = Factory::payment()->common()->verifyNotify($_POST); // 直接返回bool

但理解底层原理(即上述通用步骤)对于排查坑点、自定义处理非常有用。

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