wechatpay

您所在的位置:网站首页 php微信支付sdk wechatpay

wechatpay

2024-07-17 16:37:47| 来源: 网络整理| 查看: 265

微信支付 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

概览

微信支付 APIv2&APIv3 的Guzzle HttpClient封装组合, APIv2已内置请求数据签名及XML转换器,应答做了数据签名验签,转换提供有WeChatPay\Transformer::toArray静态方法,按需转换; APIv3已内置 请求签名 和 应答验签 两个middleware中间件,创新性地实现了链式面向对象同步/异步调用远程接口。

如果你是使用 Guzzle 的商户开发者,可以使用 WeChatPay\Builder 工厂方法直接创建一个 GuzzleHttp\Client 的链式调用封装器, 实例在执行请求时将自动携带身份认证信息,并检查应答的微信支付签名。

项目状态

当前版本为1.0.5测试版本。请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。

环境要求

我们开发和测试使用的环境如下:

PHP >=7.2 guzzlehttp/guzzle ^7.0

注: 随Guzzle7支持的PHP版本最低为7.2.5,另PHP官方已于30 Nov 2020停止维护PHP7.2,详见附注链接。

安装

推荐使用PHP包管理工具composer引入SDK到项目中:

方式一

在项目目录中,通过composer命令行添加:

composer require wechatpay/wechatpay 方式二

在项目的composer.json中加入以下配置:

"require": { "wechatpay/wechatpay": "^1.0.5" }

添加配置后,执行安装

composer install 约定

本类库是以 OpenAPI 对应的接入点 URL.pathname 以/做切分,映射成segments,编码书写方式有如下约定:

请求 pathname 切分后的每个segment,可直接以对象获取形式串接,例如 v3/pay/transactions/native 即串成 v3->pay->transactions->native; 每个 pathname 所支持的 HTTP METHOD,即作为被串接对象的末尾执行方法,例如: v3->pay->transactions->native->post(['json' => []]); 每个 pathname 所支持的 HTTP METHOD,同时支持Async语法糖,例如: v3->pay->transactions->native->postAsync(['json' => []]); 每个 segment 有中线(dash)分隔符的,可以使用驼峰camelCase风格书写,例如: merchant-service可写成 merchantService,或如 {'merchant-service'}; 每个 segment 中,若有uri_template动态参数,例如 business_code/{business_code} 推荐以business_code->{'{business_code}'}形式书写,其格式语义与pathname基本一致,阅读起来比较自然; SDK内置以 v2 特殊标识为 APIv2 的起始 segmemt,之后串接切分后的 segments,如源 pay/micropay 即串成 v2->pay->micropay->post(['xml' => []]) 即以XML形式请求远端接口; 在IDE集成环境下,也可以按照内置的chain($segment)接口规范,直接以pathname作为变量$segment,来获取OpenAPI接入点的endpoints串接对象,驱动末尾执行方法(填入对应参数),发起请求,例如 chain('v3/pay/transactions/jsapi')->post(['json' => []]);

以下示例用法,以异步(Async/PromiseA+)或同步(Sync)结合此种编码模式展开。

Note of the segments: See RFC3986 #section-3.3

A path consists of a sequence of path segments separated by a slash ("/") character.

Note of the uri_template: See RFC6570

开始

首先,通过 WeChatPay\Builder 工厂方法构建一个实例,然后如上述约定,链式同步或异步请求远端OpenAPI接口。

use WeChatPay\Builder; use WeChatPay\Util\PemUtil; // 工厂方法构造一个实例 $instance = Builder::factory([ // 商户号 'mchid' => '1000100', // 商户证书序列号 'serial' => 'XXXXXXXXXX', // 商户API私钥 PEM格式的文本字符串或者文件resource 'privateKey' => PemUtil::loadPrivateKey('/path/to/mch/apiclient_key.pem'), 'certs' => [ // 可由内置的平台证书下载器 `./bin/CertificateDownloader.php` 生成 'YYYYYYYYYY' => PemUtil::loadCertificate('/path/to/wechatpay/cert.pem') ], // APIv2密钥(32字节)--不使用APIv2可选 'secret' => 'ZZZZZZZZZZ', 'merchant' => [// --不使用APIv2可选 // 商户证书 文件路径 --不使用APIv2可选 'cert' => '/path/to/mch/apiclient_cert.pem', // 商户API私钥 文件路径 --不使用APIv2可选 'key' => '/path/to/mch/apiclient_key.pem', ], ]);

初始化字典说明如下:

