本类库可单独用于APIv2
的开发,希望能给商户提供一个过渡,可先平滑迁移至本类库以承接APIv2
对接,然后再按需替换升级至APIv3
上。
以下代码以单独使用展开示例,供商户参考。关于链式 ->
,请先阅读 链式 URI Template。
use WeChatPay\Builder;
// 商户号,假定为`1000100`
$merchantId = '1000100';
// APIv2密钥(32字节) 假定为`exposed_your_key_here_have_risks`,使用请替换为实际值
$apiv2Key = 'exposed_your_key_here_have_risks';
// 商户私钥,文件路径假定为 `/path/to/merchant/apiclient_key.pem`
$merchantPrivateKeyFilePath = '/path/to/merchant/apiclient_key.pem';
// 商户证书,文件路径假定为 `/path/to/merchant/apiclient_cert.pem`
$merchantCertificateFilePath = '/path/to/merchant/apiclient_cert.pem';
// 工厂方法构造一个实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => 'nop',
'privateKey' => 'any',
'certs' => ['any' => null],
'secret' => $apiv2Key,
'merchant' => [
'cert' => $merchantCertificateFilePath,
'key' => $merchantPrivateKeyFilePath,
],
]);
初始化字典说明如下:
mchid
为你的商户号
,一般是10字节纯数字serial
为你的商户证书序列号
,不使用APIv3可填任意值privateKey
为你的商户API私钥
,不使用APIv3可填任意值certs[$serial_number => #resource]
不使用APIv3可填任意值,$serial_number
注意不要与商户证书序列号serial
相同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 = []
初始化参数,均融合在一个型参上。
use WeChatPay\Transformer;
$res = $instance
->v2->mmpaymkttransfers->promotion->transfers
->postAsync([
'xml' => [
'mch_appid' => 'wx8888888888888888',
'mchid' => '1900000109',// 注意这个商户号,key是`mchid`非`mch_id`
'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((string)$response->getBody());
})
->otherwise(static function($e) {
// 更多`$e`异常类型判断是必须的,这里仅列出一种可能情况,请根据实际对接过程调整并增加
if ($e instanceof \GuzzleHttp\Promise\RejectionException) {
return Transformer::toArray((string)$e->getReason()->getBody());
}
return [];
})
->wait();
print_r($res);
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->risk->getpublickey
->postAsync([
'xml' => [
'mch_id' => '1900000109',
'sign_type' => 'MD5',
],
'security' => true, //请求需要双向证书
// 特殊接入点,仅对本次请求有效
'base_uri' => 'https://fraud.mch.weixin.qq.com/',
])
->then(static function($response) {
return Transformer::toArray((string)$response->getBody());
})
->wait();
print_r($res);
use WeChatPay\Transformer;
use WeChatPay\Crypto\Rsa;
// 做一个匿名方法,供后续方便使用,$rsaPubKeyString 是`risk/getpublickey` 的返回值'pub_key'字符串
$rsaPublicKeyInstance = Rsa::from($rsaPubKeyString, Rsa::KEY_TYPE_PUBLIC);
$encryptor = static function(string $msg) use ($rsaPublicKeyInstance): string {
return Rsa::encrypt($msg, $rsaPublicKeyInstance);
};
$res = $instance
->v2->mmpaysptrans->pay_bank
->postAsync([
'xml' => [
'mch_id' => '1900000109',
'partner_trade_no' => '1212121221227',
'enc_bank_no' => $encryptor('6225............'),
'enc_true_name' => $encryptor('张三'),
'bank_code' => '1001',
'amount' => '100000',
'desc' => '理财',
],
'security' => true, //请求需要双向证书
])
->then(static function($response) {
return Transformer::toArray((string)$response->getBody());
})
->wait();
print_r($res);
SDK自v1.4.6调整了XML转换函数,支持了Stringable 值的转换,对于 Scalar 标量值及实现了 __toString 方法的对象,均支持直接转换,详细可参考 TransformerTest.php 的用例用法示例。
use WeChatPay\Formatter;
use WeChatPay\Transformer;
$res = $instance
->v2->face->get_wxpayface_authinfo
->postAsync([
'xml' => [
'store_id' => '1234567',
'store_name' => '云店(广州白云机场店)',
'device_id' => 'abcdef',
'rawdata' => '从客户端`getWxpayfaceRawdata`方法取得的数据',
'appid' => 'wx8888888888888888',
'mch_id' => '1900000109',
'now' => (string)Formatter::timestamp(),
'version' => '1',
'sign_type' => 'HMAC-SHA256',
],
// 特殊接入点,仅对本次请求有效
'base_uri' => 'https://payapp.weixin.qq.com/',
])
->then(static function($response) {
return Transformer::toArray((string)$response->getBody());
})
->wait();
print_r($res);
use WeChatPay\Transformer;
$res = $instance
->v2->xdc->apiv2getsignkey->sign->getsignkey
->postAsync([
'xml' => [
'mch_id' => '1900000109',
],
// 通知SDK不接受沙箱环境重定向,仅对本次请求有效
'allow_redirects' => false,
])
->then(static function($response) {
return Transformer::toArray((string)$response->getBody());
})
->wait();
print_r($res);
use WeChatPay\Transformer;
$xml = Transformer::toXml([
'return_code' => 'SUCCESS',
'return_msg' => 'OK',
]);
echo $xml;
use WeChatPay\Formatter;
use WeChatPay\Crypto\Hash;
$apiv2Key = 'exposed_your_key_here_have_risks';
$busiFavorFlat = static function (array $params): array {
$result = ['send_coupon_merchant' => $params['send_coupon_merchant']];
foreach ($params['send_coupon_params'] as $index => $item) {
foreach ($item as $key => $value) {
$result["{$key}{$index}"] = $value;
}
}
return $result;
};
// 发券小程序所需数据结构
$busiFavor = [
'send_coupon_params' => [
['out_request_no' => '1234567', 'stock_id' => 'abc123'],
['out_request_no' => '7654321', 'stock_id' => '321cba'],
],
'send_coupon_merchant' => '10016226'
];
$busiFavor += ['sign' => Hash::sign(
Hash::ALGO_HMAC_SHA256,
Formatter::queryStringLike(Formatter::ksort($busiFavorFlat($busiFavor))),
$apiv2Key
)];
echo json_encode($busiFavor);
use WeChatPay\Formatter;
use WeChatPay\Crypto\Hash;
$apiv2Key = 'exposed_your_key_here_have_risks';
$params = [
'stock_id' => '12111100000001',
'out_request_no' => '20191204550002',
'send_coupon_merchant' => '10016226',
'open_id' => 'oVvBvwEurkeUJpBzX90-6MfCHbec',
'coupon_code' => '75345199',
];
$params += ['sign' => Hash::sign(
Hash::ALGO_HMAC_SHA256,
Formatter::queryStringLike(Formatter::ksort($params)),
$apiv2Key
)];
echo json_encode($params);
回调通知受限于开发者/商户所使用的WebServer
有很大差异,这里只给出开发指导步骤,供参考实现。
- 从请求头
Headers
获取Request-ID
,商户侧Web
解决方案可能有差异,请求头的Request-ID
可能大小写不敏感,请根据自身应用来定; - 获取请求
body
体的XML
纯文本; - 调用
SDK
内置方法,根据签名算法做本地数据签名计算,然后与通知文本的sign
做Hash::equals
对比验签; - 消息体需要解密的,调用
SDK
内置方法解密; - 如遇到问题,请拿
Request-ID
点击这里,联系官方在线技术支持;
样例代码如下:
use WeChatPay\Transformer;
use WeChatPay\Crypto\Hash;
use WeChatPay\Crypto\AesEcb;
use WeChatPay\Formatter;
$inBody = '';// 请根据实际情况获取,例如: file_get_contents('php://input');
$apiv2Key = '';// 在商户平台上设置的APIv2密钥
$inBodyArray = Transformer::toArray($inBody);
// 部分通知体无`sign_type`,部分`sign_type`默认为`MD5`,部分`sign_type`默认为`HMAC-SHA256`
// 部分通知无`sign`字典
// 请根据官方开发文档确定
['sign_type' => $signType, 'sign' => $sign] = $inBodyArray;
$calculated = Hash::sign(
$signType ?? Hash::ALGO_MD5,// 如没获取到`sign_type`,假定默认为`MD5`
Formatter::queryStringLike(Formatter::ksort($inBodyArray)),
$apiv2Key
);
$signatureStatus = Hash::equals($calculated, $sign);
if ($signatureStatus) {
// 如需要解密的
['req_info' => $reqInfo] = $inBodyArray;
$inBodyReqInfoXml = AesEcb::decrypt($reqInfo, Hash::md5($apiv2Key));
$inBodyReqInfoArray = Transformer::toArray($inBodyReqInfoXml);
// print_r($inBodyReqInfoArray);// 打印解密后的结果
}