PHP支付宝异步通知验签

wen PHP项目 6

本文目录导读:

PHP支付宝异步通知验签

  1. 核心概念
  2. 方法一:使用官方 SDK(推荐,最简单)
  3. 方法二:原生 PHP 手动验签(无 SDK 依赖)
  4. 关键安全校验清单(业务层面)
  5. 常见踩坑点(高频问题)

在 PHP 中处理支付宝异步通知(Asynchronous Notification)的验签,是接入支付宝支付后最关键的环节,只有验签通过,才能确认该通知是支付宝官方发送的,防止伪造。

以下是使用官方 SDK原生代码两种最常用的验签实现方法。


核心概念

支付宝异步通知使用的是 RSA2(SHA256withRSA) 签名算法(推荐)或 RSA。 验签需要用到支付宝公钥(是支付宝平台上的公钥,不是应用私钥)。

流程梳理:

  1. 接收支付宝 POST 过来的 $_POST 数据。
  2. 取出 signsign_type 字段。
  3. 将剩余参数(去除 signsign_type,且空值不参与)按照字典序排序,拼接成字符串。
  4. 使用支付宝公钥,通过 RSA2 算法验证签名是否一致。

使用官方 SDK(推荐,最简单)

支付宝官方提供了 alipay-sdk-php,如果项目已通过 Composer 安装,直接调用即可。

安装(如果还没装):

composer require alipay/alipay-sdk-php

异步通知处理代码:

<?php
// 引入自动加载文件
require_once 'vendor/autoload.php';
// 引入相关类(根据 SDK 版本不同,命名空间可能略有差异)
use Alipay\EasySDK\Kernel\Factory;
use Alipay\EasySDK\Kernel\Util\ResponseChecker;
use Alipay\EasySDK\Kernel\Config;
// 1. 获取支付宝 POST 过来的原始数据
$data = $_POST;
// 2. 初始化配置(填入你的配置)
$options = new Config();
$options->protocol = 'https';
$options->gatewayHost = 'openapi.alipay.com';
$options->signType = 'RSA2'; // 签名类型
$options->appId = '你的APPID';
$options->merchantPrivateKey = '你的应用私钥(原始字符串,不要PEM头)'; // 注意格式
// 关键:这里是支付宝公钥(不是应用公钥)
$options->alipayPublicKey = '你的支付宝公钥(原始字符串,不要PEM头)';
Factory::setOptions($options);
try {
    // 3. 验签(SDK内部封装了排序和验签逻辑)
    // 注意:SDK的 verify 方法需要传入未解码的原始数组
    $result = Factory::payment()->common()->verifyNotify($data);
    if ($result === true) { // 验签成功
        // 打印日志记录
        file_put_contents('notify_log.txt', json_encode($data) . PHP_EOL, FILE_APPEND);
        // --- 业务处理 ---
        // 1. 检查商户订单号 out_trade_no
        // 2. 检查交易状态 trade_status 是否为 TRADE_SUCCESS
        // 3. 检查金额 total_amount 是否与订单一致
        // 4. 检查 AppID 是否匹配
        // 幂等性处理:检查该订单是否已处理,避免重复发货
        // ... 你的业务逻辑 ...
        // 4. 验签成功且业务处理完成,必须原样输出 "success"(不要输出其他内容)
        echo 'success';
    } else {
        // 验签失败
        echo 'fail';
        // 记录日志
    }
} catch (\Exception $e) {
    // 异常处理
    file_put_contents('notify_error.log', $e->getMessage() . PHP_EOL, FILE_APPEND);
    echo 'fail';
}

原生 PHP 手动验签(无 SDK 依赖)

如果你不想引入庞大的 SDK,或者需要底层逻辑,可以手动实现。

