本文目录导读:

在PHP项目中验证支付异步通知的真伪,核心方法是使用签名验证,以下是主流支付平台(支付宝、微信)的通用校验逻辑和代码示例。
通用校验流程
- 接收通知数据:获取POST请求中的参数
- 去除签名相关字段:去掉
sign和sign_type字段 - 按规则排序并拼接:将剩余参数按key排序后拼接成字符串
- 拼接密钥:在字符串后拼接商户密钥
- 生成签名:使用MD5或SHA256等算法计算签名
- 比对签名:比对生成的签名与支付平台传来的
sign值
支付宝异步通知校验
1 核心校验代码
<?php
/**
* 支付宝异步通知签名验证
* @param array $data 支付宝异步通知的所有参数
* @param string $alipayPublicKey 支付宝公钥
* @return bool
*/
function verifyAliPayNotify($data, $alipayPublicKey) {
// 1. 验证通知ID(可选,强烈建议)
if (!verifyNotifyId($data['notify_id'])) {
return false;
}
// 2. 签名验证
$sign = $data['sign'];
$signType = $data['sign_type'] ?? 'RSA2';
// 移除签名相关字段
unset($data['sign'], $data['sign_type']);
// 按key排序
ksort($data);
// 拼接参数
$toSign = '';
foreach ($data as $key => $value) {
if (!empty($value) && $value !== 'null') {
$toSign .= $key . '=' . $value . '&';
}
}
$toSign = rtrim($toSign, '&');
// 使用OpenSSL验证签名
$key = "-----BEGIN PUBLIC KEY-----\n" .
wordwrap($alipayPublicKey, 64, "\n", true) .
"\n-----END PUBLIC KEY-----";
$result = openssl_verify(
$toSign,
base64_decode($sign),
$key,
$signType === 'RSA2' ? OPENSSL_ALGO_SHA256 : OPENSSL_ALGO_SHA1
);
return $result === 1;
}
/**
* 验证notify_id(官方API验证通知是否合法)
*/
function verifyNotifyId($notifyId) {
// 调用支付宝验证接口
$apiUrl = 'https://mapi.alipay.com/gateway.do?service=notify_verify&partner=YOUR_PARTNER_ID¬ify_id=' . $notifyId;
$response = file_get_contents($apiUrl);
return $response === 'true';
}
2 完整处理流程
// 收到支付宝异步通知
$alipayData = $_POST;
$alipayPublicKey = '你的支付宝公钥';
// 1. 业务验证
if ($_POST['trade_status'] == 'TRADE_SUCCESS') {
// 2. 签名验证
if (verifyAliPayNotify($alipayData, $alipayPublicKey)) {
// 3. 验证商户订单号
$orderNo = $_POST['out_trade_no'];
$totalAmount = $_POST['total_amount'];
// 4. 验证金额(与数据库中的订单金额比对)
$order = getOrderByOrderNo($orderNo);
if ($order['amount'] == $totalAmount) {
// 5. 避免重复处理(幂等性)
if ($order['status'] == 'pending') {
// 更新订单状态为已支付
updateOrderStatus($orderNo, 'paid');
// 6. 返回成功标识
echo 'success';
}
}
}
}
// 验证失败
echo 'fail';
微信支付异步通知校验
1 核心校验代码
<?php
/**
* 微信支付异步通知签名验证
* @param string $xmlData 微信返回的原始XML数据
* @param string $apiKey 商户API密钥
* @return array|bool 成功返回解析后的数组,失败返回false
*/
function verifyWxPayNotify($xmlData, $apiKey) {
// 1. 解析XML
$data = xmlToArray($xmlData);
// 2. 验证返回码
if ($data['return_code'] !== 'SUCCESS') {
return false;
}
// 3. 签名验证
$sign = $data['sign'];
unset($data['sign']);
// 按key排序
ksort($data);
// 拼接字符串
$toSign = '';
foreach ($data as $key => $value) {
if ($value !== '' && !is_array($value)) {
$toSign .= $key . '=' . $value . '&';
}
}
$toSign .= 'key=' . $apiKey;
// 生成签名(MD5)
$generatedSign = strtoupper(md5($toSign));
// 比对签名
if ($sign !== $generatedSign) {
return false;
}
// 4. 验证业务结果
if ($data['result_code'] !== 'SUCCESS') {
return false;
}
return $data;
}
/**
* XML转数组
*/
function xmlToArray($xml) {
// 禁止外部实体加载,防止XXE攻击
libxml_disable_entity_loader(true);
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
}
2 完整处理流程
// 接收微信通知的原始数据
$xml = file_get_contents('php://input');
$apiKey = '你的商户API密钥';
// 验证签名并获取数据
$notifyData = verifyWxPayNotify($xml, $apiKey);
if ($notifyData !== false) {
// 获取订单信息
$orderNo = $notifyData['out_trade_no'];
$transactionId = $notifyData['transaction_id'];
$totalFee = $notifyData['total_fee']; // 单位:分
// 验证订单金额
$order = getOrderByOrderNo($orderNo);
if ($order['amount'] * 100 == $totalFee) { // 元转分
// 避免重复处理
if ($order['status'] == 'pending') {
updateOrderStatus($orderNo, 'paid');
// 返回成功响应
$response = array(
'return_code' => 'SUCCESS',
'return_msg' => 'OK'
);
}
}
} else {
// 验证失败
$response = array(
'return_code' => 'FAIL',
'return_msg' => '签名验证失败'
);
}
// 输出XML响应
header('Content-Type: text/xml');
echo arrayToXml($response);
安全注意事项
1 必须做的基础验证
// 1. IP白名单(可选但推荐)
$validIps = ['119.29.29.29', '121.43.18.1']; // 支付平台IP段
if (!in_array($_SERVER['REMOTE_ADDR'], $validIps)) {
// 记录日志并拒绝
}
// 2. 订单状态幂等性检查
$order = getOrder($orderNo);
if ($order['status'] !== 'pending') {
// 已处理过,直接返回success
echo 'success';
exit;
}
// 3. 金额比对
if (abs($actualAmount - $expectedAmount) > 0.01) { // 允许极小误差
// 金额异常,记录日志
}
2 常见攻击防范
// 1. 防止重放攻击
if (isset($_POST['notify_time'])) {
$notifyTime = strtotime($_POST['notify_time']);
if (time() - $notifyTime > 3600) { // 超过1小时的通知拒绝
die('fail');
}
}
// 2. 验证通知类型
if ($_POST['notify_type'] !== 'trade_status_sync') {
die('fail');
}
// 3. 验证app_id
if ($_POST['app_id'] !== '你的APPID') {
die('fail');
}
完整示例封装
<?php
class PaymentNotifyService {
private $config;
public function __construct($config) {
$this->config = $config;
}
/**
* 处理支付宝异步通知
*/
public function handleAlipayNotify() {
$data = $_POST;
// 1. 基础验证
if (!$this->basicCheck($data)) {
return 'fail';
}
// 2. 签名验证
if (!$this->verifyAlipaySign($data)) {
logError('支付宝签名验证失败', $data);
return 'fail';
}
// 3. 业务处理
return $this->processOrder($data);
}
/**
* 处理微信异步通知
*/
public function handleWxpayNotify() {
$xml = file_get_contents('php://input');
// 1. 解析和验证签名
$data = $this->verifyWxpaySign($xml);
if (!$data) {
return $this->xmlResponse('FAIL', '签名验证失败');
}
// 2. 业务处理
$result = $this->processWxOrder($data);
return $this->xmlResponse(
$result ? 'SUCCESS' : 'FAIL',
$result ? 'OK' : '处理失败'
);
}
private function basicCheck($data) {
// 检查必要字段
$required = ['out_trade_no', 'trade_no', 'total_amount'];
foreach ($required as $field) {
if (!isset($data[$field])) {
return false;
}
}
return true;
}
private function processOrder($data) {
$orderNo = $data['out_trade_no'];
// 加锁防止并发
$lock = new RedisLock('order_' . $orderNo);
if (!$lock->acquire()) {
return 'fail';
}
try {
// 业务逻辑...
$order = OrderModel::findByNo($orderNo);
if ($order && $order->status == 'pending') {
$order->status = 'paid';
$order->save();
}
return 'success';
} finally {
$lock->release();
}
}
}
- 核心原则:永远不要信任客户端传来的数据,所有验证基于签名和服务器端逻辑
- 签名验证:使用官方提供的SDK或按照文档严格实现签名算法
- 双重验证:签名验证 + 业务验证(金额、订单状态等)
- 幂等性:确保同一通知只处理一次
- 日志记录:详细记录所有通知数据和处理结果,便于排查问题
建议使用各支付平台官方提供的SDK,它们已经封装好了签名验证逻辑,能减少很多潜在问题。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播