Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

升级PHP>=7.2,升级Guzzle>=7.0,链式同时支持同步异步接口请求编程方式 #52

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7b2f253
upgrade guzzle>=7.0
TheNorthMemory Jun 3, 2021
f776895
Formatter.php for wechatpay-apiv3/wechatpay-guzzle-middleware#51
TheNorthMemory Jun 4, 2021
e5cddf2
Crypto/AesGcm.php for wechatpay-apiv3/wechatpay-guzzle-middleware#51
TheNorthMemory Jun 4, 2021
cca6913
fully qualified name of `openssl_encrypt` on `AesGcm`
TheNorthMemory Jun 5, 2021
c2a2a3a
Crypto/Rsa.php for wechatpay-apiv3/wechatpay-guzzle-middleware#51
TheNorthMemory Jun 5, 2021
7b46dcf
ClientDecorator.php, ClientJsonTrait.php for wechatpay-apiv3/wechatpa…
TheNorthMemory Jun 5, 2021
ff59632
compatible PHP8 of wechatpay-apiv3/wechatpay-guzzle-middleware#51
TheNorthMemory Jun 6, 2021
acba897
method signature for PHP8, ref wechatpay-apiv3/wechatpay-guzzle-middl…
TheNorthMemory Jun 6, 2021
66220aa
Builder.php & BuilderTrait.php for wechatpay-apiv3/wechatpay-guzzle-m…
TheNorthMemory Jun 6, 2021
47445c4
backport uri_template for wechatpay-apiv3/wechatpay-guzzle-middleware#51
TheNorthMemory Jun 6, 2021
81ade39
renamed `tool` as `bin`
TheNorthMemory Jun 6, 2021
cfcee55
CertificateDownloader.php for wechatpay-apiv3/wechatpay-guzzle-middle…
TheNorthMemory Jun 6, 2021
117a3b7
fully qualified name on CertificateDownloader.php
TheNorthMemory Jun 6, 2021
e1f4680
cleanup for wechatpay-apiv3/wechatpay-guzzle-middleware#51
TheNorthMemory Jun 6, 2021
5a2751e
cleanup(2) for wechatpay-apiv3/wechatpay-guzzle-middleware#51
TheNorthMemory Jun 6, 2021
3fc7cec
type signature of the `MediaUtil.php` for wechatpay-apiv3/wechatpay-g…
TheNorthMemory Jun 6, 2021
6adc880
renew README.md, ref wechatpay-apiv3/wechatpay-guzzle-middleware#51
TheNorthMemory Jun 6, 2021
4063672
Merge branch 'master' into develop
TheNorthMemory Jun 6, 2021
266d388
restrict by interfaces for wechatpay-apiv3/wechatpay-guzzle-middlewar…
TheNorthMemory Jun 11, 2021
11618f8
renew cert downloader for wechatpay-apiv3/wechatpay-guzzle-middleware#51
TheNorthMemory Jun 11, 2021
ac15706
README of the cert downloader for wechatpay-apiv3/wechatpay-guzzle-m…
TheNorthMemory Jun 11, 2021
b21175b
anonymous static function for wechatpay-apiv3/wechatpay-guzzle-middle…
TheNorthMemory Jun 13, 2021
15601e2
cert downloader's timezone(Asia/Shanghai) for wechatpay-apiv3/wechatp…
TheNorthMemory Jun 13, 2021
992e3c2
`request[Async]` methods signature
TheNorthMemory Jun 14, 2021
a245b07
makes better for wechatpay-apiv3/wechatpay-guzzle-middleware#51
TheNorthMemory Jun 14, 2021
a7c3f61
APIv2&APIv3 together
TheNorthMemory Jun 15, 2021
4c418c1
drop the hardcode sections
TheNorthMemory Jun 15, 2021
384e859
use `->select($protocol)` syntax
TheNorthMemory Jun 15, 2021
6e0a190
declare asof `GuzzleHttp\ClientInterface` signature
TheNorthMemory Jun 15, 2021
ba737a0
new age of `wechatpay/wechatpay`
TheNorthMemory Jun 15, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 115 additions & 118 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
# wechatpay-guzzle-middleware
# 微信支付 WeChatPay OpenAPI SDK

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

