必须用 file_get_contents('php://input') 获取原始数据,因 $_POST 会被 ThinkPHP 多次解码、转驼峰、破坏 JSON 和特殊字符,导致验签失败;解析后需手动 urldecode、htmlspecialchars_decode fund_bill_list,并用官方 SDK 验签。

file_get_contents('php://input') 是唯一可靠入口,$_POST 和 $this->request->post() 在支付宝回调里基本不可信。
为什么不能用 $_POST 或 $this->request->post()
支付宝异步通知发的是纯 application/x-www-form-urlencoded 原始字符串,但字段值可能含 URL 编码(如 %2B、%E4%BD%A0),且某些字段(如 fund_bill_list)是 JSON 字符串再 HTML 实体编码过的。ThinkPHP 默认对 $_POST 做两次解码 + 类型转换,会破坏原始签名原文结构。
- 字段名被框架自动转驼峰(
out_trade_no→outTradeNo),验签时字段名不匹配 -
fund_bill_list里的 JSON 被当成普通字符串二次 decode,JSON 结构损坏 - 含特殊字符的字段值(如中文、+ 号)被截断或乱码,
hash_hmac()输入失真 - 一旦用了
$_POST,再调file_get_contents('php://input')就返回空——PHP 输入流只能读一次
必须用 file_get_contents('php://input') 原始读取
这是支付宝文档明确要求的入口,也是 SDK 内部默认依赖的原始数据源。
- 第一行就写:
$raw = file_get_contents('php://input');,别犹豫 - 立刻记录日志:
file_put_contents('/tmp/alipay_notify.log', date('Y-m-d H:i:s') . "\n" . $raw . "\n---\n", FILE_APPEND); - 用
parse_str($raw, $params)解析成数组,不是json_decode()—— 支付宝不是发 JSON - 对每个字段值手动
urldecode()一次(parse_str不保证完全还原,支付宝实际发送时可能多层编码) - 特别处理
fund_bill_list:$params['fund_bill_list'] = htmlspecialchars_decode($params['fund_bill_list']);
验签必须喂给 SDK,别手拼字符串
自己按字典序拼串 + hash_hmac() 极易出错:漏字段、顺序错、编码不一致、没剔除 sign 和 sign_type。
立即学习“PHP免费学习笔记(深入)”;
- 用官方
AlipayTradeService或AopClient,初始化后调$alipay->setRequestParameters($params) -
$alipay->alipay_config['rsa_public_key']必须是 PEM 格式支付宝公钥,开头为-----BEGIN PUBLIC KEY-----,不是证书文件全文 - 验签前确保所有字段名仍是原始下划线格式(
out_trade_no,非outTradeNo) - 验签失败时,先检查日志里
$raw是否完整、$params是否含sign字段、公钥是否复制错行
验签通过后必须立刻 echo 'success'; exit;
支付宝会在 24 小时内最多重试 10 次,只要没收到纯文本 success 就持续推送。任何耗时操作(查库存、发短信、调物流 API)都必须挪到异步队列。
- 第一行响应代码必须是:
echo 'success'; exit;,不能有空格、换行、BOM - 业务逻辑用
INSERT IGNORE INTO pay_notify_log (notify_id, raw_data, created_at) VALUES (?, ?, ?)先落库,再由定时任务或消费者处理 - 订单状态更新必须加数据库唯一索引(如
UNIQUE KEY(trade_no)),并发插入冲突即说明已处理过 - 别信
trade_status字段做最终判断——它可能滞后(先发TRADE_SUCCESS,后发TRADE_CLOSED),以你自己库中状态为准
最常被跳过的一步是:没验证 notify_id 是否已处理过。支付宝虽用 trade_no 做幂等主键,但 notify_id 才是单次通知的唯一标识,缓存它比查 DB 更快,且能拦截同一通知的重复解析。



















