第三方支付回调必须完成签名验证、幂等处理、状态更新、异步解耦四步闭环;需禁用csrf、严格验签、加行锁或唯一索引防并发、5秒内响应并记录原始body,回调地址须公网可访问且https有效。

第三方支付回调不是“收到通知就完事”,而是必须完成签名验证、幂等处理、状态更新、异步解耦四步闭环,缺一不可。跳过任意一步都可能造成资金错账或重复发货。
回调入口必须用 POST 方法且禁用 CSRF
CodeIgniter 默认对 POST 请求启用 CSRF 防护,但支付宝、微信等平台回调请求不携带 csrf_token,直接触发 403 错误。必须在对应控制器方法上显式关闭:
- 在控制器类顶部添加
protected $csrf_protection = FALSE;(全局关) - 或更安全的做法:仅对回调方法绕过,在
__construct()中使用$this->csrf_exclude = ['do_notify', 'notify']; - 务必确认路由未被重写规则拦截,例如
alipay/do_notify要能直通到控制器方法,不能被伪静态规则吞掉
验签逻辑必须严格匹配官方 SDK 的字段顺序与编码规则
微信和支付宝回调参数看似是普通 POST 数据,实则签名基于原始字符串拼接(非 JSON 或 URL 解码后)。常见翻车点:
- 支付宝回调中,
sign和sign_type字段不能参与验签;但notify_id、out_trade_no、trade_status等所有业务字段必须原样参与 - 微信回调的
sign是对 XML body 内所有非空字段按字典序排序后拼接 + 密钥 MD5,不是对$_POST数组做 ksort 后 http_build_query - 必须用
file_get_contents('php://input')读取原始 XML/JSON,不能依赖$this->input->post()(会自动 urldecode,破坏签名)
订单状态更新必须加数据库行锁或唯一索引防并发
支付平台可能因网络问题重复推送同一笔回调(尤其微信),若不做幂等控制,会导致多次发货、余额叠加、库存超卖。
- 推荐做法:在订单表加唯一索引
UNIQUE KEY `ux_out_trade_no` (`out_trade_no`),回调中先INSERT IGNORE一条支付成功记录,再根据影响行数判断是否首次处理 - 或用
SELECT ... FOR UPDATE加行锁(需事务包裹),但注意避免长事务阻塞 - 绝对不要只靠
WHERE status = 'pending'更新——并发请求下两条 SQL 可能同时查到 pending,然后都执行 update
回调响应必须在 5 秒内返回 success(微信)或 true(支付宝)
超时未响应将触发平台重试(微信最多 5 次,间隔指数增长),而你的日志里可能只看到一次调用——因为后续重试被 Nginx 或 PHP-FPM timeout 截断了。
- 把耗时操作(如发邮件、调用物流接口、更新 Redis 缓存)全部挪到消息队列或后台任务,回调方法只做「落库 + 返回成功」两件事
- 用
fastcgi_finish_request()(PHP-FPM 环境)提前结束 HTTP 响应,再继续处理后续逻辑 - 记录原始回调 body 到文件或 DB(用
file_put_contents()或$this->db->insert()),别只依赖log_message(),重试时日志可能被覆盖
最易被忽略的是:回调地址必须是公网可访问的绝对 URL,且不能带 localhost、127.0.0.1、内网 IP;微信还强制要求 HTTPS,支付宝沙箱虽允许 HTTP,但正式环境必须 HTTPS 且证书有效。本地开发调试务必用 ngrok 或 localtunnel 透出真实域名。