<?php
// 手动验签函数
function verifyAlipayNotify($data, $alipayPublicKey)
{
    // 1. 剔除 sign 和 sign_type
    $sign = $data['sign'] ?? '';
    unset($data['sign']);
    unset($data['sign_type']);
    // 2. 去除空值(如果值为空字符串或 null,不参与签名)
    $filteredData = array_filter($data, function ($value) {
        return $value !== '' && $value !== null;
    });
    // 3. 按照字典序排序(根据键名 ASCII 码升序排列)
    ksort($filteredData);
    // 4. 拼接成字符串  key1=value1&key2=value2
    $signStr = urldecode(http_build_query($filteredData));
    // 5. 验证签名
    // 注意:支付宝公钥可以是PEM格式,也可以是一行字符串,这里需要拼接成PEM
    $publicKey = "-----BEGIN PUBLIC KEY-----\n" . wordwrap($alipayPublicKey, 64, "\n", true) . "\n-----END PUBLIC KEY-----";
    $res = openssl_get_publickey($publicKey);
    if (!$res) {
        return false; // 公钥格式错误
    }
    // 使用 OPENSSL_ALGO_SHA256 (RSA2)
    $result = openssl_verify($signStr, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
    // 释放资源
    openssl_free_key($res);
    return $result === 1; // 1 表示验证成功
}
// --- 使用示例 ---
$data = $_POST;
// 这里填入你的支付宝公钥(在支付宝开放平台获取)
$alipayPublicKey = '你的支付宝公钥字符串';
if (verifyAlipayNotify($data, $alipayPublicKey)) {
    // 验签通过
    // 处理业务逻辑...
    // 注意:需要再次校验业务参数
    // 1. app_id 是否是你自己的
    // 2. out_trade_no 是否存在
    // 3. total_amount 是否匹配
    // 4. seller_id 是否匹配
    echo 'success';
} else {
    echo 'fail';
}

关键安全校验清单(业务层面)

验签只是第一步,为了防止海豚攻击(重放攻击)和逻辑漏洞,必须再检查以下内容:

  1. app_id:必须等于你自己应用的 AppID。
  2. out_trade_no:必须是系统中存在的订单号。
  3. total_amount:必须与你数据库中该订单的金额完全一致(注意浮点数比较,建议使用字符串比较或 bccomp)。
  4. seller_id:必须是你自己的支付宝账号对应的 ID。
  5. trade_status:只有当值为 TRADE_SUCCESS 时,才进行财务入账操作,其他状态(如 WAIT_BUYER_PAY)忽略。

常见踩坑点(高频问题)

  1. 公钥填错了:在支付宝后台,你需要使用的是 “支付宝公钥”,不要填成“应用公钥”或“应用私钥”。
  2. 空值参与签名array_filter 去掉空值是必须的,否则验签失败。
  3. 解码问题:如果使用 http_build_query,注意该函数默认会进行 urlencode,但在手动拼接时需要确保没有转义特殊字符,推荐使用原生的拼接方式或 urldecode 包裹。
  4. 返回字符串:验签并处理成功后,必须输出一个独立的单词 success(不要带引号、空格或换行——有些服务器会自动加换行,通常没问题,但建议不要额外 echo 其他内容),如果输出其他内容,支付宝会认为处理失败,并在接下来的 24 小时内按一定频率重发通知(最多重发 8 次)。
  5. 日志记录:强烈建议将 $_POST 的原始数据记录到日志文件,方便排查问题,但注意不要记录 sign 字段泄露签名信息(或者可以记录,问题不大,但出于安全可打码)。

推荐使用官方 SDK 进行验签,因为官方 SDK 处理了:openssl 库的兼容性、公钥格式转换、字符编码等问题,手动验签虽然在上述代码中能跑通,但遇到环境差异(如 PHP 7.x 和 8.x 的 openssl_verify 行为差异,或者中文参数编码问题)时容易踩坑。

最终操作步骤:

  1. 拿到 $_POST 数据。
  2. 调用 SDK 的 verifyNotify(或手动验签)。
  3. true,再检查订单金额/状态/AppID。
  4. 处理完业务,输出 success

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