请输入图片描述

<?php
namespace app\api\controller;

use Think\Exception;
use think\facade\Log;

class WeChatPay
{

    protected $_config;
    const KEY_LENGTH_BYTE = 32;
    const AUTH_TAG_LENGTH_BYTE = 16;

    public function __construct()
    {
        // 初始化一个支付对象
        $this->_config = config('app.wechatPay');
    }

    /**
     * Undocumented function
     *
     * @param [type] $total
     * @param [type] $desc
     * @param [type] $out_trade_no
     * @return void
     */
    public function pay($total, $orderNo, $desc, $ip) 
    {
        $payConfig = $this->_config;
        // 创建请求参数
        $param   = [
            'appid'        =>  $payConfig['appid'],
            'mchid'        =>  $payConfig['mchid'],
            'description'  =>  "盲盒-".$desc,
            'out_trade_no' =>  $orderNo,
            'notify_url'   =>  $payConfig['notify_url'],
            'amount'       => [
                    'total'    => $total * 100,
                    'currency' => 'CNY'
            ],
            'scene_info'   => [
                'payer_client_ip' => $ip,
                'h5_info'         => [
                       'type' => 'Wap',
                ],
            ]
        ];
        $headers = $this->sign('POST',$payConfig['pay_url'],json_encode($param));
        return $this->sendHttp($payConfig['pay_url'], $param, 'POST', $headers);
    }

    /**
     * 签名
     * @param string $http_method    请求方式GET|POST
     * @param string $url            url
     * @param string $body           报文主体
     * @return array
     */
    public function sign($http_method = 'POST',$url = '',$body = ''){
        $mch_private_key = $this->getMchKey();      //私钥
        $timestamp       = time();                  //时间戳
        $nonce           = $this->getRandomStr(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头
        $payConf = $this->_config;
        $token = sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $payConf['mchid'], $nonce, $timestamp, $payConf['serial_no'], $sign);
        $headers = [
            'Accept: application/json',
            'User-Agent: */*',
            'Content-Type: application/json; charset=utf-8',
            'Authorization: '.$token,
        ];
        return $headers;
    }


    /**
     *  curl post or get
     *
     * @param string $url
     * @param [type] $params
     * @param string $method
     * @param array $header
     * @param boolean $multi
     * @return void
     */
    public function sendHttp($url, $params, $method = 'GET', $header = array(), $multi = false)
    {
        $uuid = uniqid();
        Log::info('['. $uuid .']' .__FILE__ . __DIR__. '请求开始');
        Log::info('['. $uuid .']Request url:' . $method . '=>' . $url);
        Log::info('['. $uuid .']Request data:' . json_encode($params));
        Log::info('['. $uuid .']Request header:' . json_encode($header));
        
        $opts = array(
            CURLOPT_TIMEOUT        => 30,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_HTTPHEADER     => $header
        );
        // 根据请求类型设置特定参数
        switch(strtoupper($method)){
            case 'GET':
                $opts[CURLOPT_URL] = $url . '?' . http_build_query($params);
                break;
            case 'POST':
                //判断是否传输文件
                $params = $multi ? $params : json_encode($params);
                $opts[CURLOPT_URL] = $url;
                $opts[CURLOPT_POST] = 1;
                $opts[CURLOPT_POSTFIELDS] = $params;
                $opts[CURLOPT_ENCODING] = '';
                break;
            default:
                throw new Exception('不支持的请求方式!');
        }
        // 初始化并执行curl请求
        try {
            $ch = curl_init();
            $ch = curl_init();
            curl_setopt_array($ch, $opts);
            $data = curl_exec($ch);
            curl_close($ch);
        } catch (Exception $e) {
           Log::error('['. $uuid .']Response Error:' . $e->getMessage());
           throw new Exception('请求发生错误:' .  $e->getMessage());
        }
        $res = mb_convert_encoding($data, 'UTF-8', 'UTF-8,GBK,GB2312,BIG5');
        Log::info('['. $uuid .']Response Info:' . json_encode($res));
        return  $res;
    }


    /**
     * 读取私钥
     *
     * @return void
     */
    public function getMchKey(){
        //path->私钥文件存放路径
        $path = __DIR__ . "/cert/apiclient_key.pem";
        return openssl_get_privatekey(file_get_contents($path));
    }


    /**
     * 获得随机字符串
     * @param $len      integer       需要的长度
     * @param $special  bool      是否需要特殊符号
     * @return string       返回随机字符串
     */
    public function getRandomStr($len, $special=false){
        $chars = array(
            "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
            "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
            "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",
            "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
            "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",
            "3", "4", "5", "6", "7", "8", "9"
        );

        if($special){
            $chars = array_merge($chars, array(
                "!", "@", "#", "$", "?", "|", "{", "/", ":", ";",
                "%", "^", "&", "*", "(", ")", "-", "_", "[", "]",
                "}", "<", ">", "~", "+", "=", ",", "."
            ));
        }

        $charsLen = count($chars) - 1;
        shuffle($chars);                            //打乱数组顺序
        $str = '';
        for($i=0; $i<$len; $i++){
            $str .= $chars[mt_rand(0, $charsLen)];    //随机取出一位
        }
        return $str;
    }


    public function decryptToString($associatedData, $nonceStr, $ciphertext)
    {
        $ciphertext = \base64_decode($ciphertext);
        if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
            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, $this->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, $this->aesKey);
        }

        // openssl (PHP >= 7.1 support AEAD)
        if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
            $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
            $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);

            return \openssl_decrypt($ctext, 'aes-256-gcm', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,
                $authTag, $associatedData);
        }

        throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }
    
}
最后修改:2022 年 03 月 27 日 09 : 00 PM
如果觉得我的文章对你有用,请随意赞赏