PHP 微信支付V3版本SDK使用整理汇总

最近项目中需要使用微信支付,决定使用微信官方V3 SDK版本接口

项目支持的环境如下:

  • Guzzle 7.0,PHP >= 7.2.5
  • Guzzle 6.5,PHP >= 7.1.2

安装

composer require wechatpay/wechatpay

开始

ℹ️ 以下是 微信支付 API v3 的指引。如果你是 API v2 的使用者,请看 README_APIv2。

概念

  • 商户 API 证书,是用来证实商户身份的。证书中包含商户号、证书序列号、证书有效期等信息,由证书授权机构(Certificate Authority ,简称 CA)签发,以防证书被伪造或篡改。详情见 什么是商户API证书?如何获取商户API证书? 。

  • 商户 API 私钥。你申请商户 API 证书时,会生成商户私钥,并保存在本地证书文件夹的文件 apiclient_key.pem 中。为了证明 API 请求是由你发送的,你应使用商户 API 私钥对请求进行签名。

⚠️ 不要把私钥文件暴露在公共场合,如上传到 Github,写在 App 代码中等。

  • 微信支付平台证书。微信支付平台证书是指:由微信支付负责申请,包含微信支付平台标识、公钥信息的证书。你需使用微信支付平台证书中的公钥验证 API 应答和回调通知的签名。

ℹ️ 你需要先手工 下载平台证书 才能使用 SDK 发起请求。

  • 证书序列号。每个证书都有一个由 CA 颁发的唯一编号,即证书序列号。

获取微信支付平台证书(首次)

官方的

按官方文档使用各种报错,估计是因为有其他的库引起的

composer exec CertificateDownloader.php -- -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}

接口调用

