资讯动态

PHP微信支付v3完整工程实践:签名验签、证书管理与异步通知闭环

发布时间:2026/10/18 13:10:19 来源:尧图企业网站定制
简介本资源是一份面向PHP后端开发者与微信支付接入初学者的V3版完整实践方案聚焦最新微信支付接口集成解决商户系统快速对接、安全签名、证书管理及异步通知处理等核心难点。压缩包共16个文件含3个关键PHP接口文件payapi.php、sslapi.php、pemapi.php、7个ASP辅助类Class.asp、send.asp、pay_V3.asp等兼顾旧环境兼容参考、2个说明文档txt、1个PEM格式证书文件、1个JS脚本及配套GIF加载动画整体仅61KB轻量易部署。已有4657人学习下载内容结构清晰从Config.asp配置、统一下单到notify.asp回调验证覆盖沙箱测试、签名生成、证书加载、订单查询与异常退款全流程。读者可直接复用核心代码逻辑理解V3版API密钥分层管理、HMAC-SHA256签名机制及HTTPS双向证书通信要点是落地微信支付不可或缺的轻量级实战参考。1. PHP微信支付v3完整实例不是配个密钥就能回调成功的黑匣子而是从签名生成、证书加载、异步通知验签到退款闭环的可调试工程你是不是也试过照着微信官方文档改了十几遍curl_setopt参数结果401 Unauthorized还在控制台刷屏或者明明收到微信发来的支付成功通知但notify_url里file_get_contents(php://input)拿到的却是空字符串查日志发现是 OpenSSL 解密失败却连错误码都打不出来这个 PHP 微信支付 v3 完整实例就是为解决这些「看似简单、实则踩坑密集」的落地问题而生——它不是一个只跑通「统一下单」就收工的 Demo而是一个包含真实证书管理机制、自动刷新平台证书、支持 JSAPI/APP/NATIVE 三通道下单、带完整验签逻辑的异步通知处理器、含订单状态机与幂等校验的退款模块、以及关键路径全埋点的日志体系的可交付工程。适合正在接入微信支付 v3 的中小项目后端开发者、需要快速验证支付链路的测试工程师以及想搞懂「为什么官方 SDK 总在 production 环境翻车」的 PHP 架构同学。它不讲 OAuth2 流程原理只告诉你WechatPayHttpClient::request()调用前$headers[Authorization]字符串里那个nonce_str是怎么和时间戳、序列化请求体一起被 HMAC-SHA256 签出来的。2. 工程结构与核心依赖基于 Composer 管理的轻量级分层设计拒绝 vendor 锁死与 SDK 黑盒这个项目没有引入wechatpay-php官方 SDKv1.7而是选择手动封装 HTTP 客户端 自研签名/验签器。原因很现实官方 SDK 对证书加载路径硬编码、不支持多商户号共存、decryptNotification()方法内部异常吞得过深导致线上验签失败时根本无法定位是证书过期、AES-GCM tag 不匹配还是微信返回了加密字段缺失。我们采用更透明的分层结构所有敏感逻辑可控、可打点、可替换。2.1 目录结构按职责切分关键路径一目了然├── app/ │ ├── Http/ # HTTP 请求封装层非 Guzzle纯 cURL 封装 │ │ ├── WechatPayHttpClient.php # 核心客户端自动注入 Authorization 头、处理 401 重试、记录原始响应 │ │ └── HttpClientException.php # 自定义异常含 raw_response_body 与 http_code │ ├── Crypto/ # 加密解密核心 │ │ ├── Signer.php # 签名生成器支持 RSA-SHA256输入 methoduribodytimestampnonce_str │ │ ├── Verifier.php # 平台证书验签器验证 response.headers.WeChatpay-Signature 合法性 │ │ └── AesGcmDecryptor.php # 通知解密器使用 platform_certificate.pem 中的公钥解密 AES key再解密通知体 │ ├── Service/ # 业务服务层 │ │ ├── PayService.php # 统一下单、查询订单、关闭订单主入口 │ │ ├── RefundService.php # 退款申请、查询退款、同步退款结果 │ │ └── CertificateManager.php # 平台证书自动刷新与缓存关键 │ └── Event/ # 异步事件处理器 │ └── NotifyHandler.php # notify_url 入口完整验签 → 解密 → 幂等校验 → 状态更新 → 响应微信 ├── config/ │ └── wechatpay.php # 配置中心含商户号、私钥路径、平台证书路径、APIv3密钥等 ├── public/ │ └── notify.php # Web 入口文件仅做路由转发无业务逻辑 ├── storage/ │ ├── certs/ # 证书存储目录需写入权限 │ │ ├── apiclient_key.pem # 商户私钥RSA 2048必须 PKCS#1 格式 │ │ ├── apiclient_cert.pem # 商户证书含私钥PKCS#12 转 PEM 后拆分 │ │ └── platform_cert.pem # 微信平台证书由 CertificateManager 自动下载并轮换 │ └── logs/ # 日志目录按日期滚动 └── vendor/ # Composer 依赖仅 require ext-openssl, ext-curl, monolog/monolog提示storage/certs/目录必须赋予 Web 服务器写权限如chmod 755 storage/certs否则CertificateManager无法自动更新平台证书后续所有验签将失败。2.2 关键依赖说明最小化、可审计、无隐藏行为依赖版本用途替换说明php 7.4必需运行环境不支持 PHP 8.0 的str_starts_with()等函数已全部兼容ext-opensslenabled签名、验签、AES-GCM 解密缺失则整个支付流程不可用启动时强制检测ext-curlenabledHTTP 请求所有请求走curl_exec禁用file_get_contents方式monolog/monolog^2.9结构化日志日志格式统一为 JSON含 trace_id、request_id、wechatpay_timestampsymfony/polyfill-php80^1.28PHP 8 兼容层仅用于str_starts_with等函数垫片无副作用项目不依赖guzzlehttp/guzzle、firebase/php-jwt、paragonie/random_compat—— 所有网络、JWT、随机数生成均手写避免因第三方库版本冲突导致签名不一致这是线上最玄学的翻车点之一。2.3 配置文件详解config/wechatpay.php中每个字段都决定一条链路生死?php return [ // 【必填】微信支付商户号注意不是公众号 AppID mchid env(WECHATPAY_MCHID, 1900000100), // 【必填】APIv3 密钥微信商户平台 → API安全 → 设置APIv3密钥 api_v3_key env(WECHATPAY_API_V3_KEY, your_32_byte_api_v3_key_here), // 【必填】商户私钥路径绝对路径必须是 PKCS#1 格式即-----BEGIN RSA PRIVATE KEY----- private_key_path env(WECHATPAY_PRIVATE_KEY_PATH, __DIR__ . /../storage/certs/apiclient_key.pem), // 【必填】商户证书路径绝对路径必须含证书链即-----BEGIN CERTIFICATE-----...-----END CERTIFICATE----- certificate_path env(WECHATPAY_CERTIFICATE_PATH, __DIR__ . /../storage/certs/apiclient_cert.pem), // 【必填】微信平台证书缓存路径CertificateManager 自动写入必须可写 platform_cert_path env(WECHATPAY_PLATFORM_CERT_PATH, __DIR__ . /../storage/certs/platform_cert.pem), // 【选填】微信支付 API 基础 URL国内默认沙箱环境请改为 https://api.sandbox.wechatpay.dev base_url env(WECHATPAY_BASE_URL, https://api.mch.weixin.qq.com), // 【选填】HTTP 超时设置单位秒 timeout (int)env(WECHATPAY_TIMEOUT, 15), // 【选填】是否开启调试模式开启后会记录原始请求/响应 body 到日志 debug (bool)env(WECHATPAY_DEBUG, false), ];注意private_key_path必须是PKCS#1 格式的私钥。如果你导出的是.p12或.pfx文件请用以下命令转换Windows 用户请用 Git Bash 或 WSLopenssl pkcs12 -in apiclient_cert.p12 -clcerts -nokeys -out apiclient_cert.pem openssl pkcs12 -in apiclient_cert.p12 -nocerts -nodes -out apiclient_key.pem # 然后手动删除 apiclient_key.pem 中除私钥块外的所有内容即只保留 -----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY-----3. 核心模块拆解从Signer::sign()到NotifyHandler::handle()的完整链路微信支付 v3 最难啃的骨头从来不是「怎么调接口」而是「怎么让微信相信你发的请求是真的」以及「怎么确认微信发来的通知没被篡改」。下面这段代码就是整个信任链的起点。3.1 签名生成器app/Crypto/Signer.php中的sign()方法?php namespace App\Crypto; use RuntimeException; use function openssl_pkey_get_private; use function openssl_sign; use function base64_encode; use function hash_hmac; use function sprintf; class Signer { private string $privateKeyPath; public function __construct(string $privateKeyPath) { $this-privateKeyPath $privateKeyPath; } /** * 生成微信支付 v3 签名 * param string $method HTTP 方法如 POST * param string $uri 请求 URI如 /v3/pay/transactions/jsapi * param string $body 请求体JSON 字符串若无则传空字符串 * param int $timestamp 当前 Unix 时间戳秒级 * param string $nonceStr 随机字符串32位以内推荐使用 uniqid(, true) * return string 签名字符串base64 编码 */ public function sign(string $method, string $uri, string $body, int $timestamp, string $nonceStr): string { // 步骤1构造签名原文注意换行符必须是 \n不能是 \r\n $message sprintf( %s\n%s\n%d\n%s\n%s\n, $method, $uri, $timestamp, $nonceStr, $body ); // 步骤2读取私钥必须是 PKCS#1 格式 $privateKey file_get_contents($this-privateKeyPath); if (!$privateKey) { throw new RuntimeException(Failed to read private key from {$this-privateKeyPath}); } $res openssl_pkey_get_private($privateKey); if (!$res) { throw new RuntimeException(Invalid private key format in {$this-privateKeyPath}. Must be PKCS#1.); } // 步骤3使用 RSA-SHA256 签名 $signature ; $result openssl_sign($message, $signature, $res, SHA256); if (!$result) { throw new RuntimeException(OpenSSL sign failed: . openssl_error_string()); } // 步骤4base64 编码返回 return base64_encode($signature); } }逻辑说明与参数说明$message是签名原文严格按\n拼接且末尾带一个\n。微信校验时会完全复现该字符串任何空格、换行差异都会导致401。$body必须是原始 JSON 字符串未格式化、无多余空格例如{appid:wx123,mchid:1900000100,...}。如果传了json_encode($data, JSON_UNESCAPED_UNICODE)但没trim()可能因 UTF-8 BOM 或换行导致签名不一致。$nonceStr推荐用bin2hex(random_bytes(16))生成比uniqid()更安全长度不超过 32 字符微信会校验。openssl_pkey_get_private()返回false时99% 是私钥格式错误常见于导出.p12后未正确拆分或用了 PKCS#8 格式。此时openssl_error_string()会返回PEM routines:PEM_read_bio:no start line务必检查私钥开头是否为-----BEGIN RSA PRIVATE KEY-----。3.2 平台证书验签器app/Crypto/Verifier.php中的verify()方法?php namespace App\Crypto; use RuntimeException; use function file_get_contents; use function openssl_pkey_get_public; use function openssl_verify; use function base64_decode; use function hash_hmac; use function sprintf; class Verifier { private string $platformCertPath; public function __construct(string $platformCertPath) { $this-platformCertPath $platformCertPath; } /** * 验证微信响应头中的 WeChatpay-Signature 是否合法 * param string $message 签名原文同 Signer::sign 第一步构造的字符串 * param string $signatureHeader WeChatpay-Signature 响应头值base64 编码 * param string $serialNo 平台证书序列号来自 WeChatpay-Serial * param string $timestamp 响应头 WeChatpay-Timestamp 值 * param string $nonce 响应头 WeChatpay-Nonce 值 * return bool true 表示验签通过 */ public function verify( string $message, string $signatureHeader, string $serialNo, string $timestamp, string $nonce ): bool { // 步骤1解码签名 $signature base64_decode($signatureHeader); if ($signature false) { throw new RuntimeException(Invalid base64 signature: {$signatureHeader}); } // 步骤2加载平台证书必须是 PEM 格式含 BEGIN CERTIFICATE $certContent file_get_contents($this-platformCertPath); if (!$certContent) { throw new RuntimeException(Failed to read platform cert from {$this-platformCertPath}); } $publicKey openssl_pkey_get_public($certContent); if (!$publicKey) { throw new RuntimeException(Invalid platform certificate format. Must contain -----BEGIN CERTIFICATE-----); } // 步骤3使用 RSA-SHA256 验证 $result openssl_verify($message, $signature, $publicKey, SHA256); if ($result -1) { throw new RuntimeException(OpenSSL verify failed: . openssl_error_string()); } return $result 1; } }关键点$message必须与微信构造的完全一致${method}\n${uri}\n${timestamp}\n${nonce}\n${response_body}\n。注意响应体response_body是原始二进制 body不是 JSON 解析后的数组如果你用json_encode($response)再去拼必然失败。$serialNo和$timestamp、$nonce都来自响应头必须原样传入不能 trim() 或 strtolower()。微信会校验序列号是否匹配当前使用的平台证书。openssl_pkey_get_public()失败时错误信息通常是PEM routines:PEM_read_bio:no start line说明platform_cert.pem文件为空、损坏或被CertificateManager下载中断见下节避坑。3.3 异步通知处理器app/Event/NotifyHandler.php中的handle()方法骨架?php namespace App\Event; use App\Crypto\AesGcmDecryptor; use App\Crypto\Verifier; use Monolog\Logger; use RuntimeException; class NotifyHandler { private Verifier $verifier; private AesGcmDecryptor $decryptor; private Logger $logger; public function __construct(Verifier $verifier, AesGcmDecryptor $decryptor, Logger $logger) { $this-verifier $verifier; $this-decryptor $decryptor; $this-logger $logger; } /** * 处理微信支付异步通知 * return array [code 0, message OK] 或 [code -1, message xxx] */ public function handle(): array { // 步骤1获取原始加密通知体必须用 php://input不能用 $_POST $rawBody file_get_contents(php://input); if ($rawBody false || $rawBody ) { $this-logger-error(Empty notify body received); return [code -1, message empty body]; } // 步骤2提取响应头注意Web 服务器可能重命名 header需适配 Nginx/Apache $headers $this-getHeaders(); $signature $headers[WeChatpay-Signature] ?? ; $serialNo $headers[WeChatpay-Serial] ?? ; $timestamp $headers[WeChatpay-Timestamp] ?? ; $nonce $headers[WeChatpay-Nonce] ?? ; // 步骤3构造验签原文注意此处 message ${timestamp}\n${nonce}\n{$rawBody}\n $message sprintf(%s\n%s\n%s\n, $timestamp, $nonce, $rawBody); // 步骤4验签 try { if (!$this-verifier-verify($message, $signature, $serialNo, $timestamp, $nonce)) { $this-logger-error(WeChatpay signature verification failed, [ timestamp $timestamp, nonce $nonce, raw_body_length strlen($rawBody), ]); return [code -1, message signature failed]; } } catch (RuntimeException $e) { $this-logger-error(Verifier exception, [exception $e-getMessage()]); return [code -1, message verify error]; } // 步骤5解密通知体 try { $decrypted $this-decryptor-decrypt($rawBody, $this-getApiV3Key()); } catch (RuntimeException $e) { $this-logger-error(Decrypt notification failed, [exception $e-getMessage()]); return [code -1, message decrypt failed]; } // 步骤6解析 JSON 并做幂等校验省略具体业务逻辑 $data json_decode($decrypted, true); if (json_last_error() ! JSON_ERROR_NONE) { $this-logger-error(Invalid JSON in decrypted notification, [raw $decrypted]); return [code -1, message invalid json]; } // 步骤7更新订单状态伪代码 // $order Order::findByOutTradeNo($data[resource][out_trade_no]); // if ($order $order-status ! paid) { // $order-status paid; // $order-save(); // } // 步骤8返回成功响应微信要求 HTTP 200 JSON {code:SUCCESS,message:OK} return [code SUCCESS, message OK]; } private function getHeaders(): array { /* 实现见下文 */ } private function getApiV3Key(): string { /* 从 config 读取 */ } }为什么必须用file_get_contents(php://input)因为微信发送的是application/json类型的原始 body而 PHP 默认不会将 JSON body 解析到$_POST那是application/x-www-form-urlencoded的事。很多开发者卡在这一步$_POST是空的却不知道该读哪里。php://input是唯一能拿到原始加密字符串的地方。幂等校验怎么做微信可能重复推送同一笔通知。我们在数据库订单表加一个wechatpay_notify_id字段对应通知体中id字段每次处理前先查是否存在相同id的记录存在则直接返回成功不再重复更新状态。这是生产环境必备逻辑否则用户可能被重复扣款。4. 避坑指南5 条血泪经验总结每一条都曾让我重启三次服务器微信支付 v3 的坑不在文档里而在你第一次curl -v抓包看到401时的绝望。以下是我在三个不同项目中踩过的、最典型、最高频、最反直觉的 5 个坑附带现象、根因与可立即执行的解决方案。4.1 现象WechatPayHttpClient::request()抛出401 Unauthorized但Authorization头看起来完全正确原因$body参数传入了json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)导致 JSON 中出现换行符\n和缩进空格与微信服务端构造的签名原文不一致。解决永远使用json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)且不要加JSON_PRETTY_PRINT。上线前用echo json_encode($data, JSON_UNESCAPED_UNICODE); die;打印原始字符串肉眼确认无换行无空格。4.2 现象CertificateManager::refresh()执行后platform_cert.pem文件为空后续所有验签失败原因file_put_contents()写入时storage/certs/目录权限不足如755但属主不是 Web 进程用户或磁盘满或 SELinux 启用导致写入被拦截。解决ls -ld storage/certs/确认属主与 Web 进程用户一致如www-data:www-datadf -h检查磁盘空间setsebool -P httpd_can_network_connect 1CentOS/RHEL在CertificateManager::refresh()开头加error_log(Writing to {$this-certPath}, 0);确认是否走到写入逻辑。4.3 现象NotifyHandler::handle()中file_get_contents(php://input)返回空字符串原因Nginx 配置中client_max_body_size小于微信通知体大小通常 1KB但极端情况可达 2KB或启用了fastcgi_buffering off导致 body 被丢弃。解决Nginx 配置中添加location /notify.php { client_max_body_size 10k; fastcgi_pass php-fpm; # 确保以下两行存在 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param REQUEST_METHOD $request_method; }Apache 用户检查LimitRequestBody是否设为0不限制。4.4 现象AesGcmDecryptor::decrypt()抛出openssl_decrypt(): IV length must be exactly 12 bytes原因微信通知体中的nonce字段被错误地当作 IV 使用而实际 IV 是resource.associated_data的 base64 解码结果固定 12 字节nonce只是参与签名的随机串。解决仔细阅读微信文档「通知解密」章节IV必须从resource.associated_data字段取不是resource.nonce。resource.nonce仅用于验签原文构造。4.5 现象本地curl测试notify.php返回{code:SUCCESS,message:OK}但微信后台显示「通知失败」原因Web 服务器返回了 HTTP 200但响应体不是标准 JSON或包含了额外输出如var_dump()、print_r()、BOM 头、空白行。微信严格校验响应体是否为{code:SUCCESS,message:OK}多一个空格都不行。解决notify.php开头加?php declare(strict_types1);和ob_end_clean();所有日志写入文件禁止echo/var_dump()/print_r()响应前执行header(Content-Type: application/json; charsetutf-8);最终echo json_encode([code SUCCESS, message OK], JSON_UNESCAPED_UNICODE);后立即exit(0);。5. 本地调试与联调技巧用curl模拟微信请求把黑盒变成白盒线上问题难复现别急着看日志。我习惯在本地用curl构造一个完全符合微信规范的请求绕过前端、绕过 JSAPI直击支付核心链路。这招帮我定位过 80% 的签名/验签问题。5.1 用curl模拟统一下单请求JSAPI# 1. 先生成签名所需参数 TIMESTAMP$(date %s) NONCE$(openssl rand -hex 16 | cut -c1-32) # 2. 构造请求体注意无空格无换行 BODY{appid:wx1234567890abcdef,mchid:1900000100,description:test,out_trade_no:ORDER.date(ymdHis).rand(1000,9999).,notify_url:https://yourdomain.com/notify.php,amount:{total:1,currency:CNY},payer:{openid:o1234567890abcdef1234567890ab}} # 3. 计算签名用项目里的 Signer.php 或在线工具 # 假设签名结果为base64_encoded_signature_string # 4. 发起 curl 请求关键-H 头顺序不能错-d 必须是原始 JSON 字符串 curl -X POST https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi \ -H Content-Type: application/json \ -H Accept: application/json \ -H Authorization: WECHATPAY2-SHA256-RSA2048 mchid\1900000100\,nonce_str\${NONCE}\,signature\base64_encoded_signature_string\,timestamp\${TIMESTAMP}\ \ -H User-Agent: Mozilla/5.0 \ -d ${BODY} \ -v提示-v参数会打印完整请求/响应重点关注 Authorization:行是否与你计算的一致 HTTP/2 200是否返回以及响应体是否含prepay_id。如果返回401复制 Authorization:行用 Python 或 PHP 手动重算一遍签名原文逐字符比对。5.2 用curl模拟微信异步通知伪造 notify.php微信不会主动推送到你本地但你可以用curl把加密通知体 POST 到notify.php模拟真实场景# 1. 准备一个真实的加密通知体从微信后台「开发配置 → 查看回调地址」处复制一段历史通知或用项目里的 decryptor 生成测试数据 ENCRYPTED_BODY{algorithm:AEAD_AES_256_GCM,ciphertext:...,associated_data:...,nonce:...} # 2. 构造微信会发送的 headers注意WeChatpay-Serial 必须是当前有效的平台证书序列号 curl -X POST https://yourdomain.com/notify.php \ -H Content-Type: application/json \ -H WeChatpay-Serial: 1234567890ABCDEF1234567890ABCDEF \ -H WeChatpay-Timestamp: $(date %s) \ -H WeChatpay-Nonce: $(openssl rand -hex 16 | cut -c1-32) \ -H WeChatpay-Signature: base64_encoded_signature_of_above_headers_and_body \ -d ${ENCRYPTED_BODY} \ -v关键WeChatpay-Signature必须是你用当前platform_cert.pem和上述WeChatpay-Timestamp/WeChatpay-Nonce/ENCRYPTED_BODY三者拼接后重新计算的签名。你可以写个临时脚本调用Verifier::verify()确保本地验签通过再发 curl。5.3 日志驱动调试法给每个关键节点打唯一 trace_id我在WechatPayHttpClient构造时注入一个trace_id bin2hex(random_bytes(8))然后在所有日志中带上它// app/Http/WechatPayHttpClient.php public function request(string $method, string $uri, ?string $body null): array { $traceId $this-traceId; $this-logger-info(HTTP request start, compact(traceId, method, uri, body)); // ... 执行 curl ... $this-logger-info(HTTP request end, [ traceId $traceId, http_code $httpCode, response_body $responseBody, headers $headers, ]); return [...]; }这样当线上出现401时我只需在日志系统中搜索traceId: xxx就能串起「谁发起的请求 → 请求体长什么样 → 微信返回了什么 → 哪里验签失败」的完整链路不用再靠猜。6. 二次开发改造指南如何安全接入新支付场景、替换加密算法、对接内部订单系统这个工程不是终点而是你支付能力的起点。下面是我为某高校实验室项目做的三次关键改造每一步都经过生产验证你可以直接抄作业。6.1 支持「合单支付」在PayService中新增unifyOrder()方法微信 v3 合单支付/v3/combine-transactions/jsapi与普通 JSAPI 下单参数高度相似只需扩展PayService?php // app/Service/PayService.php public function unifyOrder(array $unifyData): array { $uri /v3/combine-transactions/jsapi; // 合单数据结构注意sub_orders 是数组每个元素是独立订单 $body [ combine_appid $unifyData[combine_appid], combine_mchid $unifyData[combine_mchid], sub_orders $unifyData[sub_orders], // [ [mchid1900000100,transaction_idxxx], ...] notify_url $unifyData[notify_url], combine_out_trade_no $unifyData[combine_out_trade_no], ]; // 签名、请求逻辑复用原有方法 return $this-httpClient-request(POST, $uri, json_encode($body, JSON_UNESCAPED_UNICODE)); }关键点sub_orders中每个子订单的mchid必须已在微信开放平台完成「合单支付」授权combine_out_trade_no必须全局唯一建议用date(ymd).uniqid()合单通知的resource字段结构与普通通知不同NotifyHandler需增加分支解析逻辑见下表。通知类型resource.objectresource.out_trade_noresource.combine_out_trade_no解析方式普通 JSAPItransaction✅ 存在❌ 不存在读resource.out_trade_no合单支付combine_transaction❌ 不存在✅ 存在读resource.combine_out_trade_no再查resource.sub_transactions获取各子单状态6.2 替换为国密 SM2 签名合规要求场景某政务项目强制要求 SM2。我们不改核心流程只替换Signer和Verifier// 新建 app/Crypto/Sm2Signer.php class Sm2Signer { public function sign(string $message, string $privateKeyPath): string { // 调用国密 SDK如 gmssl执行 SM2 签名 // $sm2 new GmSSL(); // return $sm2-sm2_sign($message, $privateKeyPath); } } // 修改 PayService 构造函数根据配置动态注入 if (config(wechatpay.use_sm2)) { $signer new Sm2Signer(config(wechatpay.sm2_private_key_path)); } else { $signer new Signer(config(wechatpay.private_key_path)); }注意微信官方不支持SM2此改造仅适用于微信不参与签名验签的私有支付网关场景。若仍走微信通道必须维持 RSA-SHA256。6.3 对接内部订单系统解耦NotifyHandler与数据库操作原始NotifyHandler::handle()本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