wechatpay-php

wechatpay-apiv3Apache-2.0活跃维护

微信支付官方 PHP SDK,封装 APIv3 与 APIv2 的签名、验签和请求流程,让 PHP 项目快速接入支付、退款、账单等接口。

微信支付 APIv3 的官方 PHP Library,同时也支持 APIv2

v1.5.0发布于 2026-09-18 07:45 UTC602star104fork同步于 2026-11-07 07:32 UTC

怎么上手

由 README 提炼,执行前请确认
composer require wechatpay/wechatpay
硬件要求按 README 说明

README 未说明,仅要求 PHP 环境:Guzzle 7.0 需 PHP >= 7.2.5,Guzzle 6.5 需 PHP >= 7.1.2

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

微信支付官方 PHP SDK,封装 APIv3 与 APIv2 的签名、验签和加解密

微信支付官方 PHP SDK,封装 APIv3 与 APIv2 的签名、验签和请求流程,让 PHP 项目快速接入支付、退款、账单等接口。

核心能力

  • 基于 Guzzle 的同步与异步请求客户端,自动完成请求签名和应答验签
  • 链式 URI Template 写法,按路径段拼出接口 URL 并传参
  • 提供敏感信息 RSA 加解密与调起支付参数签名工具
  • 支持回调通知的验签与密文解密
  • 内置 MediaUtil 处理图片、视频等媒体文件上传

容易踩的坑