mchid 为你的商户号,一般是10字节纯数字 serial 为你的商户证书序列号,一般是40字节字符串 privateKey 为你的商户API私钥,一般是通过官方证书生成工具生成的文件名是apiclient_key.pem文件,支持纯字符串或者文件resource格式 certs[$serial_number => #resource] 为通过下载工具下载的平台证书key/value键值对,键为平台证书序列号,值为平台证书pem格式的纯字符串或者文件resource格式 secret 为APIv2版的密钥,商户平台上设置的32字节字符串 merchant[cert => $path] 为你的商户证书,一般是文件名为apiclient_cert.pem文件路径,接受[$path, $passphrase] 格式,其中$passphrase为证书密码 merchant[key => $path] 为你的商户API私钥,一般是通过官方证书生成工具生成的文件名是apiclient_key.pem文件路径,接受[$path, $passphrase] 格式,其中$passphrase为私钥密码

注: APIv3, APIv2 以及 GuzzleHttp\Client 的 $config = [] 初始化参数,均融合在一个型参上; 另外初始化参数说明中的平台证书下载器可阅读使用说明文档。

APIv3 Native下单 try { $resp = $instance->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() . ' ' . $resp->getReasonPhrase(), PHP_EOL; echo $resp->getBody(), PHP_EOL; } catch (Exception $e) { // 进行错误处理 echo $e->getMessage(), PHP_EOL; if ($e instanceof \Psr\Http\Message\ResponseInterface && $e->hasResponse()) { echo $e->getResponse()->getStatusCode() . ' ' . $e->getResponse()->getReasonPhrase(), PHP_EOL; echo $e->getResponse()->getBody(); } } 查单 $res = $instance->v3->pay->transactions->id->{'{transaction_id}'} ->getAsync([ // 查询参数结构 'query' => ['mchid' => '1230000109'], // uri_template 字面量参数 'transaction_id' => '1217752501201407033233368018', ]) ->then(static function($response) { // 正常逻辑回调处理 echo $response->getBody()->getContents(), PHP_EOL; return $response; }) ->otherwise(static function($exception) { // 异常错误处理 if ($exception instanceof \Psr\Http\Message\ResponseInterface) { $body = $exception->getResponse()->getBody(); echo $body->getContents(), PHP_EOL, PHP_EOL, PHP_EOL; } echo $exception->getTraceAsString(), PHP_EOL; }) ->wait(); 关单 $res = $instance->v3->pay->transactions->outTradeNo->{'{out_trade_no}'}->close ->postAsync([ // 请求参数结构 'json' => ['mchid' => '1230000109'], // uri_template 字面量参数 'out_trade_no' => '1217752501201407033233368018', ]) ->then(static function($response) { // 正常逻辑回调处理 echo $response->getBody()->getContents(), PHP_EOL; return $response; }) ->otherwise(static function($exception) { // 异常错误处理 if ($exception instanceof \Psr\Http\Message\ResponseInterface) { $body = $exception->getResponse()->getBody(); echo $body->getContents(), PHP_EOL, PHP_EOL, PHP_EOL; } echo $exception->getTraceAsString(), PHP_EOL; }) ->wait(); 退款 $res = $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 $response->getBody()->getContents(), PHP_EOL; return $response; }) ->otherwise(static function($exception) { // 异常错误处理 if ($exception instanceof \Psr\Http\Message\ResponseInterface) { $body = $exception->getResponse()->getBody(); echo $body->getContents(), PHP_EOL, PHP_EOL, PHP_EOL; } echo $exception->getTraceAsString(), PHP_EOL; }) ->wait(); 视频文件上传 // 参考上述指引说明,并引入 `MediaUtil` 正常初始化,无额外条件 use WeChatPay\Util\MediaUtil; // 实例化一个媒体文件流,注意文件后缀名需符合接口要求 $media = new MediaUtil('/your/file/path/video.mp4'); try { $resp = $instance['v3/merchant/media/video_upload']->post([ 'body' => $media->getStream(), 'headers' => [ 'content-type' => $media->getContentType(), ] ]); echo $resp->getStatusCode() . ' ' . $resp->getReasonPhrase(), PHP_EOL; echo $resp->getBody(), PHP_EOL; } catch (Exception $e) { echo $e->getMessage(), PHP_EOL; if ($e instanceof \Psr\Http\Message\ResponseInterface && $e->hasResponse()) { echo $e->getResponse()->getStatusCode() . ' ' . $e->getResponse()->getReasonPhrase(), PHP_EOL; echo $e->getResponse()->getBody(); } } 图片上传 use WeChatPay\Util\MediaUtil; $media = new MediaUtil('/your/file/path/image.jpg'); $resp = $instance->v3->marketing->favor->media->imageUpload ->postAsync([ 'body' => $media->getStream(), 'headers' => [ 'content-type' => $media->getContentType(), ] ]) ->then(static function($response) { echo $response->getBody()->getContents(), PHP_EOL; return $response; }) ->otherwise(static function($exception) { if ($exception instanceof \Psr\Http\Message\ResponseInterface) { $body = $exception->getResponse()->getBody(); echo $body->getContents(), PHP_EOL, PHP_EOL, PHP_EOL; } echo $exception->getTraceAsString(), PHP_EOL; }) ->wait(); 敏感信息加/解密 // 参考上上述说明,引入 `WeChatPay\Crypto\Rsa` use WeChatPay\Crypto\Rsa; // 加载最新的平台证书 $publicKey = PemUtil::loadCertificate('/path/to/wechatpay/cert.pem'); // 做一个匿名方法,供后续方便使用 $encryptor = function($msg) use ($publicKey) { return Rsa::encrypt($msg, $publicKey); }; // 正常使用Guzzle发起API请求 try { // POST 语法糖 $resp = $instance->chain('v3/applyment4sub/applyment/')->post([ 'json' => [ 'business_code' => 'APL_98761234', 'contact_info' => [ 'contact_name' => $encryptor('value of `contact_name`'), 'contact_id_number' => $encryptor('value of `contact_id_number'), 'mobile_phone' => $encryptor('value of `mobile_phone`'), 'contact_email' => $encryptor('value of `contact_email`'), ], //... ], 'headers' => [ // 命令行获取证书序列号 // openssl x509 -in /path/to/wechatpay/cert.pem -noout -serial | awk -F= '{print $2}' // 或者使用工具类获取证书序列号 `PemUtil::parseCertificateSerialNo($certificate)` 'Wechatpay-Serial' => '下载的平台证书序列号', ], ]); echo $resp->getStatusCode() . ' ' . $resp->getReasonPhrase(), PHP_EOL; echo $resp->getBody(), PHP_EOL; } catch (Exception $e) { echo $e->getMessage(), PHP_EOL; if ($e instanceof \Psr\Http\Message\ResponseInterface && $e->hasResponse()) { echo $e->getResponse()->getStatusCode() . ' ' . $e->getResponse()->getReasonPhrase(), PHP_EOL; echo $e->getResponse()->getBody(); } return; } APIv2

末尾驱动的 HTTP METHOD(POST) 方法入参 array $options,接受两个自定义参数,释义如下:

$options['nonceless'] - 标量 scalar 任意值,语义上即,本次请求不用自动添加nonce_str参数,推荐 boolean(True) $options['security'] - 布尔量True,语义上即,本次请求需要加载ssl证书,对应的是初始化 array $config['merchant'] 结构体 企业付款到零钱 use WeChatPay\Transformer; $res = $instance->v2->mmpaymkttransfers->promotion->transfers ->postAsync([ 'xml' => [ 'appid' => 'wx8888888888888888', 'mch_id' => '1900000109', 'partner_trade_no' => '10000098201411111234567890', 'openid' => 'oxTWIuGaIt6gTKsQRLau2M0yL16E', 'check_name' => 'FORCE_CHECK', 're_user_name' => '王小王', 'amount' => 10099, 'desc' => '理赔', 'spbill_create_ip' => '192.168.0.1', ], 'security' => true, 'debug' => true //开启调试模式 ]) ->then(static function($response) { return Transformer::toArray($response->getBody()->getContents()); }) ->otherwise(static function($exception) { return Transformer::toArray($exception->getResponse()->getBody()->getContents()); }) ->wait(); print_r($res); 常见问题 如何下载平台证书?

使用内置的平台证书下载器 ./bin/CertificateDownloader.php ,验签逻辑与有平台证书请求其他接口一致,即在请求完成后,立即用获得的平台证书对返回的消息进行验签,下载器同时开启了 Guzzle 的 debug => true 参数,方便查询请求/响应消息的基础调试信息。

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

请参考AesGcm.php。

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

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

联系我们

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

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

链接 GuzzleHttp官方版本支持 PHP官方版本支持 变更历史 License

Apache-2.0 License



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


    图片新闻

    实验室药品柜的特性有哪些
    实验室药品柜是实验室家具的重要组成部分之一,主要
    小学科学实验中有哪些教学
    计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
    实验室各种仪器原理动图讲
    1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
    高中化学常见仪器及实验装
    1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
    微生物操作主要设备和器具
    今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
    浅谈通风柜使用基本常识
     众所周知,通风柜功能中最主要的就是排气功能。在

    专题文章

      CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