微信支付 sdk 应通过 laravel 自定义服务提供者注册单例,避免直接 new 实例;cert_path 和 key_path 必须为 storage/app/certs/ 下绝对路径且权限 600;禁用 trimstrings 中间件或放行 wechatpay- 开头 header;verify() 必须返回标准 xml 成功响应。

微信支付 SDK 不该直接在控制器里 new 实例,更不该把配置硬编码进业务逻辑——自定义服务提供者才是 Laravel 里干净、可复用、易测试的封装方式。
为什么不能直接 new WechatPayApplication?
每次 new 都会重复加载证书、解析配置、初始化签名器,既浪费资源又难统一管理;不同控制器用不同参数实例化,后期改配置要满项目搜 new Application;单元测试时无法 mock 支付行为,一跑就发真实请求。
用服务提供者注册单例,能确保整个请求生命周期内只初始化一次,且可通过 Laravel 容器自动注入。
cert_path 和 key_path 必须用绝对路径且权限为 600
微信 V3 SDK 对证书路径极其敏感:相对路径、public_path()、带符号链接的路径都会导致 Unable to load private key;证书文件必须放在 storage/app/certs/ 下,且权限设为 600(Laravel 进程用户可读不可写)。
-
cert_path指向apiclient_cert.pem(公钥证书) -
key_path指向apiclient_key.pem(无密码私钥,不是 .p12 或带密码的 PEM) - 务必用
storage_path('app/certs/apiclient_cert.pem'),别拼字符串 - 执行
chmod 600 storage/app/certs/*.pem,否则验签必失败
注册服务提供者时禁用 TrimStrings 中间件干扰 header
微信回调请求头含 Wechatpay-Serial,Laravel 默认的 TrimStrings 中间件会过滤掉带连字符的 header 键,导致 SDK 拿不到序列号,验签直接报 INVALID_SIGNATURE。
检查 app/Http/Kernel.php 的 $middleware 数组,确认没启用 TrimStrings;若必须保留,需在中间件中显式放行:
if (Str::startsWith($key, 'Wechatpay-')) {
return $key;
}
verify() 方法必须返回标准 success 响应体
微信服务器对回调响应极其严格:不返回 <xml><return_code>SUCCESS</return_code><return_msg>OK</return_msg></xml> 格式,就会持续重发通知,可能造成订单重复处理。
- 别用
response()->xml(...)自己拼,容易漏 CDATA 或编码错误 - yansongda/pay 的
$pay->verify()->success()会自动构造合规响应 - overtrue/laravel-wechat 的
$app->payment->handleNotify(...)也内置了正确返回逻辑 - 手动实现时,必须用
header('Content-Type: text/xml; charset=utf-8')并 echo 原始 XML 字符串
最难调的永远不是签名算法,而是 notify_url 的末尾斜杠、证书文件的换行符、header 被中间件吃掉的那一个连字符——这些细节不卡在上线前,就卡在凌晨三点的告警电话里。











