Yii2处理支付宝异步通知需禁用CSRF验证、用php://input获取原始POST、按字典序URL解码拼接参数、用RSA2验签、响应纯success无任何额外输出。

Yii2控制器必须禁用CSRF验证
支付宝异步通知是服务器对服务器的POST请求,不带浏览器Cookie或token,而Yii2默认开启CSRF验证,会直接在beforeAction阶段拦截并返回400错误——你根本收不到原始参数,更别提验签。
实操建议:
- 在处理回调的Controller里强制关闭:
$this->enableCsrfValidation = false; - 务必只在该Controller或具体action中关闭,不要全局关(如
Application::init()) - 如果用了RESTful风格路由(比如
NotifyController::actionAfterPay()),关在action开头也有效,但推荐统一在beforeAction里处理
验签前必须手动提取原始POST数据
Yii2的Yii::$app->request->post()会自动过滤、转义、甚至丢弃空值或特殊字符字段(比如sign含+、/、=时可能被截断或URL解码错误),导致拼接字符串和支付宝原始签名源不一致,验签100%失败。
实操建议:
- 用
file_get_contents('php://input')原始读取,再parse成数组:parse_str(file_get_contents('php://input'), $rawPost) - 或用
Yii::$app->request->getRawBody()(需确保Content-Type为application/x-www-form-urlencoded) - 绝对不要依赖
$_POST——它已被Yii2中间件污染过
排序+拼接规则必须严格对标支付宝文档
验签失败80%出在这里:参数没剔干净、排序逻辑错、拼接漏&、大小写/空格/编码不一致。支付宝要求“除sign和sign_type外,所有参数按key字典序升序排列后拼成key1=value1&key2=value2”——注意是**升序**,不是自然排序,且value要URL解码后再拼(支付宝发来的是已URL编码的值,但验签时要用原始语义值)。
实操建议:
- 先
unset($rawPost['sign'], $rawPost['sign_type']),再ksort($rawPost) - 遍历拼接时,对每个value做
urldecode()(例如out_trade_no可能含%2B) - 拼完字符串末尾不能有多余
&,开头不能有& - 用
openssl_verify()时,算法选OPENSSL_ALGO_SHA256(对应RSA2),公钥格式必须是PEM(以-----BEGIN PUBLIC KEY-----开头)
响应必须原样输出success且无任何额外输出
支付宝收到非success(纯小写、7字符、无空格、无换行、无HTML、无JSON包装)就判定失败,并在25小时内最多重试8次。Yii2默认会渲染layout、输出header、甚至debug toolbar,导致响应体混入大量垃圾字符。
实操建议:
- 在action末尾加
Yii::$app->response->format = \yii\web\Response::FORMAT_RAW; - 立即
echo 'success'; exit;(不用return,避免后续中间件干扰) - 确保整个action里没有
var_dump、error_log、print_r等调试输出(哪怕被注释了,某些IDE插件也会触发) - 检查PHP配置:关闭
output_buffering或确保它没缓存响应
urldecode那一步和echo 'success'前的任何输出。