/**
     * 获取证书
     * @return mixed
     */
    public static function certificates()
    {
        $wxPayConfig = config('wx_pay_config');
        $api_v3_key = $wxPayConfig['api_v3_key'] ?? '';     // 商户号
        $result = self::wx_curl_query('v3/certificates','GET');
        $result = json_decode($result, true);
        $ciphertext = $result['data'][0]['encrypt_certificate']['ciphertext'] ?? '';
        $nonce = $result['data'][0]['encrypt_certificate']['nonce'] ?? '';
        $associated_data = $result['data'][0]['encrypt_certificate']['associated_data'] ?? '';
        $decryptedMsg = AesGcm::decrypt($ciphertext, $api_v3_key, $nonce, $associated_data);
        $result['decryptedMsg'] = ($decryptedMsg); //解密后的内容,就是证书内容
        dump($result);
    }

    //get请求
    public static function wx_curl_query($urlV3 = '', $http_method = 'POST', $body = '')
    {
        $url = 'https://api.mch.weixin.qq.com/' . $urlV3;
        $config = config('wx_pay_config');;
        $mchId = $config['mchid'] ?? ''; // 商户ID
        $serial_no = $config['cert_serial'] ?? ''; // 证书序列号
        $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $mchId;
        // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyFilePath = $certPath . '/apiclient_key.pem';
        $mch_private_key = openssl_get_privatekey(file_get_contents($merchantPrivateKeyFilePath));;//私钥
        if(is_array($body)){
            $body = json_encode($body); // 接口参数
        }
        // 生成token 验签
        $timestamp = time();//时间戳
        $nonce = Formatter::nonce(32);//随机串
        $url_parts = parse_url($url);
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        //构造签名串
        $message = $http_method . "\n" .
            $canonical_url . "\n" .
            $timestamp . "\n" .
            $nonce . "\n" .
            $body . "\n";//报文主体
        //计算签名值
        openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);
        //设置HTTP头
        $token = sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $mchId, $nonce, $timestamp, $serial_no, $sign);
        $headers = [
            'Accept: application/json',
            'User-Agent: */*',
            'Content-Type: application/json; charset=utf-8',
            'Authorization: ' . $token,
            "Wechatpay-Serial:{$serial_no}"
        ];

        $info = curl_init();
        curl_setopt($info, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($info, CURLOPT_HEADER, 0);
        curl_setopt($info, CURLOPT_NOBODY, 0);
        curl_setopt($info, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($info, CURLOPT_SSL_VERIFYHOST, false);
        if ($http_method == 'POST') {
            curl_setopt($info, CURLOPT_POST, 1);
            curl_setopt($info, CURLOPT_POSTFIELDS, json_encode($body));
        }
        //设置header头
        curl_setopt($info, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($info, CURLOPT_URL, $url);
        $output = curl_exec($info);
        curl_close($info);
        return $output;
    }

获取平台证书列表

    /**
     * 获取平台证书列表
     * @return void
     * @author wzb
     * @date 2023/8/8 11:01
     */
    static function getCert()
    {
        // 设置参数
        [$instance, $wxPayConfig] = self::init();
        $api_v3_key = $wxPayConfig['api_v3_key'] ?? '';   // 「商户API证书」的「证书序列号」
        // 发送请求
        $resp = $instance->chain('v3/certificates')->get(
//            ['debug' => true] // 调试模式,https://docs.guzzlephp.org/en/stable/request-options.html#debug
        );
        $result = json_decode($resp->getBody(), true);
        $ciphertext = $result['data'][0]['encrypt_certificate']['ciphertext'] ?? '';
        $nonce = $result['data'][0]['encrypt_certificate']['nonce'] ?? '';
        $associated_data = $result['data'][0]['encrypt_certificate']['associated_data'] ?? '';
        $decryptedMsg = AesGcm::decrypt($ciphertext, $api_v3_key, $nonce, $associated_data);
        $result['ciphertext_msg'] = $decryptedMsg;
        dump($result);
    }
  static $merchantPrivateKeyInstance;   // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
	 /**
     * 初始化
     * @return array [$instance,$wxPayConfig];
     * @author wzb
     * @date 2023/8/8 11:05
     */
    public static function init()
    {
        // 设置参数
        $wxPayConfig = config('wx_pay_config');
        $mchId = $wxPayConfig['mchid'] ?? '';     // 商户号
        $serial = $wxPayConfig['cert_serial'] ?? '';   // 「商户API证书」的「证书序列号」
        $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $mchId;
        // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyFilePath = $certPath . '/apiclient_key.pem';
        self::$merchantPrivateKeyInstance = $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
        // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
        $platformCertificateFilePath = $certPath . '/cert.pem';
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
        // 从「微信支付平台证书」中获取「证书序列号」
        $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
        // 构造一个 APIv3 客户端实例
        $instance = Builder::factory([
            'mchid' => $mchId,
            'serial' => $serial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs' => [
                $platformCertificateSerial => $platformPublicKeyInstance,
            ],
        ]);
        return [$instance, $wxPayConfig];
    }

app支付

/**
     * 支付
     * @param $payData
     * @param $notifyUrl
     * @return array
     * @author wzb
     * @date 2023/8/8 11:01
     */
    static function pay($payData = [], $notifyUrl = '')
    {
        $notifyUrl = $notifyUrl ?? (config('host_api') . '/v1/notify/wx_notify');
        [$instance, $wxPayConfig] = self::init(); // 赋值给变量
        $merchantId = $wxPayConfig['mchid'] ?? '';     // 商户号
        $appid = $wxPayConfig['appid'] ?? '';     // 商户号
        // 调用下单
        $payJsonData = [
            'mchid' => $merchantId,
            'appid' => $appid,
            'out_trade_no' => $payData['out_trade_no'] ?? 0,
            'description' => $payData['description'] ?? 0,
            'notify_url' => $notifyUrl,
            'amount' => [
                'total' => $payData['money'] ?? 0,  // 分
                'currency' => 'CNY'
            ],
        ];
        try {
            $resp = $instance
                ->chain('v3/pay/transactions/app')
                ->post(['json' => $payJsonData]);

            if ($resp->getStatusCode() == 200) {
                $wo = json_decode($resp->getBody(), true); //prepay_id
                $params = [
                    'partnerId' => $appid,
                    'timeStamp' => (string)Formatter::timestamp(),
                    'nonceStr' => Formatter::nonce(),
                    'prepayId' => $wo["prepay_id"],
                ];
                $params += ['sign' => Rsa::sign(
                    Formatter::joinedByLineFeed(...array_values($params)),
                    self::$merchantPrivateKeyInstance
                )];
                return [1, $params];
            } else {
                return [0, '微信支付失败'];
            }
        } catch (\Exception $e) {
            $code = $e->getCode();
            $data = '';
            // 进行错误处理
            if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
                $r = $e->getResponse();
                $data = json_decode($r->getBody(), true);
                LogHelperUtil::outLog("wx_pay_error", $data, "wx_pay");
            }
        }
        return [$code, $data];
        // end
    }

app回调

/**
     * 回调
     * @param $header
     * @param $inBody
     * @return array|string[]|void
     * @author wzb
     * @date 2023/8/8 11:01
     */
    static function notify($header, $inBody)
    {
        $inWechatpaySignature = $header['wechatpay-signature'];
        $inWechatpayTimestamp = $header['wechatpay-timestamp'];
        $inWechatpaySerial = $header['wechatpay-serial'];
        $inWechatpayNonce = $header['wechatpay-nonce'];
        if (!$inWechatpaySignature or !$inWechatpayTimestamp or !$inWechatpaySerial or !$inWechatpayNonce) {
            header("Location:/404.html");
            exit;
        }
        $wxPayConfig = config('wx_pay_config');
        $merchantId = $wxPayConfig['mchid'] ?? '';     // 商户号
        $apiv3Key = $wxPayConfig['api_v3_key'] ?? '';// 在商户平台上设置的APIv3密钥
        $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $merchantId;
        // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
        $platformCertificateFilePath = $certPath . '/cert.pem';
        // 根据通知的平台证书序列号,查询本地平台证书文件,这里是自己生成的证书
        $platformPublicKeyInstance = Rsa::from(file_get_contents($platformCertificateFilePath), Rsa::KEY_TYPE_PUBLIC);
        // 检查通知时间偏移量,允许5分钟之内的偏移
        $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
        $verifiedStatus = Rsa::verify(
        // 构造验签名串
            Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
            $inWechatpaySignature,
            $platformPublicKeyInstance
        );
        if ($timeOffsetStatus && $verifiedStatus) {
            // 转换通知的JSON文本消息为PHP Array数组
            $inBodyArray = (array)json_decode($inBody, true);
            // 使用PHP7的数据解构语法,从Array中解构并赋值变量
            ['resource' => [
                'ciphertext' => $ciphertext,
                'nonce' => $nonce,
                'associated_data' => $aad
            ]] = $inBodyArray;
            // 加密文本消息解密
            $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
            // 把解密后的文本转换为PHP Array数组
            return (array)json_decode($inBodyResource, true);
        } else {
            return ['trade_state' => 'FAIL'];
        }
    }

退款


    /**
     * 退款
     * @param $orderSn
     * @param $tradeNo
     * @param $refundMoney
     * @return array|mixed array [$code,$data] $code=1 成功
     * @author wzb
     * @date 2023/8/8 9:09
     */
    static function refundOrder($orderSn, $tradeNo, $refundMoney)
    {
        [$instance, $wxPayConfig] = self::init();
        try {
            $response = $instance
                ->chain('v3/refund/domestic/refunds')
                ->post([
                    'json' => [
                        'transaction_id' => $tradeNo,
                        'out_refund_no' => $orderSn,
                        'amount' => [
                            'refund' => $refundMoney,
                            'total' => $refundMoney,
                            'currency' => 'CNY',
                        ],
                    ],
                ]);
            // 正常逻辑回调处理
            $code = $response->getStatusCode();
            $response = json_decode($response->getBody(), true);
            $status = $response['status'] ?? '';
            if ($code != 200) {
                DingDingUtil::sendDingtalkText("微信退款未知原因!订单号:{$orderSn} 错误原因:" . json_encode($response));
            }
            return [1, $response];
        } catch (\Exception $e) {
            // 异常错误处理
            if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
                $r = $e->getResponse();
                $code = $r->getStatusCode();
                $response = json_decode($r->getBody(), true);
                $subCode = $response['code'] ?? '';
                $subMsg = $response['message'] ?? '';
                $listErrorCode = [
                    'NOT_ENOUGH',  //	余额不足',
                    'MCH_NOT_EXISTS',  //MCHID不存在',
                    'NO_AUTH',  //没有退款权限',
                ];
                if (in_array($subCode, $listErrorCode)) {
                    DingDingUtil::sendDingtalkText("微信退款失败!\n订单号:{$orderSn} \n错误原因:" . $subMsg);
                }
                return [$code, $response];
            }
        }
        return [0, '退款失败'];
    }

封装PayWxUtil文件



namespace pay;

use util\DingDingUtil;
use util\LogHelperUtil;
use WeChatPay\Builder;
use WeChatPay\ClientJsonTrait;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;

class PayWxUtil
{

    static $merchantPrivateKeyInstance;   // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名

    /**
     * 初始化
     * @return array [$instance,$wxPayConfig];
     * @author wzb
     * @date 2023/8/8 11:05
     */
    public static function init()
    {
        // 设置参数
        $wxPayConfig = config('wx_pay_config');
        $mchId = $wxPayConfig['mchid'] ?? '';     // 商户号
        $serial = $wxPayConfig['cert_serial'] ?? '';   // 「商户API证书」的「证书序列号」
        $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $mchId;
        // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyFilePath = $certPath . '/apiclient_key.pem';
        self::$merchantPrivateKeyInstance = $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
        // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
        $platformCertificateFilePath = $certPath . '/cert.pem';
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
        // 从「微信支付平台证书」中获取「证书序列号」
        $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
        // 构造一个 APIv3 客户端实例
        $instance = Builder::factory([
            'mchid' => $mchId,
            'serial' => $serial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs' => [
                $platformCertificateSerial => $platformPublicKeyInstance,
            ],
        ]);
        return [$instance, $wxPayConfig];
    }

    /**
     * 支付
     * @param $payData
     * @param $notifyUrl
     * @return array
     * @author wzb
     * @date 2023/8/8 11:01
     */
    static function pay($payData = [], $notifyUrl = '')
    {
        $notifyUrl = $notifyUrl ?? (config('host_api') . '/v1/notify/wx_notify');
        [$instance, $wxPayConfig] = self::init(); // 赋值给变量
        $merchantId = $wxPayConfig['mchid'] ?? '';     // 商户号
        $appid = $wxPayConfig['appid'] ?? '';     // 商户号
        // 调用下单
        $payJsonData = [
            'mchid' => $merchantId,
            'appid' => $appid,
            'out_trade_no' => $payData['out_trade_no'] ?? 0,
            'description' => $payData['description'] ?? 0,
            'notify_url' => $notifyUrl,
            'amount' => [
                'total' => $payData['money'] ?? 0,  // 分
                'currency' => 'CNY'
            ],
        ];
        try {
            $resp = $instance
                ->chain('v3/pay/transactions/app')
                ->post(['json' => $payJsonData]);

            if ($resp->getStatusCode() == 200) {
                $wo = json_decode($resp->getBody(), true); //prepay_id
                $params = [
                    'partnerId' => $appid,
                    'timeStamp' => (string)Formatter::timestamp(),
                    'nonceStr' => Formatter::nonce(),
                    'prepayId' => $wo["prepay_id"],
                ];
                $params += ['sign' => Rsa::sign(
                    Formatter::joinedByLineFeed(...array_values($params)),
                    self::$merchantPrivateKeyInstance
                )];
                return [1, $params];
            } else {
                return [0, '微信支付失败'];
            }
        } catch (\Exception $e) {
            $code = $e->getCode();
            $data = '';
            // 进行错误处理
            if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
                $r = $e->getResponse();
                $data = json_decode($r->getBody(), true);
                LogHelperUtil::outLog("wx_pay_error", $data, "wx_pay");
            }
        }
        return [$code, $data];
        // end
    }

    /**
     * 退款
     * @param $orderSn
     * @param $tradeNo
     * @param $refundMoney
     * @return array|mixed array [$code,$data] $code=1 成功
     * @author wzb
     * @date 2023/8/8 9:09
     */
    static function refundOrder($orderSn, $tradeNo, $refundMoney)
    {
        [$instance, $wxPayConfig] = self::init();
        try {
            $response = $instance
                ->chain('v3/refund/domestic/refunds')
                ->post([
                    'json' => [
                        'transaction_id' => $tradeNo,
                        'out_refund_no' => $orderSn,
                        'amount' => [
                            'refund' => $refundMoney,
                            'total' => $refundMoney,
                            'currency' => 'CNY',
                        ],
                    ],
                ]);
            // 正常逻辑回调处理
            $code = $response->getStatusCode();
            $response = json_decode($response->getBody(), true);
            $status = $response['status'] ?? '';
            if ($code != 200) {
                DingDingUtil::sendDingtalkText("微信退款未知原因!订单号:{$orderSn} 错误原因:" . json_encode($response));
            }
            return [1, $response];
        } catch (\Exception $e) {
            // 异常错误处理
            if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
                $r = $e->getResponse();
                $code = $r->getStatusCode();
                $response = json_decode($r->getBody(), true);
                $subCode = $response['code'] ?? '';
                $subMsg = $response['message'] ?? '';
                $listErrorCode = [
                    'NOT_ENOUGH',  //	余额不足',
                    'MCH_NOT_EXISTS',  //MCHID不存在',
                    'NO_AUTH',  //没有退款权限',
                ];
                if (in_array($subCode, $listErrorCode)) {
                    DingDingUtil::sendDingtalkText("微信退款失败!\n订单号:{$orderSn} \n错误原因:" . $subMsg);
                }
                return [$code, $response];
            }
        }
        return [0, '退款失败'];
    }

    /**
     * 回调
     * @param $header
     * @param $inBody
     * @return array|string[]|void
     * @author wzb
     * @date 2023/8/8 11:01
     */
    static function notify($header, $inBody)
    {
        $inWechatpaySignature = $header['wechatpay-signature'];
        $inWechatpayTimestamp = $header['wechatpay-timestamp'];
        $inWechatpaySerial = $header['wechatpay-serial'];
        $inWechatpayNonce = $header['wechatpay-nonce'];
        if (!$inWechatpaySignature or !$inWechatpayTimestamp or !$inWechatpaySerial or !$inWechatpayNonce) {
            header("Location:/404.html");
            exit;
        }
        $wxPayConfig = config('wx_pay_config');
        $merchantId = $wxPayConfig['mchid'] ?? '';     // 商户号
        $apiv3Key = $wxPayConfig['api_v3_key'] ?? '';// 在商户平台上设置的APIv3密钥
        $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $merchantId;
        // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
        $platformCertificateFilePath = $certPath . '/cert.pem';
        // 根据通知的平台证书序列号,查询本地平台证书文件,这里是自己生成的证书
        $platformPublicKeyInstance = Rsa::from(file_get_contents($platformCertificateFilePath), Rsa::KEY_TYPE_PUBLIC);
        // 检查通知时间偏移量,允许5分钟之内的偏移
        $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
        $verifiedStatus = Rsa::verify(
        // 构造验签名串
            Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
            $inWechatpaySignature,
            $platformPublicKeyInstance
        );
        if ($timeOffsetStatus && $verifiedStatus) {
            // 转换通知的JSON文本消息为PHP Array数组
            $inBodyArray = (array)json_decode($inBody, true);
            // 使用PHP7的数据解构语法,从Array中解构并赋值变量
            ['resource' => [
                'ciphertext' => $ciphertext,
                'nonce' => $nonce,
                'associated_data' => $aad
            ]] = $inBodyArray;
            // 加密文本消息解密
            $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
            // 把解密后的文本转换为PHP Array数组
            return (array)json_decode($inBodyResource, true);
        } else {
            return ['trade_state' => 'FAIL'];
        }
    }


    /**
     * 获取平台证书列表
     * @return void
     * @author wzb
     * @date 2023/8/8 11:01
     */
    static function getCert()
    {
        // 设置参数
        [$instance, $wxPayConfig] = self::init();
        $api_v3_key = $wxPayConfig['api_v3_key'] ?? '';   // 「商户API证书」的「证书序列号」
        // 发送请求
        $resp = $instance->chain('v3/certificates')->get(
//            ['debug' => true] // 调试模式,https://docs.guzzlephp.org/en/stable/request-options.html#debug
        );
        $result = json_decode($resp->getBody(), true);
        $ciphertext = $result['data'][0]['encrypt_certificate']['ciphertext'] ?? '';
        $nonce = $result['data'][0]['encrypt_certificate']['nonce'] ?? '';
        $associated_data = $result['data'][0]['encrypt_certificate']['associated_data'] ?? '';
        $decryptedMsg = AesGcm::decrypt($ciphertext, $api_v3_key, $nonce, $associated_data);
        $result['ciphertext_msg'] = $decryptedMsg;
        dump($result);
    }

    /**
     * 获取证书
     * @return mixed
     */
    public static function certificates()
    {
        $wxPayConfig = config('wx_pay_config');
        $api_v3_key = $wxPayConfig['api_v3_key'] ?? '';     // 商户号
        $result = self::wx_curl_query('v3/certificates','GET');
        $result = json_decode($result, true);
        $ciphertext = $result['data'][0]['encrypt_certificate']['ciphertext'] ?? '';
        $nonce = $result['data'][0]['encrypt_certificate']['nonce'] ?? '';
        $associated_data = $result['data'][0]['encrypt_certificate']['associated_data'] ?? '';
        $decryptedMsg = AesGcm::decrypt($ciphertext, $api_v3_key, $nonce, $associated_data);
        $result['decryptedMsg'] = ($decryptedMsg); //解密后的内容,就是证书内容
        dump($result);
    }

    //get请求
    public static function wx_curl_query($urlV3 = '', $http_method = 'POST', $body = '')
    {
        $url = 'https://api.mch.weixin.qq.com/' . $urlV3;
        $config = config('wx_pay_config');;
        $mchId = $config['mchid'] ?? ''; // 商户ID
        $serial_no = $config['cert_serial'] ?? ''; // 证书序列号
        $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $mchId;
        // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyFilePath = $certPath . '/apiclient_key.pem';
        $mch_private_key = openssl_get_privatekey(file_get_contents($merchantPrivateKeyFilePath));;//私钥
        if(is_array($body)){
            $body = json_encode($body); // 接口参数
        }
        // 生成token 验签
        $timestamp = time();//时间戳
        $nonce = Formatter::nonce(32);//随机串
        $url_parts = parse_url($url);
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        //构造签名串
        $message = $http_method . "\n" .
            $canonical_url . "\n" .
            $timestamp . "\n" .
            $nonce . "\n" .
            $body . "\n";//报文主体
        //计算签名值
        openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);
        //设置HTTP头
        $token = sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $mchId, $nonce, $timestamp, $serial_no, $sign);
        $headers = [
            'Accept: application/json',
            'User-Agent: */*',
            'Content-Type: application/json; charset=utf-8',
            'Authorization: ' . $token,
            "Wechatpay-Serial:{$serial_no}"
        ];

        $info = curl_init();
        curl_setopt($info, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($info, CURLOPT_HEADER, 0);
        curl_setopt($info, CURLOPT_NOBODY, 0);
        curl_setopt($info, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($info, CURLOPT_SSL_VERIFYHOST, false);
        if ($http_method == 'POST') {
            curl_setopt($info, CURLOPT_POST, 1);
            curl_setopt($info, CURLOPT_POSTFIELDS, json_encode($body));
        }
        //设置header头
        curl_setopt($info, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($info, CURLOPT_URL, $url);
        $output = curl_exec($info);
        curl_close($info);
        return $output;
    }

}

服务端SDK

你可能感兴趣的:(PHP,微信,php,微信,开发语言)