微信支付WeChatPay构建客户端实例实践与疑难解答

作者: unvs 分类: PHP 发布时间: 2021-12-17 20:27 ė110,018 views 6没有评论

以下是以wechatpay-php版本 PHP>=7.2为实践,其他语言大同小异!
一、找到官方的SDK链接

https://github.com/wechatpay-apiv3/wechatpay-php,通过composer安装

方式一
在项目目录中,通过composer命令行添加:
composer require wechatpay/wechatpay
方式二
在项目的composer.json中加入以下配置:
“require”: {
“wechatpay/wechatpay”: “^1.4.2″
}
添加配置后,执行安装
composer install

引用成功,进入下一步配置

二、配置wechatpay,初始化实例
通过 WeChatPay\Builder::factory 构建一个客户端实例
页面头部引用
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;

//初始化函数里增加公共
protected $wepay = null;

/**
* @var Application
*/

public function initialize(){
parent::initialize();

// 商户号
$merchantId = ’1602992962′;
// 从本地文件中加载商户API私钥,商户API私钥会用来生成请求的签名
$merchantPrivateKeyFilePath = ‘file:///www/wwwroot/****/config/cert/apiclient_key.pem’;
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);

// 商户API证书序列号
$merchantCertificateSerial = ’681D9C9112BF894F****C229C0BE16CBE46DCEB9′;

// 从本地文件中加载微信支付平台证书,用来验证微信支付应答的签名
$platformCertificateFilePath = ‘file:///www/wwwroot/****/config/cert/cert.pem’;
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 获取微信支付平台证书序列号
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);

// 构造一个 APIv3 客户端实例
$this->wepay = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
}

以上就是构建的实例代码,官方SDK也有说明,后面会列下运行过程中遇到的问题

三、当报下列错误时,考虑是微信支付平台证书不正确导致
错误一:
The `certs(681D9C9112BF894F****C229C0BE16CBE46DCEB9)` contains the merchant’s certificate serial number(681D9C9112BF894F****C229C0BE16CBE46DCEB9) which is not allowed here.
错误二:
Cannot load publicKey from(string), please take care about the \$thing input.
都是说明证书无效的原因,区别是$merchantPrivateKeyFilePath参数是指商户API私钥证书,$platformCertificateFilePath参数是指微信支付平台证书,需要单独再申请,详细查看

https://wechatpay-api.gitbook.io/wechatpay-api-v3/jie-kou-wen-dang/ping-tai-zheng-shu

https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay5_1.shtml

https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay6_0.shtml

前面两个链接,是告诉你自己写签名,然后请求接口去获取证书相关参数,第3个链接,里面是带有一些工具(JAVA、PHP、script)已经集成好的代码,编辑获取证书相关参数
这里就直接拿PHP、script来说
1、PHP

https://github.com/wechatpay-apiv3/wechatpay-php/blob/main/bin/README.md

下载bin/CertificateDownloader.php文件
然后修改参数执行命令
./bin/CertificateDownloader.php -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}

php -f ./bin/CertificateDownloader.php — -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}

注意:mchPrivateKeyFilePath指的绝对完整文件路径、outputFilePath指的完整文件目录,${}整个需要替换为参数值

2、script
https://github.com/wechatpay-apiv3/wechatpay-postman-script,由于通过PHP方法实现执行命令有报错No such file or directory,咨询客服说参数不对或未找到证书文件,为节省时间,换了postman+script方法,十几分钟解决问题,拿到证书
首先,你得下载新版的postman工具(估计得7.0以上版本),旧版的不行
其次,安装SDK里面说的导入json文件、配置脚本(选中wechatpay-apiv3,右键Edit进入Collection的配置页面)
在弹出的Edit Collection的浮层上部的多个分栏中,找到Pre-request Scripts一栏,如果是旧版是看不到;
其中红色方框中的代码,是需要配置三个参数。
const private_key = `—–BEGIN PRIVATE KEY—–
{商户的私钥}
—–END PRIVATE KEY—–`;
const mchid = “{商户号}”;
const serialNo = “{商户证书的证书序列号}”;

然后,回到主界面,配置请求
Authorization的Type选择No Auth。
在请求的头部Headers中,新增一条,KEY设为Authorization,VALUE设为{{auth}}
最后,点击SEND按钮,结果框会生成个data的数组,包含了effective_time、encrypt_certificate、algorithm、associated_data、ciphertext、nonce、serial_no、expire_time值

3、将得到的证书相关参数,进行解密
官方文档https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml
里面有各种语言版本,基本可以直接利用,然后就能生成一个CERTIFICATE证书,复制下来创建一个pem文件,下面是我集成的一个解密函数,可以直接传参调用,供大家参考

