必须用 file_get_contents("php://input") 原始读取 POST 数据,因 $_POST 可能为空或截断;验签前需 htmlspecialchars_decode() 解码 fund_bill_list、保留下划线参数名、使用正确 RSA2 公钥;验签通过后立即 echo 'success' 并 exit,业务逻辑异步处理。

怎么正确接收支付宝异步通知的 POST 数据
支付宝异步通知只走 POST,且**不保证参数能被 $_POST 完整捕获**——尤其当含特殊字符(如 fund_bill_list 里的 JSON 字符串)或服务器启用了某些编码过滤时,$_POST 可能为空或被截断。
- 必须优先用
file_get_contents("php://input")原始读取请求体,再手动解析 - 若用框架(如 ThinkPHP5/6),避免直接调
$this->request->post(),应改用$this->request->getRawInput()或等效方法 - 收到后立刻
file_put_contents('notify.log', print_r($raw, true), FILE_APPEND)记日志,别等出问题再翻
验签失败的三个高频原因和修复方式
验签失败不是“公钥填错了”这么简单,多数卡在参数预处理环节。支付宝要求验签前必须还原原始语义,否则 rsaCheckV1() 必然返回 false。
-
fund_bill_list是 HTML 实体编码过的 JSON 字符串,必须先用htmlspecialchars_decode()解码,否则验签字段不匹配 - 所有带下划线的参数名(如
out_trade_no、trade_status)不能被框架自动转成驼峰(如outTradeNo),验签时字段名必须原样保留 - 支付宝公钥必须是 RSA2 格式(
-----BEGIN PUBLIC KEY-----开头),且不能混用证书模式下的公钥文件(如alipayCertPublicKey_RSA2.crt内容需提取 PEM 段)
为什么不能在验签后直接更新数据库?
支付宝会在 24 小时内最多重试 10 次异步通知,且只要没收到纯文本 success 就持续发。如果你在验签后立刻执行耗时操作(比如查库存、调物流 API、发短信),响应超时 → 支付宝认为失败 → 再次推送 → 订单重复处理。
- 验签通过后,**第一行代码就该输出
echo 'success'; exit;**,其他逻辑全部扔进队列或异步进程 - 用
file_put_contents()或 Redis 存原始通知数据,另起一个定时任务/消费者处理业务 - 订单状态更新前,务必加数据库唯一约束(如
UNIQUE KEY(out_trade_no, trade_status))或先SELECT ... FOR UPDATE锁行
调试时如何模拟支付宝回调?
别等真支付才测,本地就能模拟。关键点:HTTP 头、签名字段、原始 POST Body 三者必须一致。
立即学习“PHP免费学习笔记(深入)”;
- 用
curl发送时,必须指定-H "Content-Type: application/x-www-form-urlencoded",不能用application/json - 签名字段
sign要用你自己的私钥 + 其他所有参数(不含sign自身)按字典序拼接后生成,官方 SDK 的rsaSign()才可靠 - 最简验证法:把支付宝文档里的「验签示例参数」复制下来,用
file_get_contents("php://input")接收,硬编码进你的notify.php,跑通再换真实逻辑
curl -X POST http://yourdomain.com/notify.php \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "out_trade_no=202512291158000001" \ --data-urlencode "trade_status=TRADE_SUCCESS" \ --data-urlencode "total_amount=1.00" \ --data-urlencode "sign=xxx..."
真正难的从来不是写完代码,而是确认每一次回调都只被处理一次、且每次都能在 1 秒内干净利落地回 success。











