IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

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

    unvs发表于 2021-12-17 12:27:11
    love 0

    以下是以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生成的时不需要)



沪ICP备19023445号-2号
友情链接