[微信支付API v3](https://wechatpay-api.gitbook.io/wechatpay-api-v3/)的[Guzzle HttpClient](http://docs.guzzlephp.org/)中间件Middleware,实现了请求签名的生成和应答签名的验证。
[![Packagist Stars](https://img.shields.io/packagist/stars/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)
[![Packagist Downloads](https://img.shields.io/packagist/dm/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)
[![Packagist Version](https://img.shields.io/packagist/v/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)
[![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)
[![Packagist License](https://img.shields.io/packagist/l/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)

如果你是使用Guzzle的商户开发者,可以在构造`GuzzleHttp\Client`时将`WechatPayGuzzleMiddleware`传入,得到的`GuzzleHttp\Client`实例在执行请求时将自动携带身份认证信息,并检查应答的微信支付签名。
## 概览

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

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

## 项目状态

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

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


## 环境要求

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

+ PHP 5.5+ / PHP 7.0+
+ guzzlehttp/guzzle 6.0+

+ PHP 7.2+
+ guzzlehttp/guzzle 7.0+


## 安装
Expand All @@ -30,107 +38,102 @@
#### Composer

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

```shell
composer require wechatpay/wechatpay-guzzle-middleware
composer require wechatpay/wechatpay
```


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

```json
"require": {
"wechatpay/wechatpay-guzzle-middleware": "^0.2.0"
"wechatpay/wechatpay": "^1.0.0"
}
```

添加配置后,执行安装

```shell
composer install
```



## 开始

首先,通过`WechatPayMiddlewareBuilder`构建一个`WechatPayMiddleware`,然后将其加入`GuzzleHttp\Client`的`HandlerStack`中。我们提供相应的方法,可以方便的传入商户私钥和微信支付平台证书等信息
首先,通过 `WCchatPay\Builder` 工厂方法构建一个实例,然后就可以按照 [#51 链式同步或异步请求远端接口](https://github.com/wechatpay-apiv3/wechatpay-guzzle-middleware/issues/51)

```php
use GuzzleHttp\Exception\RequestException;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use WeChatPay\Builder;
use WeChatPay\Util\PemUtil;

// 工厂方法构造一个实例
$wxpay = Builder::factory([
// 商户号
'mchid' => '1000100',
// 商户证书序列号
'serial' => 'XXXXXXXXXX',
// 商户API私钥 PEM格式的文本字符串或者文件resource
'privateKey' => PemUtil::loadPrivateKey('/path/to/mch/apiclient_key.pem'),
'certs' => [
// CLI `./bin/CertificateDownloader.php -m {商户号} -s {商户证书序列号} -f {商户API私钥文件路径} -k {APIv3密钥(32字节)} -o {保存地址}` 生成
'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',
],
]);
```

// 商户相关配置
$merchantId = '1000100'; // 商户号
$merchantSerialNumber = 'XXXXXXXXXX'; // 商户API证书序列号
$merchantPrivateKey = PemUtil::loadPrivateKey('/path/to/mch/private/key.pem'); // 商户私钥
// 微信支付平台配置
$wechatpayCertificate = PemUtil::loadCertificate('/path/to/wechatpay/cert.pem'); // 微信支付平台证书
初始化字典说明如下:

// 构造一个WechatPayMiddleware
$wechatpayMiddleware = WechatPayMiddleware::builder()
->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置
->withWechatPay([ $wechatpayCertificate ]) // 可传入多个微信支付平台证书,参数类型为array
->build();
- `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`文件路径
- `merchant[key => $path]` 为你的`商户API私钥`,一般是通过官方证书生成工具生成的文件名是`apiclient_key.pem`文件路径

// 将WechatPayMiddleware添加到Guzzle的HandlerStack中
$stack = GuzzleHttp\HandlerStack::create();
$stack->push($wechatpayMiddleware, 'wechatpay');
**注:** 1.0版本做了重构及优化, `APIv3`, `APIv2` 以及 `GuzzleHttp\Client` 的 `$config = []` 初始化参数,均融合在一个型参上。

// 创建Guzzle HTTP Client时,将HandlerStack传入
$client = new GuzzleHttp\Client(['handler' => $stack]);
## APIv3

### Native下单

// 接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
```php
try {
$resp = $client->request('GET', 'https://api.mch.weixin.qq.com/v3/...', [ // 注意替换为实际URL
'headers' => [ 'Accept' => 'application/json' ]
]);
$resp = $wxpay->v3->pay->transactions->native->post(['json' => [/*文档参数放这里就好*/]]);

echo $resp->getStatusCode().' '.$resp->getReasonPhrase()."\n";
echo $resp->getBody()."\n";

$resp = $client->request('POST', 'https://api.mch.weixin.qq.com/v3/...', [
'json' => [ // JSON请求体
'field1' => 'value1',
'field2' => 'value2'
],
'headers' => [ 'Accept' => 'application/json' ]
]);

echo $resp->getStatusCode().' '.$resp->getReasonPhrase()."\n";
echo $resp->getBody()."\n";
echo $resp->getStatusCode() .' ' . $resp->getReasonPhrase()."\n";
echo $resp->getBody() . "\n";
} catch (RequestException $e) {
// 进行错误处理
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
echo $e->getResponse()->getStatusCode().' '.$e->getResponse()->getReasonPhrase()."\n";
echo $e->getResponse()->getBody();
}
return;
}
```

### 上传媒体文件
### 视频文件上传

```php
// 参考上述指引说明,并引入 `MediaUtil` 正常初始化,无额外条件
use WechatPay\GuzzleMiddleware\Util\MediaUtil;
use WeChatPay\Util\MediaUtil;
// 实例化一个媒体文件流,注意文件后缀名需符合接口要求
$media = new MediaUtil('/your/file/path/with.extension');

// 正常使用Guzzle发起API请求
try {
$resp = $client->request('POST', 'https://api.mch.weixin.qq.com/v3/[merchant/media/video_upload|marketing/favor/media/image-upload]', [
$resp = $wxpay['v3/merchant/media/video_upload']->post([
'body' => $media->getStream(),
'headers' => [
'Accept' => 'application/json',
'content-type' => $media->getContentType(),
]
]);
// POST 语法糖
$resp = $client->post('merchant/media/upload', [
'body' => $media->getStream(),
'headers' => [
'Accept' => 'application/json',
'content-type' => $media->getContentType(),
]
]);
Expand All @@ -142,22 +145,41 @@ try {
echo $e->getResponse()->getStatusCode().' '.$e->getResponse()->getReasonPhrase()."\n";
echo $e->getResponse()->getBody();
}
return;
}
```

### 图片上传

```php
$resp = $wxpay->v3->marketing->favor->media->imageUpload->postAsync([
'body' => $media->getStream(),
'headers' => [
'content-type' => $media->getContentType(),
]
])->then(function($response) {
echo $response->getBody()->getContents(), PHP_EOL;
return $response;
})->otherwise(function($exception) {
$body = $exception->getResponse()->getBody();
echo $body->getContents(), PHP_EOL, PHP_EOL, PHP_EOL;
echo $exception->getTraceAsString(), PHP_EOL;
})->wait();
```

### 敏感信息加/解密

```php
// 参考上上述说明,引入 `SensitiveInfoCrypto`
use WechatPay\GuzzleMiddleware\Util\SensitiveInfoCrypto;
// 上行加密API 多于 下行解密,默认为加密,实例后直接当方法用即可
$encryptor = new SensitiveInfoCrypto(PemUtil::loadCertificate('/path/to/wechatpay/cert.pem'));
// 参考上上述说明,引入 `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 = $client->post('/v3/applyment4sub/applyment/', [
$resp = $wxpay['v3/applyment4sub/applyment/']->post([
'json' => [
'business_code' => 'APL_98761234',
'contact_info' => [
Expand All @@ -173,7 +195,6 @@ try {
// openssl x509 -in /path/to/wechatpay/cert.pem -noout -serial | awk -F= '{print $2}'
// 或者使用工具类获取证书序列号 `PemUtil::parseCertificateSerialNo($certificate)`
'Wechatpay-Serial' => 'must be the serial number via the downloaded pem file of `/v3/certificates`',
'Accept' => 'application/json',
],
]);
echo $resp->getStatusCode().' '.$resp->getReasonPhrase()."\n";
Expand All @@ -186,69 +207,45 @@ try {
}
return;
}

// 单例加解密示例如下
$crypto = new SensitiveInfoCrypto($wechatpayCertificate, $merchantPrivateKey);
$encrypted = $crypto('Alice');
$decrypted = $crypto->setStage('decrypt')($encrypted);
```

## 定制
## APIv2

当默认的本地签名和验签方式不适合你的系统时,你可以通过实现`Signer`或者`Verifier`来定制签名和验签。比如,你的系统把商户私钥集中存储,业务系统需通过远程调用进行签名,你可以这样做。
### 企业付款到零钱

```php
use WechatPay\GuzzleMiddleware\Auth\Signer;
use WechatPay\GuzzleMiddleware\Auth\SignatureResult;
use WechatPay\GuzzleMiddleware\Auth\WechatPay2Credentials;

class CustomSigner implements Signer
{
public function sign($message)
{
// 调用签名RPC服务,然后返回包含签名和证书序列号的SignatureResult
return new SignatureResult('xxxx', 'yyyyy');
}
}

$credentials = new WechatPay2Credentials($merchantId, new CustomSigner);

$wechatpayMiddleware = WechatPayMiddleware::builder()
->withCredentials($credentials)
->withWechatPay([ $wechatpayCertificate ])
->build();
use WeChatPay\Transformer;
$res = $wxpay->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);
```



## 常见问题

### 如何下载平台证书?

使用`WechatPayMiddlewareBuilder`需要调用`withWechatpay`设置[微信支付平台证书](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#ping-tai-zheng-shu),而平台证书又只能通过调用[获取平台证书接口](https://wechatpay-api.gitbook.io/wechatpay-api-v3/jie-kou-wen-dang/ping-tai-zheng-shu#huo-qu-ping-tai-zheng-shu-lie-biao)下载。为了解开"死循环",你可以在第一次下载平台证书时,按照下述方法临时"跳过”应答签名的验证。

```php
use WechatPay\GuzzleMiddleware\Validator;

class NoopValidator implements Validator
{
public function validate(\Psr\Http\Message\ResponseInterface $response)
{
return true;
}
}

$wechatpayMiddleware = WechatPayMiddleware::builder()
->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey)
->withValidator(new NoopValidator) // NOTE: 设置一个空的应答签名验证器,**不要**用在业务请求
->build();
```
使用内置的平台下载器 `./bin/CertificateDownloader.php` ,验签逻辑与有`平台证书`请求其他接口一致,即在请求完成后,立即用获得的`平台证书`对返回的消息进行验签,下载器同时开启了 `Guzzle` 的 `debug => true` 方便查询请求/返回消息内容。

**注意**:业务请求请使用标准的初始化流程,务必验证应答签名。

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

请参考[AesUtil.php](src/Util/AesUtil.php)。
请参考[AesGcm.php](src/Crypto/AesGcm.php)。

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

Expand Down
Loading