/**
* Decrypt AEAD_AES_256_GCM ciphertext V3签名解密
* @param stingr $aesKey V3签名
* @param string $associatedData AES GCM additional authentication data
* @param string $nonceStr AES GCM nonce
* @param string $ciphertext AES GCM cipher text
*
* @return string|bool Decrypted string on success or FALSE on failure
*/
public function decryptToString($aesKey ,$associatedData, $nonceStr, $ciphertext)
{

if (strlen($aesKey) != 32 ) {
throw new InvalidArgumentException(‘无效的ApiV3Key,长度应为32个字节’);
}

$ciphertext = \base64_decode($ciphertext , true);
if (strlen($ciphertext) <= 16) {
return false;
}

// ext-sodium (default installed on >= PHP 7.2)
if(function_exists(‘\sodium_crypto_aead_aes256gcm_is_available’) && \sodium_crypto_aead_aes256gcm_is_available() ){
return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey);
}

// ext-libsodium (need install libsodium-php 1.x via pecl)
if(function_exists(‘\Sodium\crypto_aead_aes256gcm_is_available’) && \Sodium\crypto_aead_aes256gcm_is_available()){

return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey);
}

// PHP >= 7.1
if(PHP_VERSION_ID >= 70100 && in_array(‘aes-256-gcm’, \openssl_get_cipher_methods()) ){
$ctext = substr($ciphertext, 0, -16);
$authTag = substr($ciphertext, -16);
return \openssl_decrypt($ctext, ‘aes-256-gcm’, $aesKey, \OPENSSL_RAW_DATA, $nonceStr,$authTag, $associatedData);
}

throw new \RuntimeException(‘AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php’);
}

四、最后再贴一个创建微信支付分订单的实例请求,只拿到正确的返回数据
//创建支付分订单
public function wepayserviceorder(){
$order_no = date(“YmdHis”).rand(100000,999999);//随机生成临时订单号
$time = strval(date(‘YmdHis’));//开始时间,必须是字符串
$notify_url = “https://blog.unvs.cn”;//必须是https协议
$openid = “****”;//微信用户openid

$json = [
'out_order_no' => $order_no, // 商户服务订单号
'appid' => "", // 应用ID
'service_id' => "", // 服务ID
'service_introduction' => '超市购物', // 服务信息,用于介绍本订单所提供的服务
'time_range' => [
'start_time' => $time //'20211216160710',
],
‘risk_fund’ => [
'name' => 'ESTIMATE_ORDER_COST', // 风险金名称
'amount' => 300, // 风险金额 数字,必须>0(单位分)
],
‘attach’ => $order_no,// 商户数据包
‘notify_url’ => $notify_url,
‘openid’ => $openid,// 用户标识
‘need_user_confirm’ => false,// 是否需要用户确认
];

try {
$resp = $this->wepay->v3->payscore->serviceorder->post(
[
'json' => $json
]
);
$res = json_decode($resp->getBody(), true);

//写入日志文件
file_put_contents($_SERVER['DOCUMENT_ROOT'].”/logs/serviceorder.txt”, date(‘Y-m-d H:i:s’).”支付分订单创建回传数据:”.var_export($res,1).”\n”,FILE_APPEND);
exit;

// 入库支付分订单
return [
'code' => 0,
'msg' => '支付分订单创建完成',
];
} catch (\Exception $e) {
// 进行错误处理
if ($e->hasResponse()) {
$body = $e->getResponse()->getBody();
if ($body) {
return [
'code' => -1,
'msg' => (string)$body,
];
}
}
return ”;
}
}

五、请求接口出现“签名错误”排查方法顺序
接口返回:”签名错误”
原则,通过签名工具检查参数是否正确,签名sign值对上后,然后是密钥是否正确,多数情况是这个问题。
请按照以下几点进行排查:
1、使用签名检查工具(https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=20_1 )校验签名算法是否有误
2、确认秘钥是否有误(服务商模式使用服务商商户号秘钥,秘钥是在商户平台配置,如果同一商户号调用其它接口成功可排除是秘钥问题)
3、确认接口实际的请求参数与生成签名原串的参数一致,不能增加或缺少参数(可通过打印签名原串进行排查)
4、确认参数的大小写,参数名与接口文档一致
5、签名原串的参数值使用原始值,不需要encode
6、接口需要使用UTF-8编码,不支持UTF8非3字节编码的字符,如+
7、openid是否对应当前调起授权的微信号
8、H5授权跳转URL排查URL格式是否正确,如是否多了空格、车牌参数是否没有进行URL编码转义(注:车牌参数跳转url中需要转义,参与sign生成的时不需要)

本博文章基本上属于原创或收集整理,都是心血结晶。
欢迎转载分享,转载请注明出处,谢谢!
本文地址:微信支付WeChatPay构建客户端实例实践与疑难解答

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Ɣ回顶部