第三方支付不是“调个接口就能用”,而是由「凭证管理 + 请求签名 + 回调验签 + 状态幂等」四块硬骨头组成的闭环,跳过任意一环将导致支付成功但订单未更新、重复扣款或回调被伪造等问题。

直接说结论:第三方支付不是“调个接口就能用”,而是由「凭证管理 + 请求签名 + 回调验签 + 状态幂等」四块硬骨头组成的闭环。跳过任意一环,上线后大概率出现支付成功但订单没更新、重复扣款、回调被伪造等问题。
支付宝网页支付必须用 alipay.trade.page.pay,别误用扫码接口
很多开发者看到“支付”就下意识选 alipay.trade.precreate(预下单),结果发现跳转失败或提示“不支持该交易类型”。这是因为:
-
alipay.trade.page.pay生成的是带支付表单的 HTML 页面,适合 PC/H5 跳转场景; -
alipay.trade.precreate返回的是qr_code字符串,只适用于线下静态码或主动轮询查单,不能直接跳转; - 若你在 H5 页面里用
precreate+ 前端跳转,支付宝会拒绝并返回INVALID_PARAMETER错误。
正确做法是:用户提交订单后,后端调用 Factory::payment()->page()->pay(),输出 $response->body 即可自动提交表单。注意 return_url 必须是 HTTP/HTTPS 协议且能被公网访问,否则支付宝无法跳回。
微信支付 V3 回调验签必须读原始请求体,$request->post() 会破坏签名
微信支付回调使用的是原始 JSON 或 XML,且签名基于完整原始字节计算。如果你在 ThinkPHP、Laravel 等框架中用 $request->post() 或 $request->input() 解析,框架会先做 JSON decode / form parse,导致 body 被修改,验签必然失败。
- ThinkPHP 6 必须用
$request->getRawBody()获取未处理原始数据; - Laravel 应用
file_get_contents('php://input'); - 验签时还要校验三个 header:
WECHATPAY-TIMESTAMP、WECHATPAY-NONCE、WECHATPAY-SIGNATURE,缺一不可; - 时间戳超过 5 分钟的请求必须直接拒绝,防止重放攻击。
常见错误现象:verifySign failed 或 invalid signature,90% 是因为用了封装后的参数而非原始 body。
密钥格式不对会导致签名永远失败,RSA2 要求 PKCS#8 私钥
支付宝和微信都明确要求使用 PKCS#8 格式的私钥。但很多人用 OpenSSL 默认生成的是 PKCS#1(-----BEGIN RSA PRIVATE KEY-----),直接填进去,SDK 内部调用 openssl_sign() 会静默失败,返回空签名或报错 error:0909006C:PEM routines:get_name:no start line。
- 把私钥转成 PKCS#8:运行
openssl pkcs8 -topk8 -inform PEM -in private.pem -outform PEM -nocrypt; - 确认输出开头是
-----BEGIN PRIVATE KEY-----(无 RSA 字样); - 公钥无需转换,但支付宝公钥必须是对方平台导出的完整内容,不能自己用私钥推导;
- 所有密钥字符串不要加换行、空格,
file_get_contents()后建议用trim()清理。
这个点非常隐蔽——代码逻辑完全正确,但就是签不了名。线上环境务必用 var_dump(openssl_pkey_get_private($key)) 检查私钥是否加载成功。
异步通知处理必须进队列 + 加唯一约束,不能同步更新数据库
支付平台会在网络异常时高频重试 notify,比如微信可能每 15 分钟重发一次,持续 24 小时。如果回调入口直接执行 update order set status = 'paid',极容易因并发写入导致重复发货、重复扣库存。
- 回调入口只做两件事:验签通过后,立即返回
success(微信)或{"code":"10000"}(支付宝),然后投递到消息队列(如 Redis List / Laravel Horizon / TP 的 Job); - 消费任务里用
DB::transaction()包裹状态更新,并在订单表加唯一索引(如UNIQUE KEY `uk_out_trade_no` (`out_trade_no`)); - 任务类必须实现幂等控制,例如先查订单当前状态,仅当为
pending时才更新; - 记录日志必须包含原始请求体、签名头、商户订单号,方便事后审计。
最常被忽略的是“立刻返回 success”——哪怕后续队列处理失败,也不能卡住回调响应,否则平台将持续重试,形成雪崩。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











