可链式调用微信支付OpenAPI的PHP版SDK
source link: https://thenorthmemory.github.io/post/async-sync-chainable-wechatpay-v2-and-v3-openapi-sdk-for-php/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
可链式调用微信支付OpenAPI的PHP版SDK
微信支付 APIv2&APIv3 的 Guzzle HttpClient
封装组合,
APIv2已内置请求数据签名及XML
转换器,应答做了数据签名验签
,转换提供有WeChatPay\Transformer::toArray
静态方法,按需转换;
APIv3已内置 请求签名
和 应答验签
两个middleware中间件,创新性地实现了链式面向对象同步/异步调用远程接口。
如果你是使用 Guzzle
的商户开发者,可以使用 WeChatPay\Builder
工厂方法直接创建一个 GuzzleHttp\Client
的链式调用封装器,
实例在执行请求时将自动携带身份认证信息,并检查应答的微信支付签名。
当前版本为 1.0.2
测试版本。请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。
我们开发和测试使用的环境如下:
- PHP >=7.2
- guzzlehttp/guzzle ^7.0
推荐使用PHP包管理工具composer
引入SDK到项目中:
在项目目录中,通过composer命令行添加:
composer require wechatpay/wechatpay
在项目的composer.json
中加入以下配置:
"require": {
"wechatpay/wechatpay": "^1.0.2"
}
添加配置后,执行安装
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()."\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();
}
}
$res = $instance->v3->pay->transactions->id->{'{transaction_id}'}
->getAsync([
// 查询参数结构
'query' => ['mchid' => '1230000109'],
// uri_template 字面量参数
'transaction_id' => '1217752501201407033233368018',
])
->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();
$res = $instance->v3->pay->transactions->outTradeNo->{'{out_trade_no}'}->close
->postAsync([
// 请求参数结构
'json' => ['mchid' => '1230000109'],
// uri_template 字面量参数
'out_trade_no' => '1217752501201407033233368018',
])
->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();
$res = $instance->chain('v3/refund/domestic/refunds')
->postAsync([
'json' => [
'transaction_id' => '1217752501201407033233368018',
'out_refund_no' => '1217752501201407033233368018',
'amount' => [
'refund' => 888,
'total' => 888,
'currency' => 'CNY',
],
],
])
->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();
视频文件上传
// 参考上述指引说明,并引入 `MediaUtil` 正常初始化,无额外条件
use WeChatPay\Util\MediaUtil;
// 实例化一个媒体文件流,注意文件后缀名需符合接口要求
$media = new MediaUtil('/your/file/path/with.extension');
try {
$resp = $instance['v3/merchant/media/video_upload']->post([
'body' => $media->getStream(),
'headers' => [
'content-type' => $media->getContentType(),
]
]);
echo $resp->getStatusCode().' '.$resp->getReasonPhrase()."\n";
echo $resp->getBody()."\n";
} catch (Exception $e) {
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
echo $e->getResponse()->getStatusCode().' '.$e->getResponse()->getReasonPhrase()."\n";
echo $e->getResponse()->getBody();
}
}
$resp = $instance->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();
敏感信息加/解密
// 参考上上述说明,引入 `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()."\n";
echo $resp->getBody()."\n";
} catch (Exception $e) {
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
echo $e->getResponse()->getStatusCode().' '.$e->getResponse()->getReasonPhrase()."\n";
echo $e->getResponse()->getBody();
}
return;
}
APIv2
末尾驱动的 HTTP METHOD
方法入参 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);
SDK包的设计是开放式的,目前已知可以采用不下十种方式编程编码方式均可触达,同步/异步、模版字面量、驼峰、链式,甚至企业微信之企业支付,均可按开放规范正常工作。
项目链接地址:https://github.com/TheNorthMemory/wechatpay-php 如果喜欢,欢迎star。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK