{"id":19499671,"url":"https://github.com/wechatpay-apiv3/wechatpay-php","last_synced_at":"2025-05-14T08:05:45.586Z","repository":{"id":38323450,"uuid":"377110659","full_name":"wechatpay-apiv3/wechatpay-php","owner":"wechatpay-apiv3","description":"微信支付 APIv3 的官方 PHP Library，同时也支持 APIv2","archived":false,"fork":false,"pushed_at":"2025-01-26T14:31:18.000Z","size":515,"stargazers_count":541,"open_issues_count":2,"forks_count":104,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-04-15T01:54:18.909Z","etag":null,"topics":["api-client","php","sdk","wechatpay","wechatpay-apiv3"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wechatpay-apiv3.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-15T09:39:45.000Z","updated_at":"2025-04-10T10:26:32.000Z","dependencies_parsed_at":"2024-11-17T23:00:51.912Z","dependency_job_id":"49a6ea9a-fcad-437d-9ba4-e9d37cedec5c","html_url":"https://github.com/wechatpay-apiv3/wechatpay-php","commit_stats":{"total_commits":365,"total_committers":10,"mean_commits":36.5,"dds":0.0931506849315068,"last_synced_commit":"2cabc8a15136050c4ee61083cd24959114756a03"},"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wechatpay-apiv3%2Fwechatpay-php","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wechatpay-apiv3%2Fwechatpay-php/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wechatpay-apiv3%2Fwechatpay-php/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wechatpay-apiv3%2Fwechatpay-php/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wechatpay-apiv3","download_url":"https://codeload.github.com/wechatpay-apiv3/wechatpay-php/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254101613,"owners_count":22014909,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-client","php","sdk","wechatpay","wechatpay-apiv3"],"created_at":"2024-11-10T22:05:36.287Z","updated_at":"2025-05-14T08:05:40.569Z","avatar_url":"https://github.com/wechatpay-apiv3.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 微信支付 WeChatPay OpenAPI SDK\n\n[A]Sync Chainable WeChatPay v2\u0026v3's OpenAPI SDK for PHP\n\n[![GitHub actions](https://github.com/wechatpay-apiv3/wechatpay-php/workflows/CI/badge.svg)](https://github.com/wechatpay-apiv3/wechatpay-php/actions)\n[![Packagist Stars](https://img.shields.io/packagist/stars/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)\n[![Packagist Downloads](https://img.shields.io/packagist/dm/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)\n[![Packagist Version](https://img.shields.io/packagist/v/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)\n[![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)\n[![Packagist License](https://img.shields.io/packagist/l/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)\n\n## 概览\n\n基于 [Guzzle HTTP Client](http://docs.guzzlephp.org/) 的微信支付 PHP 开发库。\n\n### 功能介绍\n\n1. 微信支付 APIv2 和 APIv3 的 Guzzle HTTP 客户端，支持 [同步](#同步请求) 或 [异步](#异步请求) 发送请求，并自动进行请求签名和应答验签\n\n1. [链式实现的 URI Template](#链式-uri-template)\n\n1. [敏感信息加解密](#敏感信息加解密)\n\n1. [回调通知](#回调通知)的验签和解密\n\n## 项目状态\n\n当前版本为 `1.4.12` 版。\n项目版本遵循 [语义化版本号](https://semver.org/lang/zh-CN/)。\n如果你使用的版本 `\u003c=v1.3.2`，升级前请参考 [升级指南](UPGRADING.md)。\n\n## 环境要求\n\n项目支持的环境如下：\n\n+ Guzzle 7.0，PHP \u003e= 7.2.5\n+ Guzzle 6.5，PHP \u003e= 7.1.2\n\n我们推荐使用目前处于 [Active Support](https://www.php.net/supported-versions.php) 阶段的 PHP 8 和 Guzzle 7。\n\n## 安装\n\n推荐使用 PHP 包管理工具 [Composer](https://getcomposer.org/) 安装 SDK：\n\n```shell\ncomposer require wechatpay/wechatpay\n```\n\n## 开始\n\n:information_source: 以下是 [微信支付 API v3](https://pay.weixin.qq.com/docs/merchant/development/interface-rules/introduction.html) 的指引。如果你是 API v2 的使用者，请看 [README_APIv2](README_APIv2.md)。\n\n### 概念\n\n+ **商户 API 证书**，是用来证实商户身份的。证书中包含商户号、证书序列号、证书有效期等信息，由证书授权机构（Certificate Authority ，简称 CA）签发，以防证书被伪造或篡改。详情见 [什么是商户API证书？如何获取商户API证书？](https://kf.qq.com/faq/161222NneAJf161222U7fARv.html) 。\n\n+ **商户 API 私钥**。你申请商户 API 证书时，会生成商户私钥，并保存在本地证书文件夹的文件 apiclient_key.pem 中。为了证明 API 请求是由你发送的，你应使用商户 API 私钥对请求进行签名。\n\n  \u003e :key: 不要把私钥文件暴露在公共场合，如上传到 Github，写在 App 代码中等。\n\n+ **微信支付平台证书**。微信支付平台证书是指：由微信支付负责申请，包含微信支付平台标识、公钥信息的证书。你需使用微信支付平台证书中的公钥验证 API 应答和回调通知的签名。\n\n  \u003e :bookmark: 通用的 composer 命令，像安装依赖包一样 [下载平台证书](#如何下载平台证书) 文件，供SDK初始化使用。\n\n+ **证书序列号**。每个证书都有一个由 CA 颁发的唯一编号，即证书序列号。\n\n+ **微信支付公钥**，用于应答及回调通知的数据签名，可在 [微信支付商户平台](https://pay.weixin.qq.com) -\u003e 账户中心 -\u003e API安全 直接下载。\n\n+ **微信支付公钥ID**，是微信支付公钥的唯一标识，可在 [微信支付商户平台](https://pay.weixin.qq.com) -\u003e 账户中心 -\u003e API安全 直接查看。\n\n### 初始化一个APIv3客户端\n\n```php\n\u003c?php\n\nrequire_once('vendor/autoload.php');\n\nuse WeChatPay\\Builder;\nuse WeChatPay\\Crypto\\Rsa;\n\n// 设置参数\n\n// 商户号\n$merchantId = '190000****';\n\n// 从本地文件中加载「商户API私钥」，「商户API私钥」会用来生成请求的签名\n$merchantPrivateKeyFilePath = 'file:///path/to/merchant/apiclient_key.pem';\n$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);\n\n// 「商户API证书」的「证书序列号」\n$merchantCertificateSerial = '3775B6A45ACD588826D15E583A95F5DD********';\n\n// 从本地文件中加载「微信支付平台证书」，可由内置CLI工具下载到，用来验证微信支付应答的签名\n$platformCertificateFilePath  = 'file:///path/to/wechatpay/certificate.pem';\n$onePlatformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);\n\n// 「微信支付平台证书」的「平台证书序列号」\n// 可以从「微信支付平台证书」文件解析，也可以在 商户平台 -\u003e 账户中心 -\u003e API安全 查询到\n$platformCertificateSerial = '7132D72A03E93CDDF8C03BBD1F37EEDF********';\n\n// 从本地文件中加载「微信支付公钥」，用来验证微信支付应答的签名\n$platformPublicKeyFilePath    = 'file:///path/to/wechatpay/publickey.pem';\n$twoPlatformPublicKeyInstance = Rsa::from($platformPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);\n\n// 「微信支付公钥」的「微信支付公钥ID」\n// 需要在 商户平台 -\u003e 账户中心 -\u003e API安全 查询\n$platformPublicKeyId = 'PUB_KEY_ID_01142321349124100000000000********';\n\n// 构造一个 APIv3 客户端实例\n$instance = Builder::factory([\n    'mchid'      =\u003e $merchantId,\n    'serial'     =\u003e $merchantCertificateSerial,\n    'privateKey' =\u003e $merchantPrivateKeyInstance,\n    'certs'      =\u003e [\n        $platformCertificateSerial =\u003e $onePlatformPublicKeyInstance,\n        $platformPublicKeyId       =\u003e $twoPlatformPublicKeyInstance,\n    ],\n]);\n```\n\n### 示例，第一个请求：查询「微信支付平台证书」\n\n```php\n// 发送请求\ntry {\n    $resp = $instance-\u003echain('v3/certificates')-\u003eget(\n        /** @see https://docs.guzzlephp.org/en/stable/request-options.html#debug */\n        // ['debug' =\u003e true] // 调试模式\n    );\n    echo (string) $resp-\u003egetBody(), PHP_EOL;\n} catch(\\Exception $e) {\n    // 进行异常捕获并进行错误判断处理\n    echo $e-\u003egetMessage(), PHP_EOL;\n    if ($e instanceof \\GuzzleHttp\\Exception\\RequestException \u0026\u0026 $e-\u003ehasResponse()) {\n        $r = $e-\u003egetResponse();\n        echo $r-\u003egetStatusCode() . ' ' . $r-\u003egetReasonPhrase(), PHP_EOL;\n        echo (string) $r-\u003egetBody(), PHP_EOL, PHP_EOL, PHP_EOL;\n    }\n    echo $e-\u003egetTraceAsString(), PHP_EOL;\n}\n```\n\n当程序进入「异常捕获」逻辑，输出形如：\n\n```json\n{\n    \"code\": \"RESOURCE_NOT_EXISTS\",\n    \"message\": \"无可用的平台证书，请在商户平台-API安全申请使用微信支付公钥。可查看指引https://pay.weixin.qq.com/docs/merchant/products/platform-certificate/wxp-pub-key-guide.html\"\n}\n```\n\n即表示商户仅能运行在「微信支付公钥」模式，初始化即无需读取及配置`$platformCertificateSerial`及`$onePlatformPublicKeyInstance`等信息。\n\n## 文档\n\n### 同步请求\n\n使用客户端提供的 `get`、`put`、`post`、`patch` 或 `delete` 方法发送同步请求。以 [Native支付下单](https://pay.weixin.qq.com/docs/merchant/apis/native-payment/direct-jsons/native-prepay.html) 为例。\n\n```php\ntry {\n    $resp = $instance\n    -\u003echain('v3/pay/transactions/native')\n    -\u003epost(['json' =\u003e [\n        'mchid'        =\u003e '1900006XXX',\n        'out_trade_no' =\u003e 'native12177525012014070332333',\n        'appid'        =\u003e 'wxdace645e0bc2cXXX',\n        'description'  =\u003e 'Image形象店-深圳腾大-QQ公仔',\n        'notify_url'   =\u003e 'https://weixin.qq.com/',\n        'amount'       =\u003e [\n            'total'    =\u003e 1,\n            'currency' =\u003e 'CNY'\n        ],\n    ]]);\n\n    echo $resp-\u003egetStatusCode(), PHP_EOL;\n    echo (string) $resp-\u003egetBody(), PHP_EOL;\n} catch (\\Exception $e) {\n    // 进行错误处理\n    echo $e-\u003egetMessage(), PHP_EOL;\n    if ($e instanceof \\GuzzleHttp\\Exception\\RequestException \u0026\u0026 $e-\u003ehasResponse()) {\n        $r = $e-\u003egetResponse();\n        echo $r-\u003egetStatusCode() . ' ' . $r-\u003egetReasonPhrase(), PHP_EOL;\n        echo (string) $r-\u003egetBody(), PHP_EOL, PHP_EOL, PHP_EOL;\n    }\n    echo $e-\u003egetTraceAsString(), PHP_EOL;\n}\n```\n\n请求成功后，你会获得一个 `GuzzleHttp\\Psr7\\Response` 的应答对象。\n阅读 Guzzle 文档 [Using Response](https://docs.guzzlephp.org/en/stable/quickstart.html#using-responses) 进一步了解如何访问应答内的信息。\n\n### 异步请求\n\n使用客户端提供的 `getAsync`、`putAsync`、`postAsync`、`patchAsync` 或 `deleteAsync` 方法发送异步请求。以 [退款申请](https://pay.weixin.qq.com/docs/merchant/apis/native-payment/create.html) 为例。\n\n```php\n$promise = $instance\n-\u003echain('v3/refund/domestic/refunds')\n-\u003epostAsync([\n    'json' =\u003e [\n        'transaction_id' =\u003e '1217752501201407033233368018',\n        'out_refund_no'  =\u003e '1217752501201407033233368018',\n        'amount'         =\u003e [\n            'refund'   =\u003e 888,\n            'total'    =\u003e 888,\n            'currency' =\u003e 'CNY',\n        ],\n    ],\n])\n-\u003ethen(static function($response) {\n    // 正常逻辑回调处理\n    echo (string) $response-\u003egetBody(), PHP_EOL;\n    return $response;\n})\n-\u003eotherwise(static function($e) {\n    // 异常错误处理\n    echo $e-\u003egetMessage(), PHP_EOL;\n    if ($e instanceof \\GuzzleHttp\\Exception\\RequestException \u0026\u0026 $e-\u003ehasResponse()) {\n        $r = $e-\u003egetResponse();\n        echo $r-\u003egetStatusCode() . ' ' . $r-\u003egetReasonPhrase(), PHP_EOL;\n        echo (string) $r-\u003egetBody(), PHP_EOL, PHP_EOL, PHP_EOL;\n    }\n    echo $e-\u003egetTraceAsString(), PHP_EOL;\n});\n// 同步等待\n$promise-\u003ewait();\n\n```\n\n`[get|post|put|patch|delete]Async` 返回的是 [Guzzle Promises](https://github.com/guzzle/promises)。你可以做两件事：\n\n+ 成功时使用 `then()` 处理得到的 `Psr\\Http\\Message\\ResponseInterface`，（可选地）将它传给下一个 `then()`\n+ 失败时使用 `otherwise()` 处理异常\n\n最后使用 `wait()` 等待请求执行完成。\n\n### 同步还是异步\n\n对于大部分开发者，我们建议使用同步的模式，因为它更加易于理解。\n\n如果你是具有异步编程基础的开发者，在某些连续调用 API 的场景，将多个操作通过 `then()` 流式串联起来会是一种优雅的实现方式。例如 [以函数链的形式流式下载交易帐单](https://developers.weixin.qq.com/community/pay/article/doc/000ec4521086b85fb81d6472a51013)。\n\n## 链式 URI Template\n\n[URI Template](https://www.rfc-editor.org/rfc/rfc6570.html) 是表达 URI 中变量的一种方式。微信支付 API 使用这种方式表示 URL Path 中的单号或者 ID。\n\n```\n# 使用微信支付订单号查询订单\nGET /v3/pay/transactions/id/{transaction_id}\n\n# 使用商户订单号查询订单\nGET /v3/pay/transactions/out-trade-no/{out_trade_no}\n```\n\n使用 [链式](https://en.wikipedia.org/wiki/Method_chaining) URI Template，你能像书写代码一样流畅地书写 URL，轻松地输入路径并传递 URL 参数。配置接口描述包后还能开启 [IDE提示](https://github.com/TheNorthMemory/wechatpay-openapi)。\n\n链式串联的基本单元是 URI Path 中的 [segments](https://www.rfc-editor.org/rfc/rfc3986.html#section-3.3)，`segments` 之间以 `-\u003e` 连接。连接的规则如下：\n\n+ 普通 segment\n  + 直接书写。例如 `v3-\u003epay-\u003etransactions-\u003enative`\n  + 使用 `chain()`。例如 `chain('v3/pay/transactions/native')`\n+ 包含连字号(-)的 segment\n  + 使用驼峰 camelCase 风格书写。例如 `merchant-service` 可写成 `merchantService`\n  + 使用 `{'foo-bar'}` 方式书写。例如 `{'merchant-service'}`\n+ Path 变量。URL 中的 Path 变量应使用这种写法，避免自行组装或者使用 `chain()`，导致大小写处理错误\n  + **推荐使用** `_variable_name_` 方式书写，支持 IDE 提示。例如 `v3-\u003epay-\u003etransactions-\u003eid-\u003e_transaction_id_`。\n  + 使用 `{'{variable_name}'}` 方式书写。例如 `v3-\u003epay-\u003etransactions-\u003eid-\u003e{'{transaction_id}'}`\n+ 请求的 `HTTP METHOD` 作为链式最后的执行方法。例如 `v3-\u003epay-\u003etransactions-\u003enative-\u003epost([ ... ])`\n+ Path 变量的值，以同名参数传入执行方法\n+ Query 参数，以名为 `query` 的参数传入执行方法\n\n以 [查询订单](https://pay.weixin.qq.com/docs/merchant/apis/native-payment/query-by-wx-trade-no.html) `GET` 方法为例：\n\n```php\n$promise = $instance\n-\u003ev3-\u003epay-\u003etransactions-\u003eid-\u003e_transaction_id_\n-\u003egetAsync([\n    // Query 参数\n    'query' =\u003e ['mchid' =\u003e '1230000109'],\n    // 变量名 =\u003e 变量值\n    'transaction_id' =\u003e '1217752501201407033233368018',\n]);\n```\n\n以 [关闭订单](https://pay.weixin.qq.com/docs/merchant/apis/native-payment/close-order.html) `POST` 方法为例：\n\n```php\n$promise = $instance\n-\u003ev3-\u003epay-\u003etransactions-\u003eoutTradeNo-\u003e_out_trade_no_-\u003eclose\n-\u003epostAsync([\n    // 请求消息\n    'json' =\u003e ['mchid' =\u003e '1230000109'],\n    // 变量名 =\u003e 变量值\n    'out_trade_no' =\u003e '1217752501201407033233368018',\n]);\n```\n\n## 更多例子\n\n### [视频文件上传](https://pay.weixin.qq.com/docs/partner/apis/contracted-merchant-application/video-upload.html)\n\n```php\n// 参考上述指引说明，并引入 `MediaUtil` 正常初始化，无额外条件\nuse WeChatPay\\Util\\MediaUtil;\n// 实例化一个媒体文件流，注意文件后缀名需符合接口要求\n$media = new MediaUtil('/your/file/path/video.mp4');\n\n$resp = $instance-\n\u003echain('v3/merchant/media/video_upload')\n-\u003epost([\n    'body'    =\u003e $media-\u003egetStream(),\n    'headers' =\u003e [\n        'content-type' =\u003e $media-\u003egetContentType(),\n    ]\n]);\n```\n\n### [营销图片上传](https://pay.weixin.qq.com/docs/partner/apis/cash-coupons/upload-image.html)\n\n```php\nuse WeChatPay\\Util\\MediaUtil;\n$media = new MediaUtil('/your/file/path/image.jpg');\n$resp = $instance\n-\u003ev3-\u003emarketing-\u003efavor-\u003emedia-\u003eimageUpload\n-\u003epost([\n    'body'    =\u003e $media-\u003egetStream(),\n    'headers' =\u003e [\n        'Content-Type' =\u003e $media-\u003egetContentType(),\n    ]\n]);\n```\n\n## 敏感信息加/解密\n\n为了保证通信过程中敏感信息字段（如用户的住址、银行卡号、手机号码等）的机密性，\n\n+ 微信支付要求加密上送的敏感信息\n+ 微信支付会加密下行的敏感信息\n\n下面以 [特约商户进件](https://pay.weixin.qq.com/docs/partner/apis/contracted-merchant-application/applyment/submit.html) 为例，演示如何进行 [敏感信息加解密](https://pay.weixin.qq.com/docs/partner/development/interface-rules/sensitive-data-encryption.html)。\n\n```php\nuse WeChatPay\\Crypto\\Rsa;\n// 做一个匿名方法，供后续方便使用，$platformPublicKeyInstance 见初始化章节\n$encryptor = static function(string $msg) use ($platformPublicKeyInstance): string {\n    return Rsa::encrypt($msg, $platformPublicKeyInstance);\n};\n\n$resp = $instance\n-\u003echain('v3/applyment4sub/applyment/')\n-\u003epost([\n    'json' =\u003e [\n        'business_code' =\u003e 'APL_98761234',\n        'contact_info'  =\u003e [\n            'contact_name'      =\u003e $encryptor('张三'),\n            'contact_id_number' =\u003e $encryptor('110102YYMMDD888X'),\n            'mobile_phone'      =\u003e $encryptor('13000000000'),\n            'contact_email'     =\u003e $encryptor('abc123@example.com'),\n        ],\n        //...\n    ],\n    'headers' =\u003e [\n        // $platformCertificateSerialOrPublicKeyId 见初始化章节\n        'Wechatpay-Serial' =\u003e $platformCertificateSerialOrPublicKeyId,\n    ],\n]);\n```\n\n## 签名\n\n你可以使用 `Rsa::sign()` 计算调起支付时所需参数签名。以 [JSAPI支付](https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/jsapi-transfer-payment.html) 为例。\n\n```php\nuse WeChatPay\\Formatter;\nuse WeChatPay\\Crypto\\Rsa;\n\n$merchantPrivateKeyFilePath = 'file:///path/to/merchant/apiclient_key.pem';\n$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);\n\n$params = [\n    'appId'     =\u003e 'wx8888888888888888',\n    'timeStamp' =\u003e (string)Formatter::timestamp(),\n    'nonceStr'  =\u003e Formatter::nonce(),\n    'package'   =\u003e 'prepay_id=wx201410272009395522657a690389285100',\n];\n$params += ['paySign' =\u003e Rsa::sign(\n    Formatter::joinedByLineFeed(...array_values($params)),\n    $merchantPrivateKeyInstance\n), 'signType' =\u003e 'RSA'];\n\necho json_encode($params);\n```\n\n## 回调通知\n\n回调通知受限于开发者/商户所使用的`WebServer`有很大差异，这里只给出开发指导步骤，供参考实现。\n\n1. 从请求头部`Headers`，拿到`Wechatpay-Signature`、`Wechatpay-Nonce`、`Wechatpay-Timestamp`、`Wechatpay-Serial`及`Request-ID`，商户侧`Web`解决方案可能有差异，请求头可能大小写不敏感，请根据自身应用来定；\n2. 获取请求`body`体的`JSON`纯文本；\n3. 检查通知消息头标记的`Wechatpay-Timestamp`偏移量是否在5分钟之内；\n4. 调用`SDK`内置方法，[构造验签名串](https://pay.weixin.qq.com/docs/merchant/development/verify-signature-overview/overview-signature-and-verification.html) 然后经`Rsa::verfify`验签；\n5. 消息体需要解密的，调用`SDK`内置方法解密；\n6. 如遇到问题，请拿`Request-ID`点击[这里](https://support.pay.weixin.qq.com/online-service?utm_source=github\u0026utm_medium=wechatpay-php\u0026utm_content=apiv3)，联系官方在线技术支持；\n\n样例代码如下：\n\n```php\nuse WeChatPay\\Crypto\\Rsa;\nuse WeChatPay\\Crypto\\AesGcm;\nuse WeChatPay\\Formatter;\n\n$inWechatpaySignature = '';// 请根据实际情况获取\n$inWechatpayTimestamp = '';// 请根据实际情况获取\n$inWechatpaySerial = '';// 请根据实际情况获取\n$inWechatpayNonce = '';// 请根据实际情况获取\n$inBody = '';// 请根据实际情况获取，例如: file_get_contents('php://input');\n\n$apiv3Key = '';// 在商户平台上设置的APIv3密钥\n\n// 根据通知的平台证书序列号，查询本地平台证书文件，\n// 假定为 `/path/to/wechatpay/inWechatpaySerial.pem`\n$platformPublicKeyInstance = Rsa::from('file:///path/to/wechatpay/inWechatpaySerial.pem', Rsa::KEY_TYPE_PUBLIC);\n\n// 检查通知时间偏移量，允许5分钟之内的偏移\n$timeOffsetStatus = 300 \u003e= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);\n$verifiedStatus = Rsa::verify(\n    // 构造验签名串\n    Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),\n    $inWechatpaySignature,\n    $platformPublicKeyInstance\n);\nif ($timeOffsetStatus \u0026\u0026 $verifiedStatus) {\n    // 转换通知的JSON文本消息为PHP Array数组\n    $inBodyArray = (array)json_decode($inBody, true);\n    // 使用PHP7的数据解构语法，从Array中解构并赋值变量\n    ['resource' =\u003e [\n        'ciphertext'      =\u003e $ciphertext,\n        'nonce'           =\u003e $nonce,\n        'associated_data' =\u003e $aad\n    ]] = $inBodyArray;\n    // 加密文本消息解密\n    $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);\n    // 把解密后的文本转换为PHP Array数组\n    $inBodyResourceArray = (array)json_decode($inBodyResource, true);\n    // print_r($inBodyResourceArray);// 打印解密后的结果\n}\n```\n\n## 异常处理\n\n`Guzzle` 默认已提供基础中间件`\\GuzzleHttp\\Middleware::httpErrors`来处理异常，文档可见[这里](https://docs.guzzlephp.org/en/stable/quickstart.html#exceptions)。\n本SDK自`v1.1`对异常处理做了微调，各场景抛送出的异常如下：\n\n+ `HTTP`网络错误，如网络连接超时、DNS解析失败等，送出`\\GuzzleHttp\\Exception\\RequestException`；\n+ 服务器端返回了 `5xx HTTP` 状态码，送出`\\GuzzleHttp\\Exception\\ServerException`;\n+ 服务器端返回了 `4xx HTTP` 状态码，送出`\\GuzzleHttp\\Exception\\ClientException`;\n+ 服务器端返回了 `30x HTTP` 状态码，如超出SDK客户端重定向设置阈值，送出`\\GuzzleHttp\\Exception\\TooManyRedirectsException`;\n+ 服务器端返回了 `20x HTTP` 状态码，如SDK客户端逻辑处理失败，例如应答签名验证失败，送出`\\GuzzleHttp\\Exception\\RequestException`；\n+ 请求签名准备阶段，`HTTP`请求未发生之前，如PHP环境异常、商户私钥异常等，送出`\\UnexpectedValueException`;\n+ 初始化时，如把`商户证书序列号`配置成`平台证书序列号`，送出`\\InvalidArgumentException`;\n\n以上示例代码，均含有`catch`及`otherwise`错误处理场景示例，测试用例也覆盖了[5xx/4xx/20x异常](tests/ClientDecoratorTest.php)，开发者可参考这些代码逻辑进行错误处理。\n\n## 定制\n\n当默认的本地签名和验签方式不适合你的系统时，你可以通过实现`signer`或者`verifier`中间件来定制签名和验签，比如，你的系统把商户私钥集中存储，业务系统需通过远程调用进行签名。\n以下示例用来演示如何替换SDK内置中间件，来实现远程`请求签名`及`结果验签`，供商户参考实现。\n\n\u003cdetails\u003e\n\u003csummary\u003e例：内网集中签名/验签解决方案\u003c/summary\u003e\n\n```php\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Middleware;\nuse GuzzleHttp\\Exception\\RequestException;\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\n\n// 假设集中管理服务器接入点为内网`http://192.168.169.170:8080/`地址，并提供两个URI供签名及验签\n// - `/wechatpay-merchant-request-signature` 为请求签名\n// - `/wechatpay-response-merchant-validation` 为响应验签\n$client = new Client(['base_uri' =\u003e 'http://192.168.169.170:8080/']);\n\n// 请求参数签名，返回字符串形如`\\WeChatPay\\Formatter::authorization`返回的字符串\n$remoteSigner = function (RequestInterface $request) use ($client, $merchantId): string {\n    return (string)$client-\u003epost('/wechatpay-merchant-request-signature', ['json' =\u003e [\n        'mchid' =\u003e $merchantId,\n        'verb'  =\u003e $request-\u003egetMethod(),\n        'uri'   =\u003e $request-\u003egetRequestTarget(),\n        'body'  =\u003e (string)$request-\u003egetBody(),\n    ]])-\u003egetBody();\n};\n\n// 返回结果验签，返回可以是4xx,5xx，与远程验签应用约定返回字符串'OK'为验签通过\n$remoteVerifier = function (ResponseInterface $response) use ($client, $merchantId): string {\n    [$nonce]     = $response-\u003egetHeader('Wechatpay-Nonce');\n    [$serial]    = $response-\u003egetHeader('Wechatpay-Serial');\n    [$signature] = $response-\u003egetHeader('Wechatpay-Signature');\n    [$timestamp] = $response-\u003egetHeader('Wechatpay-Timestamp');\n    return (string)$client-\u003epost('/wechatpay-response-merchant-validation', ['json' =\u003e [\n        'mchid'     =\u003e $merchantId,\n        'nonce'     =\u003e $nonce,\n        'serial'    =\u003e $serial,\n        'signature' =\u003e $signature,\n        'timestamp' =\u003e $timestamp,\n        'body'      =\u003e (string)$response-\u003egetBody(),\n    ]])-\u003egetBody();\n};\n\n$stack = $instance-\u003egetDriver()-\u003eselect()-\u003egetConfig('handler');\n// 卸载SDK内置签名中间件\n$stack-\u003eremove('signer');\n// 注册内网远程请求签名中间件\n$stack-\u003ebefore('prepare_body', Middleware::mapRequest(\n    static function (RequestInterface $request) use ($remoteSigner): RequestInterface {\n        return $request-\u003ewithHeader('Authorization', $remoteSigner($request));\n    }\n), 'signer');\n// 卸载SDK内置验签中间件\n$stack-\u003eremove('verifier');\n// 注册内网远程请求验签中间件\n$stack-\u003ebefore('http_errors', static function (callable $handler) use ($remoteVerifier): callable {\n    return static function (RequestInterface $request, array $options = []) use ($remoteVerifier, $handler) {\n        return $handler($request, $options)-\u003ethen(\n            static function(ResponseInterface $response) use ($remoteVerifier, $request): ResponseInterface {\n                $verified = '';\n                try {\n                    $verified = $remoteVerifier($response);\n                } catch (\\Throwable $exception) {}\n                if ($verified === 'OK') { //远程验签约定，返回字符串`OK`作为验签通过\n                    throw new RequestException('签名验签失败', $request, $response, $exception ?? null);\n                }\n                return $response;\n            }\n        );\n    };\n}, 'verifier');\n\n// 链式/同步/异步请求APIv3即可，例如:\n$instance-\u003ev3-\u003ecertificates-\u003egetAsync()-\u003ethen(static function($res) { return $res-\u003egetBody(); })-\u003ewait();\n```\n\n\u003c/details\u003e\n\n## 常见问题\n\n### 如何下载平台证书？\n\n使用内置的[微信支付平台证书下载器](bin/README.md)。\n\n```bash\ncomposer exec CertificateDownloader.php -- -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}\n```\n\n微信支付平台证书下载后，下载器会用获得的`平台证书`对返回的消息进行验签。下载器同时开启了 `Guzzle` 的 `debug =\u003e true` 参数，方便查询请求/响应消息的基础调试信息。\n\nℹ️ [什么是APIv3密钥？如何设置？](https://kf.qq.com/faq/180830E36vyQ180830AZFZvu.html)\n\n### 证书和回调解密需要的AesGcm解密在哪里？\n\n请参考[AesGcm.php](src/Crypto/AesGcm.php)，例如内置的`平台证书`下载工具解密代码如下:\n\n```php\nAesGcm::decrypt($cert-\u003eciphertext, $apiv3Key, $cert-\u003enonce, $cert-\u003eassociated_data);\n```\n\n### 配合swoole使用时，上传文件接口报错\n\n建议升级至swoole 4.6+，swoole在 4.6.0 中增加了native-curl([swoole/swoole-src#3863](https://github.com/swoole/swoole-src/pull/3863))支持，我们测试能正常使用了。\n更详细的信息，请参考[#36](https://github.com/wechatpay-apiv3/wechatpay-guzzle-middleware/issues/36)。\n\n### 如何加载公/私钥和证书\n\n`v1.2`提供了统一的加载函数 `Rsa::from($thing, $type)`。\n\n- `Rsa::from($thing, $type)` 支持从文件/字符串加载公/私钥和证书，使用方法可参考 [RsaTest.php](tests/Crypto/RsaTest.php)\n- `Rsa::fromPkcs1`是个语法糖，支持加载 `PKCS#1` 格式的公/私钥，入参是 `base64` 字符串\n- `Rsa::fromPkcs8`是个语法糖，支持加载 `PKCS#8` 格式的私钥，入参是 `base64` 字符串\n- `Rsa::fromSpki`是个语法糖，支持加载 `SPKI` 格式的公钥，入参是 `base64` 字符串\n- `Rsa::pkcs1ToSpki`是个 `RSA公钥` 格式转换函数，入参是 `base64` 字符串\n\n### 如何计算商家券发券 API 的签名\n\n使用 `Hash::sign()`计算 APIv2 的签名，示例请参考 APIv2 文档的 [数据签名](README_APIv2.md#数据签名)。\n\n### 为什么 URL 上的变量 OpenID，请求时被替换成小写了？\n\n本 SDK 把 URL 中的大写视为包含连字号的 segment。请求时， `camelCase` 会替换为 `camel-case`。相关 issue 可参考 [#56](https://github.com/wechatpay-apiv3/wechatpay-php/issues/56)、 [#69](https://github.com/wechatpay-apiv3/wechatpay-php/issues/69)。\n\n为了避免大小写错乱，URL 中存在变量时的正确做法是：使用 [链式 URI Template](#%E9%93%BE%E5%BC%8F-uri-template) 的 Path 变量。比如：\n\n- **推荐写法** `-\u003ev3-\u003emarketing-\u003efavor-\u003eusers-\u003e_openid_-\u003ecoupons-\u003epost(['openid' =\u003e 'AbcdEF12345'])`\n- `-\u003ev3-\u003emarketing-\u003efavor-\u003eusers-\u003e{'{openid}'}-\u003ecoupons-\u003epost(['openid' =\u003e 'AbcdEF12345'])`\n- `-\u003echain('{+myurl}')-\u003epost(['myurl' =\u003e 'v3/marketing/favor/users/AbcdEF12345/coupons'])`\n- `-\u003e{'{+myurl}'}-\u003epost(['myurl' =\u003e 'v3/marketing/favor/users/AbcdEF12345/coupons'])`\n\n## 联系我们\n\n如果你发现了**BUG**或者有任何疑问、建议，请通过issue进行反馈。\n\n也欢迎访问我们的[开发者社区](https://developers.weixin.qq.com/community/pay)。\n\n## 链接\n\n+ [GuzzleHttp官方版本支持](https://docs.guzzlephp.org/en/stable/overview.html#requirements)\n+ [PHP官方版本支持](https://www.php.net/supported-versions.php)\n+ [变更历史](CHANGELOG.md)\n+ [升级指南](UPGRADING.md)\n+ \u003ca name=\"note-rfc3986\"\u003e\u003c/a\u003e [RFC3986](https://www.rfc-editor.org/rfc/rfc3986.html#section-3.3)\n  \u003e section-3.3 `segments`: A path consists of a sequence of path segments separated by a slash (\"/\") character.\n+ \u003ca name=\"note-rfc6570\"\u003e\u003ca\u003e [RFC6570](https://www.rfc-editor.org/rfc/rfc6570.html)\n+ [PHP密钥/证书参数 相关说明](https://www.php.net/manual/zh/openssl.certparams.php)\n\n## License\n\n[Apache-2.0 License](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwechatpay-apiv3%2Fwechatpay-php","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwechatpay-apiv3%2Fwechatpay-php","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwechatpay-apiv3%2Fwechatpay-php/lists"}