支付宝异步通知(notify_url)是支付结果唯一可信来源,必须禁用CSRF、原始读取POST、严格验签并返回纯文本“success”;同步通知(return_url)不可靠,仅作展示入口。

支付宝 notify_url 异步回调处理
支付宝的支付结果唯一可信来源是异步通知,不是用户跳转回来的 return_url。notify_url 必须禁用 CSRF、原始读取数据、严格验签、干净响应。
- 在控制器顶部或 beforeAction() 中关闭 CSRF:$this->enableCsrfValidation = false;
- 第一行就读取原始 POST:$raw = file_get_contents('php://input');(不能用 $_POST 或 Yii::$app->request->post())
- 用 parse_str($raw, $data) 解析后,剔除 sign 和 sign_type,再 ksort + urldecode 拼接待验签字符串
- 验签使用支付宝公钥(alipay_public_key),算法选 OPENSSL_ALGO_SHA256(对应 RSA2),私钥必须是 PKCS#8 格式
- 验签通过后仅更新订单状态、记录日志等副作用操作;响应必须是纯文本 success(7 字母小写,无空格、换行、HTML、JSON 包裹)
微信支付 notify 接口实现要点
微信回调是 XML 格式原始 POST,需解析、验签、解密三步缺一不可。easywechat v6+ 提供了 verifyNotify() 方法,但底层依赖正确配置和原始输入。
- 入口 action 第一行调用:$xml = file_get_contents('php://input');(simplexml_load_string 会丢 CDATA,建议用 simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA))
- 初始化支付实例时,必须显式传入独立的支付配置数组(含 mch_id、key、notify_url),不能混在公众号配置里
- easywechat 的 $payment->verifyNotify() 默认会校验签名并尝试解密(V3 要求),若失败需检查 key 是否为 32 位 MD5 密钥、是否被截断或含 BOM
- 回调成功后返回标准 XML 响应:
(注意大小写和 CDATA) - 业务逻辑如改订单状态、发消息等,建议放在事务提交后触发(Transaction::onCommit),避免部分失败导致状态不一致
统一回调入口设计建议
避免为每个支付渠道写独立 controller,可用一个 NotifyController 统一分发,靠路由或参数识别来源,提升可维护性。
- URL 设计示例:/notify/alipay、/notify/wechat,或统一为 /notify?channel=alipay
- 在 action 内根据 channel 加载对应服务类(如 AlipayNotifyService、WechatNotifyService),职责分离
- 所有回调 action 均设为 public,且不继承任何需登录/权限的 base controller,防止中间件拦截
- 日志记录建议包含原始请求体、验签结果、业务动作、耗时,方便排查「验签通过但未更新状态」类问题
- 生产环境务必关闭调试工具栏(YII_DEBUG=false),避免响应体混入 HTML 或 JS 导致 success 不被识别
常见失败原因与快速定位
90% 的回调失败不是逻辑错误,而是环境或配置细节偏差。以下是最常踩的坑:
- 支付宝:php://input 为空 → 检查是否提前调用了 $_POST 或 Yii::$app->request->post();验签失败 → 确认公钥是「支付宝平台导出的」而非自己生成的;响应含空格 → 用 var_dump 或 header() 查看真实输出
- 微信:Invalid signature → 检查支付 key 是否 32 字符、是否含不可见字符;回调收不到 → 确认 notify_url 是公网 HTTPS、不在支付授权目录白名单中、且微信后台已填对
- 两者共性:服务器时间误差超 15 分钟会导致验签失败;Nginx/Apache 配置了 request body 限制(如 client_max_body_size)可能截断 XML/POST 数据
- 沙箱测试通但线上失败 → 检查密钥是否切换、网关地址是否仍为 dev 版本、PKCS#8 私钥是否在线上环境被自动转义


