需自行准备商户 API 私钥、商户证书序列号及微信支付平台证书或公钥,否则客户端无法初始化;平台证书可用内置 CLI 工具下载,但需先完成商户配置;版本 <=v1.3.2 升级前须参考 UPGRADING.md 做迁移

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v1.5.0
  1. v1.5.0最新正式版2026-09-18 · 2026-09-18 07:45 UTC

    What's Changed

    New Contributors

    Full Changelog: https://github.com/wechatpay-apiv3/wechatpay-php/compare/v1.4.12...v1.5.0

  2. v1.4.122025-01-26 · 2025-01-26 14:35 UTC
    查看更新说明
    • 修正APIv2特殊GET请求抛异常问题,相关记录见 #146, 感谢 @saber-x

    详情可查阅CHANGELOG

  3. v1.4.112024-12-27 · 2024-12-27 15:39 UTC
    查看更新说明
    • 精细化处理APIv2返回值,对于返回状态码及/或业务结果不为SUCCESS抛出异常处理;
    • 优化README关于微信支付平台证书及微信支付公钥章节内容及示例代码;

    详情可查阅CHANGELOG。

  4. v1.4.102024-09-19 · 2024-09-19 05:05 UTC
    查看更新说明
    • 客户端在RSA非对称加解密方案上,不再支持OPENSSL_PKCS1_PADDING填充模式,相关记录见这里;
    • 增加#[\SensitiveParameter]参数注解,加强信息安全;
    • 支持PHP8.4运行时;

    详情可查阅CHANGELOG。

  5. v1.4.92023-11-22 · 2023-11-22 11:32 UTC
    查看更新说明
    • 支持PHP8.3运行时

    详情可查阅CHANGELOG。

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    微信支付 WeChatPay OpenAPI SDK

    [A]Sync Chainable WeChatPay v2&v3's OpenAPI SDK for PHP

    GitHub actions Packagist Stars Packagist Downloads Packagist Version Packagist PHP Version Support Packagist License

    概览

    基于 Guzzle HTTP Client 的微信支付 PHP 开发库。

    功能介绍

    1. 微信支付 APIv2 和 APIv3 的 Guzzle HTTP 客户端,支持 同步 或 异步 发送请求,并自动进行请求签名和应答验签

    2. 链式实现的 URI Template

    3. 敏感信息加解密

    4. 回调通知的验签和解密

    项目状态

    当前版本为 1.5.0 版。 项目版本遵循 语义化版本号。 如果你使用的版本 <=v1.3.2,升级前请参考 升级指南。

    环境要求

    项目支持的环境如下:

    • Guzzle 7.0,PHP >= 7.2.5
    • Guzzle 6.5,PHP >= 7.1.2

    我们推荐使用目前处于 Active Support 阶段的 PHP 8 和 Guzzle 7。

    安装

    推荐使用 PHP 包管理工具 Composer 安装 SDK:

    composer require wechatpay/wechatpay
    

    开始

    :information_source: 以下是 微信支付 API v3 的指引。如果你是 API v2 的使用者,请看 README_APIv2。

    概念

    • 商户 API 证书,是用来证实商户身份的。证书中包含商户号、证书序列号、证书有效期等信息,由证书授权机构(Certificate Authority ,简称 CA)签发,以防证书被伪造或篡改。详情见 什么是商户API证书?如何获取商户API证书? 。

    • 商户 API 私钥。你申请商户 API 证书时,会生成商户私钥,并保存在本地证书文件夹的文件 apiclient_key.pem 中。为了证明 API 请求是由你发送的,你应使用商户 API 私钥对请求进行签名。

      :key: 不要把私钥文件暴露在公共场合,如上传到 Github,写在 App 代码中等。

    • 微信支付平台证书。微信支付平台证书是指:由微信支付负责申请,包含微信支付平台标识、公钥信息的证书。你需使用微信支付平台证书中的公钥验证 API 应答和回调通知的签名。

      :bookmark: 通用的 composer 命令,像安装依赖包一样 下载平台证书 文件,供SDK初始化使用。

    • 证书序列号。每个证书都有一个由 CA 颁发的唯一编号,即证书序列号。

    • 微信支付公钥,用于应答及回调通知的数据签名,可在 微信支付商户平台 -> 账户中心 -> API安全 直接下载。

    • 微信支付公钥ID,是微信支付公钥的唯一标识,可在 微信支付商户平台 -> 账户中心 -> API安全 直接查看。

    初始化一个APIv3客户端

    <?php
    
    require_once('vendor/autoload.php');
    
    use WeChatPay\Builder;
    use WeChatPay\Crypto\Rsa;
    
    // 设置参数
    
    // 商户号
    $merchantId = '190000****';
    
    // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
    $merchantPrivateKeyFilePath = 'file:///path/to/merchant/apiclient_key.pem';
    $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
    
    // 「商户API证书」的「证书序列号」
    $merchantCertificateSerial = '3775B6A45ACD588826D15E583A95F5DD********';
    
    // 从本地文件中加载「微信支付平台证书」,可由内置CLI工具下载到,用来验证微信支付应答的签名
    $platformCertificateFilePath  = 'file:///path/to/wechatpay/certificate.pem';
    $onePlatformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
    
    // 「微信支付平台证书」的「平台证书序列号」
    // 可以从「微信支付平台证书」文件解析,也可以在 商户平台 -> 账户中心 -> API安全 查询到
    $platformCertificateSerial = '7132D72A03E93CDDF8C03BBD1F37EEDF********';
    
    // 从本地文件中加载「微信支付公钥」,用来验证微信支付应答的签名
    $platformPublicKeyFilePath    = 'file:///path/to/wechatpay/publickey.pem';
    $twoPlatformPublicKeyInstance = Rsa::from($platformPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);
    
    // 「微信支付公钥」的「微信支付公钥ID」
    // 需要在 商户平台 -> 账户中心 -> API安全 查询
    $platformPublicKeyId = 'PUB_KEY_ID_01142321349124100000000000********';
    
    // 构造一个 APIv3 客户端实例
    $instance = Builder::factory([
        'mchid'      => $merchantId,
        'serial'     => $merchantCertificateSerial,
        'privateKey' => $merchantPrivateKeyInstance,
        'certs'      => [
            $platformCertificateSerial => $onePlatformPublicKeyInstance,
            $platformPublicKeyId       => $twoPlatformPublicKeyInstance,
        ],
    ]);
    

    示例,第一个请求:查询「微信支付平台证书」

    // 发送请求
    try {
        $resp = $instance->chain('v3/certificates')->get(
            /** @see https://docs.guzzlephp.org/en/stable/request-options.html#debug */
            // ['debug' => true] // 调试模式
        );
        echo (string) $resp->getBody(), PHP_EOL;
    } catch(\Exception $e) {
        // 进行异常捕获并进行错误判断处理
        echo $e->getMessage(), PHP_EOL;
        if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
            $r = $e->getResponse();
            echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
            echo (string) $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
        }
        echo $e->getTraceAsString(), PHP_EOL;
    }
    

    当程序进入「异常捕获」逻辑,输出形如:

    {
        "code": "RESOURCE_NOT_EXISTS",
        "message": "无可用的平台证书,请在商户平台-API安全申请使用微信支付公钥。可查看指引https://pay.weixin.qq.com/docs/merchant/products/platform-certificate/wxp-pub-key-guide.html"
    }
    

    即表示商户仅能运行在「微信支付公钥」模式,初始化即无需读取及配置$platformCertificateSerial及$onePlatformPublicKeyInstance等信息。

    文档

    同步请求

    使用客户端提供的 get、put、post、patch 或 delete 方法发送同步请求。以 Native支付下单 为例。

    try {
        $resp = $instance
        ->chain('v3/pay/transactions/native')
        ->post(['json' => [
            'mchid'        => '1900006XXX',
            'out_trade_no' => 'native12177525012014070332333',
            'appid'        => 'wxdace645e0bc2cXXX',
            'description'  => 'Image形象店-深圳腾大-QQ公仔',
            'notify_url'   => 'https://weixin.qq.com/',
            'amount'       => [
                'total'    => 1,
                'currency' => 'CNY'
            ],
        ]]);
    
        echo $resp->getStatusCode(), PHP_EOL;
        echo (string) $resp->getBody(), PHP_EOL;
    } catch (\Exception $e) {
        // 进行错误处理
        echo $e->getMessage(), PHP_EOL;
        if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
            $r = $e->getResponse();
            echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
            echo (string) $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
        }
        echo $e->getTraceAsString(), PHP_EOL;
    }
    

    请求成功后,你会获得一个 GuzzleHttp\Psr7\Response 的应答对象。 阅读 Guzzle 文档 Using Response 进一步了解如何访问应答内的信息。

    异步请求

    使用客户端提供的 getAsync、putAsync、postAsync、patchAsync 或 deleteAsync 方法发送异步请求。以 退款申请 为例。

    $promise = $instance
    ->chain('v3/refund/domestic/refunds')
    ->postAsync([
        'json' => [
            'transaction_id' => '1217752501201407033233368018',
            'out_refund_no'  => '1217752501201407033233368018',
            'amount'         => [
                'refund'   => 888,
                'total'    => 888,
                'currency' => 'CNY',
            ],
        ],
    ])
    ->then(static function($response) {
        // 正常逻辑回调处理
        echo (string) $response->getBody(), PHP_EOL;
        return $response;
    })
    ->otherwise(static function($e) {
        // 异常错误处理
        echo $e->getMessage(), PHP_EOL;
        if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
            $r = $e->getResponse();
            echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
            echo (string) $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
        }
        echo $e->getTraceAsString(), PHP_EOL;
    });
    // 同步等待
    $promise->wait();
    
    

    [get|post|put|patch|delete]Async 返回的是 Guzzle Promises。你可以做两件事:

    • 成功时使用 then() 处理得到的 Psr\Http\Message\ResponseInterface,(可选地)将它传给下一个 then()
    • 失败时使用 otherwise() 处理异常

    最后使用 wait() 等待请求执行完成。

    同步还是异步

    对于大部分开发者,我们建议使用同步的模式,因为它更加易于理解。

    如果你是具有异步编程基础的开发者,在某些连续调用 API 的场景,将多个操作通过 then() 流式串联起来会是一种优雅的实现方式。例如 以函数链的形式流式下载交易帐单。

    链式 URI Template

    URI Template 是表达 URI 中变量的一种方式。微信支付 API 使用这种方式表示 URL Path 中的单号或者 ID。

    # 使用微信支付订单号查询订单
    GET /v3/pay/transactions/id/{transaction_id}
    
    # 使用商户订单号查询订单
    GET /v3/pay/transactions/out-trade-no/{out_trade_no}
    

    使用 链式 URI Template,你能像书写代码一样流畅地书写 URL,轻松地输入路径并传递 URL 参数。配置接口描述包后还能开启 IDE提示。

    链式串联的基本单元是 URI Path 中的 segments,segments 之间以 -> 连接。连接的规则如下:

    • 普通 segment
      • 直接书写。例如 v3->pay->transactions->native
      • 使用 chain()。例如 chain('v3/pay/transactions/native')
    • 包含连字号(-)的 segment
      • 使用驼峰 camelCase 风格书写。例如 merchant-service 可写成 merchantService
      • 使用 {'foo-bar'} 方式书写。例如 {'merchant-service'}
    • Path 变量。URL 中的 Path 变量应使用这种写法,避免自行组装或者使用 chain(),导致大小写处理错误
      • 推荐使用 _variable_name_ 方式书写,支持 IDE 提示。例如 v3->pay->transactions->id->_transaction_id_。
      • 使用 {'{variable_name}'} 方式书写。例如 v3->pay->transactions->id->{'{transaction_id}'}
    • 请求的 HTTP METHOD 作为链式最后的执行方法。例如 v3->pay->transactions->native->post([ ... ])
    • Path 变量的值,以同名参数传入执行方法
    • Query 参数,以名为 query 的参数传入执行方法

    以 查询订单 GET 方法为例:

    $promise = $instance
    ->v3->pay->transactions->id->_transaction_id_
    ->getAsync([
        // Query 参数
        'query' => ['mchid' => '1230000109'],
        // 变量名 => 变量值
        'transaction_id' => '1217752501201407033233368018',
    ]);
    

    以 关闭订单 POST 方法为例:

    $promise = $instance
    ->v3->pay->transactions->outTradeNo->_out_trade_no_->close
    ->postAsync([
        // 请求消息
        'json' => ['mchid' => '1230000109'],
        // 变量名 => 变量值
        'out_trade_no' => '1217752501201407033233368018',
    ]);
    

    更多例子

    视频文件上传

    // 参考上述指引说明,并引入 `MediaUtil` 正常初始化,无额外条件
    use WeChatPay\Util\MediaUtil;
    // 实例化一个媒体文件流,注意文件后缀名需符合接口要求
    $media = new MediaUtil('/your/file/path/video.mp4');
    
    $resp = $instance-
    >chain('v3/merchant/media/video_upload')
    ->post([
        'body'    => $media->getStream(),
        'headers' => [
            'content-type' => $media->getContentType(),
        ]
    ]);
    

    营销图片上传

    use WeChatPay\Util\MediaUtil;
    $media = new MediaUtil('/your/file/path/image.jpg');
    $resp = $instance
    ->v3->marketing->favor->media->imageUpload
    ->post([
        'body'    => $media->getStream(),
        'headers' => [
            'Content-Type' => $media->getContentType(),
        ]
    ]);
    

    敏感信息加/解密

    为了保证通信过程中敏感信息字段(如用户的住址、银行卡号、手机号码等)的机密性,

    • 微信支付要求加密上送的敏感信息
    • 微信支付会加密下行的敏感信息

    下面以 特约商户进件 为例,演示如何进行 敏感信息加解密。

    use WeChatPay\Crypto\Rsa;
    // 做一个匿名方法,供后续方便使用,$platformPublicKeyInstance 见初始化章节
    $encryptor = static function(string $msg) use ($platformPublicKeyInstance): string {
        return Rsa::encrypt($msg, $platformPublicKeyInstance);
    };
    
    $resp = $instance
    ->chain('v3/applyment4sub/applyment/')
    ->post([
        'json' => [
            'business_code' => 'APL_98761234',
            'contact_info'  => [
                'contact_name'      => $encryptor('张三'),
                'contact_id_number' => $encryptor('110102YYMMDD888X'),
                'mobile_phone'      => $encryptor('13000000000'),
                'contact_email'     => $encryptor('[email protected]'),
            ],
            //...
        ],
        'headers' => [
            // $platformCertificateSerialOrPublicKeyId 见初始化章节
            'Wechatpay-Serial' => $platformCertificateSerialOrPublicKeyId,
        ],
    ]);
    

    签名

    你可以使用 Rsa::sign() 计算调起支付时所需参数签名。以 JSAPI支付 为例。

    use WeChatPay\Formatter;
    use WeChatPay\Crypto\Rsa;
    
    $merchantPrivateKeyFilePath = 'file:///path/to/merchant/apiclient_key.pem';
    $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);
    
    $params = [
        'appId'     => 'wx8888888888888888',
        'timeStamp' => (string)Formatter::timestamp(),
        'nonceStr'  => Formatter::nonce(),
        'package'   => 'prepay_id=wx201410272009395522657a690389285100',
    ];
    $params += ['paySign' => Rsa::sign(
        Formatter::joinedByLineFeed(...array_values($params)),
        $merchantPrivateKeyInstance
    ), 'signType' => 'RSA'];
    
    echo json_encode($params);
    

    回调通知

    回调通知受限于开发者/商户所使用的WebServer有很大差异,这里只给出开发指导步骤,供参考实现。

    1. 从请求头部Headers,拿到Wechatpay-Signature、Wechatpay-Nonce、Wechatpay-Timestamp、Wechatpay-Serial及Request-ID,商户侧Web解决方案可能有差异,请求头可能大小写不敏感,请根据自身应用来定;
    2. 获取请求body体的JSON纯文本;
    3. 检查通知消息头标记的Wechatpay-Timestamp偏移量是否在5分钟之内;
    4. 调用SDK内置方法,构造验签名串 然后经Rsa::verfify验签;
    5. 消息体需要解密的,调用SDK内置方法解密;
    6. 如遇到问题,请拿Request-ID点击这里,联系官方在线技术支持;

    样例代码如下:

    use WeChatPay\Crypto\Rsa;
    use WeChatPay\Crypto\AesGcm;
    use WeChatPay\Formatter;
    
    $inWechatpaySignature = '';// 请根据实际情况获取
    $inWechatpayTimestamp = '';// 请根据实际情况获取
    $inWechatpaySerial = '';// 请根据实际情况获取
    $inWechatpayNonce = '';// 请根据实际情况获取
    $inBody = '';// 请根据实际情况获取,例如: file_get_contents('php://input');
    
    $apiv3Key = '';// 在商户平台上设置的APIv3密钥
    
    // 根据通知的平台证书序列号,查询本地平台证书文件,
    // 假定为 `/path/to/wechatpay/inWechatpaySerial.pem`
    $platformPublicKeyInstance = Rsa::from('file:///path/to/wechatpay/inWechatpaySerial.pem', Rsa::KEY_TYPE_PUBLIC);
    
    // 检查通知时间偏移量,允许5分钟之内的偏移
    $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
    $verifiedStatus = Rsa::verify(
        // 构造验签名串
        Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
        $inWechatpaySignature,
        $platformPublicKeyInstance
    );
    if ($timeOffsetStatus && $verifiedStatus) {
        // 转换通知的JSON文本消息为PHP Array数组
        $inBodyArray = (array)json_decode($inBody, true);
        // 使用PHP7的数据解构语法,从Array中解构并赋值变量
        ['resource' => [
            'ciphertext'      => $ciphertext,
            'nonce'           => $nonce,
            'associated_data' => $aad
        ]] = $inBodyArray;
        // 加密文本消息解密
        $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
        // 把解密后的文本转换为PHP Array数组
        $inBodyResourceArray = (array)json_decode($inBodyResource, true);
        // print_r($inBodyResourceArray);// 打印解密后的结果
    }
    

    异常处理

    Guzzle 默认已提供基础中间件\GuzzleHttp\Middleware::httpErrors来处理异常,文档可见这里。 本SDK自v1.1对异常处理做了微调,各场景抛送出的异常如下:

    • HTTP网络错误,如网络连接超时、DNS解析失败等,送出\GuzzleHttp\Exception\RequestException;
    • 服务器端返回了 5xx HTTP 状态码,送出\GuzzleHttp\Exception\ServerException;
    • 服务器端返回了 4xx HTTP 状态码,送出\GuzzleHttp\Exception\ClientException;
    • 服务器端返回了 30x HTTP 状态码,如超出SDK客户端重定向设置阈值,送出\GuzzleHttp\Exception\TooManyRedirectsException;
    • 服务器端返回了 20x HTTP 状态码,如SDK客户端逻辑处理失败,例如应答签名验证失败,送出\GuzzleHttp\Exception\RequestException;
    • 请求签名准备阶段,HTTP请求未发生之前,如PHP环境异常、商户私钥异常等,送出\UnexpectedValueException;
    • 初始化时,如把商户证书序列号配置成平台证书序列号,送出\InvalidArgumentException;

    以上示例代码,均含有catch及otherwise错误处理场景示例,测试用例也覆盖了5xx/4xx/20x异常,开发者可参考这些代码逻辑进行错误处理。

    定制

    当默认的本地签名和验签方式不适合你的系统时,你可以通过实现signer或者verifier中间件来定制签名和验签,比如,你的系统把商户私钥集中存储,业务系统需通过远程调用进行签名。 以下示例用来演示如何替换SDK内置中间件,来实现远程请求签名及结果验签,供商户参考实现。

    use GuzzleHttp\Client;
    use GuzzleHttp\Middleware;
    use GuzzleHttp\Exception\RequestException;
    use Psr\Http\Message\RequestInterface;
    use Psr\Http\Message\ResponseInterface;
    
    // 假设集中管理服务器接入点为内网`http://192.168.169.170:8080/`地址,并提供两个URI供签名及验签
    // - `/wechatpay-merchant-request-signature` 为请求签名
    // - `/wechatpay-response-merchant-validation` 为响应验签
    $client = new Client(['base_uri' => 'http://192.168.169.170:8080/']);
    
    // 请求参数签名,返回字符串形如`\WeChatPay\Formatter::authorization`返回的字符串
    $remoteSigner = function (RequestInterface $request) use ($client, $merchantId): string {
        return (string)$client->post('/wechatpay-merchant-request-signature', ['json' => [
            'mchid' => $merchantId,
            'verb'  => $request->getMethod(),
            'uri'   => $request->getRequestTarget(),
            'body'  => (string)$request->getBody(),
        ]])->getBody();
    };
    
    // 返回结果验签,返回可以是4xx,5xx,与远程验签应用约定返回字符串'OK'为验签通过
    $remoteVerifier = function (ResponseInterface $response) use ($client, $merchantId): string {
        [$nonce]     = $response->getHeader('Wechatpay-Nonce');
        [$serial]    = $response->getHeader('Wechatpay-Serial');
        [$signature] = $response->getHeader('Wechatpay-Signature');
        [$timestamp] = $response->getHeader('Wechatpay-Timestamp');
        return (string)$client->post('/wechatpay-response-merchant-validation', ['json' => [
            'mchid'     => $merchantId,
            'nonce'     => $nonce,
            'serial'    => $serial,
            'signature' => $signature,
            'timestamp' => $timestamp,
            'body'      => (string)$response->getBody(),
        ]])->getBody();
    };
    
    $stack = $instance->getDriver()->select()->getConfig('handler');
    // 卸载SDK内置签名中间件
    $stack->remove('signer');
    // 注册内网远程请求签名中间件
    $stack->before('prepare_body', Middleware::mapRequest(
        static function (RequestInterface $request) use ($remoteSigner): RequestInterface {
            return $request->withHeader('Authorization', $remoteSigner($request));
        }
    ), 'signer');
    // 卸载SDK内置验签中间件
    $stack->remove('verifier');
    // 注册内网远程请求验签中间件
    $stack->before('http_errors', static function (callable $handler) use ($remoteVerifier): callable {
        return static function (RequestInterface $request, array $options = []) use ($remoteVerifier, $handler) {
            return $handler($request, $options)->then(
                static function(ResponseInterface $response) use ($remoteVerifier, $request): ResponseInterface {
                    $verified = '';
                    try {
                        $verified = $remoteVerifier($response);
                    } catch (\Throwable $exception) {}
                    if ($verified === 'OK') { //远程验签约定,返回字符串`OK`作为验签通过
                        throw new RequestException('签名验签失败', $request, $response, $exception ?? null);
                    }
                    return $response;
                }
            );
        };
    }, 'verifier');
    
    // 链式/同步/异步请求APIv3即可,例如:
    $instance->v3->certificates->getAsync()->then(static function($res) { return $res->getBody(); })->wait();
    

    常见问题

    如何下载平台证书?

    使用内置的微信支付平台证书下载器。

    composer exec CertificateDownloader.php -- -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
    

    微信支付平台证书下载后,下载器会用获得的平台证书对返回的消息进行验签。下载器同时开启了 Guzzle 的 debug => true 参数,方便查询请求/响应消息的基础调试信息。

    ℹ️ 什么是APIv3密钥?如何设置?

    证书和回调解密需要的AesGcm解密在哪里?

    请参考AesGcm.php,例如内置的平台证书下载工具解密代码如下:

    AesGcm::decrypt($cert->ciphertext, $apiv3Key, $cert->nonce, $cert->associated_data);
    

    配合swoole使用时,上传文件接口报错

    建议升级至swoole 4.6+,swoole在 4.6.0 中增加了native-curl(swoole/swoole-src#3863)支持,我们测试能正常使用了。 更详细的信息,请参考#36。

    如何加载公/私钥和证书

    v1.2提供了统一的加载函数 Rsa::from($thing, $type)。

    • Rsa::from($thing, $type) 支持从文件/字符串加载公/私钥和证书,使用方法可参考 RsaTest.php
    • Rsa::fromPkcs1是个语法糖,支持加载 PKCS#1 格式的公/私钥,入参是 base64 字符串
    • Rsa::fromPkcs8是个语法糖,支持加载 PKCS#8 格式的私钥,入参是 base64 字符串
    • Rsa::fromSpki是个语法糖,支持加载 SPKI 格式的公钥,入参是 base64 字符串
    • Rsa::pkcs1ToSpki是个 RSA公钥 格式转换函数,入参是 base64 字符串

    如何计算商家券发券 API 的签名

    使用 Hash::sign()计算 APIv2 的签名,示例请参考 APIv2 文档的 数据签名。

    为什么 URL 上的变量 OpenID,请求时被替换成小写了?

    本 SDK 把 URL 中的大写视为包含连字号的 segment。请求时, camelCase 会替换为 camel-case。相关 issue 可参考 #56、 #69。

    为了避免大小写错乱,URL 中存在变量时的正确做法是:使用 链式 URI Template 的 Path 变量。比如:

    • 推荐写法 ->v3->marketing->favor->users->_openid_->coupons->post(['openid' => 'AbcdEF12345'])
    • ->v3->marketing->favor->users->{'{openid}'}->coupons->post(['openid' => 'AbcdEF12345'])
    • ->chain('{+myurl}')->post(['myurl' => 'v3/marketing/favor/users/AbcdEF12345/coupons'])
    • ->{'{+myurl}'}->post(['myurl' => 'v3/marketing/favor/users/AbcdEF12345/coupons'])

    联系我们

    如果你发现了BUG或者有任何疑问、建议,请通过issue进行反馈。

    也欢迎访问我们的开发者社区。

    链接

    License

    Apache-2.0 License