webman支付网关需禁用jsonbodyparser中间件,用$request->rawbody()获取原始请求体验签;支付宝需parse_str解析后调用rsacheckv1;微信v3须透传三个header并用绝对路径证书;须用redis锁+条件更新保障幂等。

Webman 支付网关必须绕开框架默认的 JSON 自动解析
支付宝和微信的异步通知(/notify/alipay、/notify/wechat)都要求原始 HTTP body 不被修改,而 Webman 默认启用 JsonBodyParser 中间件,会把 application/json 或 application/x-www-form-urlencoded 请求自动转成 $request->post() 数组——这直接导致验签失败。
- 在路由定义中显式禁用该中间件:
Route::post('/notify/alipay', [NotifyController::class, 'alipay'])->withoutMiddleware(JsonBodyParser::class); - 回调方法内必须用
$request->rawBody()拿到原始字节流,不能依赖$_POST(已被框架重置或清空) - 若通知是 form-data 类型,
rawBody()仍可用;但注意 PHP 的php://input在 multipart 场景下不可读,此时需改用$request->getUploadedFiles()+ 手动解析 boundary(极少场景)
支付宝验签必须用原始 POST 字符串拼接,不是数组
AlipayAopClient::verifyNotify() 内部会重新拼接待签名字符串,但前提是传入的是原始 $_POST。Webman 下 $_POST 已为空,直接传空数组必然返回 false。
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
- 正确做法:用
parse_str($request->rawBody(), $data)解析 x-www-form-urlencoded 数据,再调用AlipaySignature::rsaCheckV1($data, $alipay_public_key, $charset = 'UTF-8') - 关键字段不能丢:
sign、sign_type必须原样保留;timestamp格式必须为yyyy-MM-dd HH:mm:ss(支付宝不接受毫秒或 ISO 格式) - 私钥必须是 PKCS#1 格式(以
-----BEGIN RSA PRIVATE KEY-----开头),PKCS#8 会导致openssl_pkey_get_private()返回 false
微信 v3 回调验签要用 verifyEvent,且证书路径必须绝对
wechatpay-php 的 verifyEvent() 依赖三个 HTTP 头:WECHATPAY-SIGNATURE、WECHATPAY-TIMESTAMP、WECHATPAY-NONCE,缺一不可。Webman 默认不透传这些头,且证书路径若为相对路径,在多 worker 场景下极易读取失败。
- 确保 Nginx/Apache 反向代理时透传 header:
proxy_set_header WECHATPAY-SIGNATURE $http_wechatpay_signature;等三行 - 初始化验签器时,证书路径必须用
realpath(__DIR__ . '/cert/apiclient_cert.pem'),不能写config/cert/... - 验签前先检查
$request->rawBody()是否为空;微信回调 body 是 JSON,但验签需原始字节流,不能先json_decode() - 响应必须是纯文本
200 OK,不能带任何 HTML 标签、换行或空格,否则微信认为失败并重试
支付网关要防并发重复处理,别只靠数据库唯一索引
高并发下,同一笔订单的多次回调可能同时到达不同 worker 进程,仅靠 INSERT ... ON DUPLICATE KEY UPDATE 不足以保证幂等——因为验签、查单、更新状态这几步之间存在时间窗口。
- 用 Redis 锁控制入口:
$lockKey = 'pay:notify:' . $out_trade_no; if (Redis::set($lockKey, 1, ['nx', 'ex' => 30]) === false) return response('OK'); - 锁释放时机必须严格:只在状态更新成功后
Redis::del($lockKey),失败则留着让下次重试跳过 - 数据库更新语句要包含状态条件:
UPDATE orders SET status = 'paid' WHERE id = ? AND status = 'unpaid',避免覆盖已处理状态 - 不要在回调里触发发券、发货等耗时操作,应投递到消息队列(如 Redis List 或 AMQP),由独立消费者处理










