ThinkPHP扫码支付须用NATIVE模式二,trade_type设为"NATIVE",notify_url需外网可达,金额单位为分且为整数,验签前禁用input_filter,后端生成PNG二维码更稳定,异步通知必须验签+查单+幂等更新。

ThinkPHP 项目里做扫码支付,别直接抄官网 Demo 的裸 PHP 代码——它和 ThinkPHP 的自动加载、命名空间、输入过滤机制天然冲突,90% 的「验签失败」「空回调」「二维码生成失败」都源于此。
微信扫码支付必须用 NATIVE 模式二,别碰模式一
模式一要商户自己生成 prepay_id 并拼接 redirect_url,还要额外维护 product_id 注册接口,对 ThinkPHP 而言就是多一层不可控的 HTTP 请求和状态管理。模式二直接走统一下单接口,返回 code_url,拿来生成二维码就行,流程干净、错误点少。
-
trade_type必须设为"NATIVE",不是"JSAPI"或"APP" -
notify_url要填外网可访问的完整 URL(如https://yourdomain.com/api/wechat/notify),本地127.0.0.1或内网地址必然失败 -
out_trade_no建议用业务订单号,不要用时间戳+随机数拼接后直接传给微信——后续查单、对账、幂等控制全靠它 - 金额单位是分,
total_fee必须是整数,1表示 0.01 元;传0.01或字符串"0.01"会直接报错INVALID_TOTAL_FEE
支付宝扫码用 alipay.trade.precreate,不是 page.pay
alipay.trade.page.pay 是跳转到支付宝收银台,适用于 PC 端网页支付;扫码支付必须用 alipay.trade.precreate,它返回的是一个可直接渲染成二维码的 qr_code 字符串,而不是跳转链接。
- ThinkPHP 6.x 强烈建议用
alipay/easysdk,初始化时private_key和ali_public_key必须是 PEM 原文(含-----BEGIN RSA PRIVATE KEY-----头尾),不能去换行、不能 base64_decode - 沙箱环境务必显式配置
gateway为https://openapi.alipaydev.com/gateway.do,否则默认走正式网关,沙箱账号会返回INVALID_PARAMETER -
notify_url同样需外网可达,且 Nginx/Apache 不能强制改写Content-Type为multipart/form-data——支付宝异步通知是application/x-www-form-urlencoded,否则$_POST为空 - 验签前必须禁用 ThinkPHP 默认的 input_filter:
\think\Request::instance()->filter(false),否则+、/、=被自动 urldecode 后验签必败
二维码生成别依赖前端 JS,后端吐 PNG 最稳
前端用 qrcode.js 渲染容易因跨域、CSP、移动端兼容等问题白屏;后端生成 PNG 直接输出或存文件,前端只负责 <img src="..."/>,可控性高得多。
立即学习“PHP免费学习笔记(深入)”;
- 用
phpqrcode库最轻量,QRcode::png($code_url, false, QR_ECLEVEL_L, 4)即可输出 PNG 流 - 不要把
code_url或qr_code直接返回 JSON 给前端再渲染——URL 中的weixin:协议或支付宝的HTTPS链接在部分 WebView 下无法识别 - 生成的二维码图片建议加缓存头:
header('Cache-Control: max-age=300');,避免用户反复刷新导致重复请求 - 若用 ThinkPHP 自带的
image类生成,注意它不支持透明背景,而微信官方要求二维码背景为白色,否则扫码失败
异步通知处理必须做三件事:验签、查单、幂等更新
微信/支付宝都可能重复发通知,且通知到达不一定代表真实支付成功——比如用户扫码后取消、网络中断、银行扣款失败等。只靠通知内容更新订单状态是危险的。
- 微信回调收到原始 POST 数据后,先调用 SDK 的
$wxpay->notify()验签,成功才继续;失败必须原样返回fail,不能输出任何其他字符(包括空格、BOM) - 支付宝 EasySDK 的
$response = $client->execute($request)返回对象后,必须用$response->isSuccess()判断,再检查$response->body['alipay_trade_precreate_response']['code'] === '10000' - 验签通过后,**必须立即调用微信的
orderquery或支付宝的alipay.trade.query接口查单**,确认trade_state为SUCCESS才更新数据库 - 订单状态更新要用唯一索引(如
out_trade_no+status)或UPDATE ... WHERE status = 'unpaid',防止重复执行
最常被忽略的是:微信回调里 session 和 cookie 不可用,所有上下文(如用户 ID、订单归属)必须从通知数据或数据库中重新查,不能依赖当前请求的登录态。



















