EasyWeChat v6与v5差异大,新项目必须用v6;需手动传配置、正确处理prepay_id路径、规范JSAPI参数、严格验签回调、结合异步通知与主动查单防丢,证书须无BOM且为绝对路径。

EasyWeChat 版本选错直接导致支付失败
Laravel 集成微信支付时,overtrue/wechat(即 EasyWeChat)的 v5 和 v6 行为差异极大,v6 强制要求使用 HttpClient 实现,且签名逻辑、参数结构、回调验签方式全变了。很多教程照搬旧版代码,结果 app.pay()->unifiedOrder() 报 Invalid signature 或空响应。
实操建议:
- v5 仍支持 Laravel 5.8–8.x,但已停止维护;新项目必须用 v6(
overtrue/wechat:^6.0) - v6 不再自动读取
config/wechat.php,需手动传入配置数组或使用WeChat::officialAccount()等工厂方法 - 沙箱环境调试时,v6 的
useSandbox(true)必须在实例化后调用,不能写在配置里 - 微信返回的
prepay_id在 v6 中藏在$result['response']['prepay_id']下,不是顶层字段
Laravel 中如何安全传参给前端发起 JSAPI 支付
很多人把 appId、timeStamp、nonceStr、package、signType、paySign 全部从后端拼好再返回 JSON,结果前端调 WXJSSDK.chooseWXPay() 时提示“参数错误”——问题常出在 package 值没按微信规范转义,或 timeStamp 是字符串而非整数秒。
实操建议:
-
package字段值必须是prepay_id=xxx,不能带空格、换行或额外引号 -
timeStamp必须是(int) time(),不能是Carbon::now()->timestamp(可能带毫秒) -
paySign生成前,所有参与签名的字段必须按字典序排序,且package的值不能 URL 编码(EasyWeChat v6 内部已处理,勿二次 encode) - 别把
key(商户 API 密钥)暴露到前端,签名必须 100% 在服务端完成
微信支付回调验签失败的三个高频原因
收到微信 POST 回调后,$app->payment->handleNotify() 返回空或抛出 InvalidSignatureException,不是证书问题就是数据解析错了。v6 默认只接受 application/xml,而有些 Nginx 或 CDN 会把 POST body 转成 application/x-www-form-urlencoded。
实操建议:
- Laravel 中需禁用默认的表单请求解析:在中间件里加
if (request()->isMethod('POST') && request()->header('Content-Type') === 'application/xml') { $content = file_get_contents('php://input'); } - v6 的
handleNotify()默认会自动验签并更新订单状态,若需自定义逻辑,应传false关闭自动处理:$app->payment->handleNotify($callback, false) - 验签失败时先 dump
file_get_contents('php://input'),确认是否为空或被截断——常见于 Nginx 的client_max_body_size过小 - 证书路径必须是绝对路径,
ssl_cer和ssl_key要对应微信商户平台下载的apiclient_cert.pem和apiclient_key.pem,不能用 .p12
异步通知与主动查询订单状态怎么配合才不丢单
微信回调可能超时、重复或丢失,只依赖 handleNotify() 更新订单状态极易造成“用户付了钱,系统没记账”。但也不能每笔都轮询查,成本高还可能触发风控。
实操建议:
- 回调成功后,立即记录原始 XML 到数据库(字段如
notify_raw、notify_at),哪怕只是存个日志表,方便事后对账 - 对“通知未到达”的订单,启用延迟队列(如 Laravel Horizon)在 5 分钟后查一次
$app->payment->find($orderNo),最多重试 3 次 - 查单接口返回
trade_state=NOTPAY或CLOSED时,不要直接标记失败——可能是用户中途取消,得结合return_code和result_code综合判断 - 微信侧的
out_trade_no必须全局唯一,Laravel 中建议用Str::uuid()->toString()或时间戳+随机数生成,避免并发重复
最麻烦的其实是证书和签名链——微信的 SHA256withRSA 和 EasyWeChat v6 的 OpenSSL 调用方式耦合极深,稍有不慎就卡在 openssl_sign(): supplied key param cannot be coerced into a private key,这时候别急着换包,先检查 pem 文件有没有被编辑器悄悄转成 UTF-8 BOM。事情说清了就结束










