<?php
namespace App\Services;

use Illuminate\Database\Eloquent\Model;
use BlueCity\Core\Service\Service;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use App\Models\Common;
use Illuminate\Support\Facades\DB;
use BlueCity\Core\Inc\ErrorInc;
use App\Http\Models\Services\ImageUpload;
use App\Models\OperationLog;
use Illuminate\Http\Request;
use App\Http\Requests\Validators\Business\WashPayValidator;

class WashPayservice extends Service
{
    public static $instance;
    public $modelMain = null;
    public $mainValidator = null;
 
    public function __construct()
    {
        $this->mainValidator = new WashPayValidator();
    }

    /**
     * @return mixed
     */
    public static function getInstance()
    {
        if (is_null(static::$instance)) {
            static::$instance = new static;
        }
        return static::$instance;
    }

    /**
     * 微信电商收付通API V3
     * 不同的商户，对应的微信支付平台证书是不一样的，平台证书会周期性更换。建议商户定时通过API下载新的证书，不要依赖人工更换证书。
     * 微信支付的平台证书序列号位于HTTP头Wechatpay-Serial。验证签名前，请商户先检查序列号是否跟商户当前所持有的微信支付平台证书的序列号一致。
     * 生成签名的serial_no是网站上面的证书的序列号。而HTTP头Wechatpay-Serial以post方式带上的serial_no是实时获取的serial_no。
     * 后期可以做缓存serial_no，并且缓存新证书内容，不需要每次都获取且保存文件。
     * 如果不一致，请重新获取证书。否则，签名的私钥和证书不匹配，将无法成功验证签名。
     */
    // get方式 提交 body不参与签名
    public static function _Getresponse($url, $body = '')
    {
        $methed = 'GET';
        $date = time();
        $nonce = self::createNoncestr();
        $sign = self::sign($url, $methed, $date, $nonce, $body); //$http_method要大写
        $header[] = 'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36';
        $header[] = 'Accept:application/json';
        $header[] = 'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $sign;
        $r = self::_requestGet($url, $body, $header);
        return $r;
    }

    public static function _Postresponse($url, $body)
    {
        $methed = 'POST';
        $date = time();
        if (is_array($body)) {
            $body = json_encode($body);
        }
        $nonce = self::createNoncestr();
        $sign = self::sign($url, $methed, $date, $nonce, $body);
        $serial_nos = self::certificates();
        $header[] = 'User-Agent:233';
        $header[] = 'Accept:application/json';
        $header[] = 'Content-Type:application/json';
        $header[] = 'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $sign;
        $header[] = 'Wechatpay-Serial:' . $serial_nos;
        $r = self::_requestPost($url, $body, $header);
        return $r;
    }
    /**
     * [_requestGet CURL GET请求]
     * @param  [type] $url    [请求目标]
     * @param  [type] $meta   [请求参数]
     * @param  [type] $header [头部参数]
     * @return [type]         [结果返回]
     */
    private static function _requestGet($url, $meta, $header = array(), $referer = '',  $timeout = 30)
    {
        $ch = curl_init();
        //设置抓取的url
        curl_setopt($ch, CURLOPT_URL, $url);
        //设置头文件的信息作为数据流输出
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        //设置获取的信息以文件流的形式返回，而不是直接输出。
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        //执行命令
        $response = curl_exec($ch);
        if ($error = curl_error($ch)) {
            curl_close($ch);
            throw new WxPayv3Exception($error);
        }
        curl_close($ch);
        return $response;
    }

    /**
     * [_requestPost CURL POST请求]
     * @param  [type]  $url     [请求目标]
     * @param  [type]  $data    [请求参数]
     * @param  array   $header  [头部参数]
     * @param  string  $referer [referer]
     * @param  integer $timeout [超时时间：单位秒]
     * @return [type]           [结果返回]
     */
    private static function _requestPost($url, $data, $header = array(), $referer = '', $timeout = 30)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        //避免https 的ssl验证
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_SSLVERSION, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_POST, false);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        // 模拟来源
        curl_setopt($ch, CURLOPT_REFERER, $referer);
        $response = curl_exec($ch);
        if ($error = curl_error($ch)) {
            curl_close($ch);
            throw new WxPayv3Exception($error);
        }
        curl_close($ch);
        return $response;
    }

    /**|---------------------------------------------------------------------------------
     * @name 模拟post请求
     * @param string $url 请求链接
     * @param array $param 请求参数
     * @param array $header 请求头
     * @retrun multitype:array
     * @author dou 2021/8/9 19:35
     * |---------------------------------------------------------------------------------
    */
    public static function requestPost($url,$param,$header='') {
        /*$header = array(
            'Content-Type: application/json',
        );*/
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        if($header!=''){
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        }
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POST, 1);
        // https 请求//如果地址是https协议，且CURLOPT_SSL_VERIFYPEER和CURLOPT_SSL_VERIFYHOST打开了，则需要加载证书
        if (strlen($url) > 5 && strtolower(substr($url, 0, 5)) == "https") {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        }
        curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
        if (is_array($param) && 0 < count($param)) {
            $postMultipart = false;
            foreach ($param as $k => $v) {
                if ('@' == substr($v, 0, 1)) {
                    $postMultipart = true;
                    break;
                }
            }
            unset($k, $v);
            if ($postMultipart) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $param);// post传输数据
            } else {
                curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($param));
            }
        }else{
            curl_setopt($ch, CURLOPT_POSTFIELDS, $param);// post传输数据
        }
        $response = curl_exec($ch);
        curl_close($ch);
        return trim($response);
    }

    /**[sign 签名]
     * [sign 签名]
     * @param  [type] $url             [请求目标]
     * @param  [type] $http_method     [请求方式 GET POST PUT]
     * @param  [type] $timestamp       [时间戳]
     * @param  [type] $nonce           [随机串]
     * @param  [type] $body            [报文 GET请求时可以为空]
     * @param  [type] $mch_private_key [api 密钥]
     * @param  [type] $merchant_id     [发起请求的商户（包括直连商户、服务商或渠道商）的商户号mchid]
     * @param  [type] $serial_no       [证书序列号]
     * @return [type]                  [返回为签名串]
     */
    public static function sign($url, $http_method, $timestamp, $nonce, $body = '')
    {

        if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) {
            throw new WxPayv3Exception("当前PHP环境不支持SHA256withRSA");
        }

        //发起请求的商户（包括直连商户、服务商或渠道商）的商户号mchid
        // $merchant_id    =  Config::MCHID;
        $merchant_id    = config('apisystem.MCHID');
        //商户API证书序列号
        // $serial_no      =  Config::SERIAL_NO;
        $serial_no      =  config('apisystem.SERIAL_NO');
        //获取私钥
        // $mch_private_key = self::getPrivateKey(getcwd() . Config::SSLKEY_PATH);       //商户私钥
        $mch_private_key = self::getPrivateKey(getcwd() . config('apisystem.SSLKEY_PATH'));       //商户私钥

        $url_parts = parse_url($url);

        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        if ($http_method == 'GET') {
            $body = '';
        }
        $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);
        $token = sprintf(
            'mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $merchant_id,
            $nonce,
            $timestamp,
            $serial_no,
            $sign
        );
        return $token;
    }


    /**
     * 吊起支付 封裝
     * @param string appid 微信公众号的APPID或者小程序的appid
     * @param string prepay_id 合单支付返回的prepay_id
     */
    public static function _PayJson($appid, $prepay_id)
    {
        $timestamp = (string)time();
        $nonceStr = self::createNoncestr();
        $package = 'prepay_id=' . $prepay_id;
        $paySign = self::paySign($appid, $timestamp, $nonceStr, $package);
        $data = [
            'appId'    => $appid,
            'timeStamp' => $timestamp,
            'nonceStr' => $nonceStr,
            'package'  => $package,
            'signType' => 'RSA',
            'paySign'  => $paySign
        ];
        return json_encode($data);
    }

    /**
     * 支付pay计算签名
     * @param string appid 微信公众号的APPID或者小程序的appid
     * @param string timestamp 时间戳
     * @param string nonceStr 随机字符串
     * @param string body 签名主体内容 prepay_id=wx_12121
     */
    private static function paySign($appid, $timestamp, $nonceStr, $body)
    {
        if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) {
            throw new WxPayv3Exception("当前PHP环境不支持SHA256withRSA");
        }
        // $mch_private_key = self::getPrivateKey(getcwd() . Config::SSLKEY_PATH);       //商户私钥
        $mch_private_key = self::getPrivateKey(getcwd() . config('apisystem.SSLKEY_PATH'));       //商户私钥
        $message =
            $appid . "\n" .
            $timestamp . "\n" .
            $nonceStr . "\n" .
            $body . "\n";
        openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
        $paySign = base64_encode($raw_sign);
        return $paySign;
    }

    //获取私钥
    private static function getPrivateKey($filepath)
    {
        return openssl_get_privatekey(file_get_contents($filepath));
    }

    //加载证书 公钥
    public static function getCertificate($filepath)
    {
        return openssl_x509_read(file_get_contents($filepath));
    }
    //作用：产生随机字符串，不长于32位
    private static function createNoncestr($length = 32)
    {
        $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     *  [getEncrypt V3敏感信息进行加密]  
     *  使用的是公钥 ok
     * @param string str要加密的内容 
     */
    public static function getEncrypt($str)
    {
        //$public_key_path = '证书地址'; //看情况使用证书， 个别接口证书 使用的是 平台证书而不是 api证书
        // $mch_public_key = self::getCertificate(getcwd() . Config::SSLCERT_PATH);
        // $mch_public_key = self::getCertificate(getcwd() . config('apisystem.SSLCERT_PATH'));
        // var_dump(getcwd() . config('apisystem.SSLCERT_PATH'));die;
        $mch_public_key = self::getCertificate(getcwd() . '/pay/weixin/cert/apiclient_20220622_cert.pem');

        $encrypted = '';

        if (openssl_public_encrypt($str, $encrypted, $mch_public_key, OPENSSL_PKCS1_OAEP_PADDING)) {
            //base64编码 
            $sign = base64_encode($encrypted);
        } else {
            throw new WxPayv3Exception('加密encrypt failed');
        }
        return $sign;
    }
    /**
     * [getPrivateEncrypt V3敏感信息进行解密]  
     * @param string str 需要解密的秘闻 ok
     */
    public static function getPrivateEncrypt($str)
    {
        $result = false;
        $str = base64_decode($str);
        $mch_private_key = self::getPrivateKey(getcwd() . Config::SSLKEY_PATH);
        if (openssl_private_decrypt($str, $result, $mch_private_key, OPENSSL_PKCS1_OAEP_PADDING)) {
            return $result;
        } else {
            throw new WxPayv3Exception('解密Encrypt failed');
        }
    }
    /**
     * [decryptToString V3 证书和回调报文解密]  
     * @param stingr    $aesKey             V3签名
     * @param string    $associatedData     附加数据包
     * @param string    $nonceStr           加密使用的随机串初始化向量）
     * @param string    $ciphertext         Base64编码后的密文
     *
     * @return string|bool      Decrypted string on success or FALSE on failure
     */
    public static function decryptToString($associatedData, $nonceStr, $ciphertext)
    {
        // $aesKey = Config::APPKERV3;
        $aesKey = config('apisystem.APPKERV3');
        if (strlen($aesKey) != 32) {
            throw new WxPayv3Exception('无效的ApiV3Key，长度应为32个字节');
        }

        $ciphertext = \base64_decode($ciphertext);
        if (strlen($ciphertext) <= 16) {
            throw new WxPayv3Exception('无效的数据密文，长度应为16个字节以上');
        }

        // 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);
        }

        // openssl (PHP >= 7.1 support AEAD)
        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 WxPayv3Exception('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }

    /**
     * 获取毫秒级别的时间戳
     */
    private static function getMillisecond()
    {
        $time = explode(" ", microtime());
        $time = $time[1] . ($time[0] * 1000);
        $time2 = explode(".", $time);
        $time = $time2[0];
        return $time;
    }

    /** 
     * [certificates 保存获取的平台证书，且返回证书的序列号serial_no] 缓存一天时间
     * @return string serial_no证书的序列号
     */
    public static function certificates()
    {
        if (file_exists(getcwd() . '/vendor/wechat3/cert/serial_no.txt')) {
            if ($serial_nos = file_get_contents(getcwd() . '/vendor/wechat3/cert/serial_no.txt')) {
                if ($serial_nos) {
                    $serial_nos = json_decode($serial_nos,true);
                    if (time() < $serial_nos['time']) { //如果 1天缓存未过期，直接使用。
                        return $serial_nos['serial_no'];
                    }
                }
            }  
        }
              
        $url = 'https://api.mch.weixin.qq.com/v3/certificates';
        $r = self::_Getresponse($url);
        $r = json_decode($r, true);
        if (isset($r['code'])) {
            return $r;
        }
        $r = $r['data'];
        if (is_array($r)) {
            $associatedData = $r[0]['encrypt_certificate']['associated_data'];
            $nonceStr = $r[0]['encrypt_certificate']['nonce'];
            $ciphertext = $r[0]['encrypt_certificate']['ciphertext'];
            try {
                $data1 =  self::decryptToString($associatedData, $nonceStr, $ciphertext);
                $serial_no = $r[0]['serial_no'];
                file_put_contents(getcwd() . '/pay/weixin/cert/'.'apiclient_'.date('Ymd').'_cert'.'.pem', $data1);
                unset($serial_nos);
                $serial_nos = [
                    'time' => time() + 24 * 3600,
                    'serial_no' => $serial_no
                ];
                file_put_contents(getcwd() . '/pay/weixin/cert/serial_no.txt', json_encode($serial_nos));
            } catch (WxPayv3Exception $th) {
                throw $th;
            }
        }
        return $serial_no;
    }

    // 统一下单
    public static function transactionsjsapi ($request)
    {
        if (!isset($request['weus_openid'])) {
            $request['weus_openid'] = DB::table('weixin_user')
                ->where('weus_id', $request['weus_id'])
                ->value('weus_openid');
        }
        // 查询小程序订单的数据
        $find_data = DB::table('order as ord')
            ->leftJoin('order_goods as orgo', 'orgo.orgo_ord_id', '=', 'ord.ord_id')
            ->where('ord_id', $request['ord_id'])
            ->select('ord_id', 'ord_order_number', 'ord_actual_price', 'ord_order_price', 'ord_state', 'orgo_goo_name', 'ord_shop_id')
            ->first();
        if (empty($find_data)) {
            return ['code'=>500, 'msg'=>'请选择支付订单', 'data'=>''];
        }
        if ($find_data->ord_order_price == 0) {
            DB::beginTransaction();
            $find_order = DB::table('order')
                ->leftjoin('order_client', 'ord_id', '=', 'orcl_ord_id')
                ->where('ord_id', $request['ord_id'])
                ->first();
            //修改订单信息
            $update_data['ord_state'] = 2;
            $update_data['ord_pay_state'] = 1;
            $update_data['ord_update_time'] = date('Y-m-d H:i:s');
            $update_data['ord_settlement_time'] = date('Y-m-d H:i:s');
            $update_res = DB::table('order')
                ->where('ord_id', $request['ord_id'])
                ->update($update_data);
            // 修改已售数量
            $pait_data = DB::table('order as o')
                ->leftjoin('order_goods as orgo', 'orgo.orgo_ord_id', '=', 'o.ord_id')
                ->leftjoin('package_items as pait', 'pait.pait_id', '=', 'orgo.orgo_goo_id')
                ->where('ord_id', $request['ord_id'])
                ->where('orgo_goo_type', 3)
                ->select('pait_id', 'pait_sell_number', 'pait_saleable_quantity', 'ord_type')
                ->first();
            if ($pait_data) {
                $update_pait_data['pait_sell_number'] = $pait_data->pait_sell_number + 1;
                $update_pait_data['pait_saleable_quantity'] = $pait_data->pait_saleable_quantity - 1;
                if ($pait_data->pait_saleable_quantity - 1 == 0) {
                    $update_pait_data['pait_state'] = 2;//1上架2下架
                }
                $update_pait_data['pait_update_time'] = date('Y-m-d H:i:s');
                $update_pait = DB::table('package_items')
                    ->where('pait_id', $pait_data->pait_id)
                    ->update($update_pait_data);
            } else {
                $update_pait = true;
            }
            if ($find_order) {
                if ($find_order->ord_service_type == 4) {//会员卡订单添加会员会员卡
                    $find_orgo_data = DB::table('order_goods')
                        ->where('orgo_ord_id', $find_order->ord_id)
                        ->first();
                    if ($find_orgo_data) {
                        $find_vica_data = DB::table('vip_card')
                            ->where('vica_name', $find_orgo_data->orgo_goo_name)
                            ->first();
                        if ($find_vica_data) {
                            $myvi_num = 'HYK'.time().rand(10000,99999);
                            $data['myvi_num'] = $myvi_num;//会员卡编号
                            $data['myvi_create_adm_id'] = $find_order->ord_create_id;
                            $data['myvi_shop_id'] = $find_order->ord_shop_id;
                            $data['myvi_adm_id'] = $find_order->orcl_client_id;
                            $data['myvi_surplus_times'] = $find_vica_data->vica_num; //剩余次数
                            $data['myvi_all_times'] = $find_vica_data->vica_num; //总次数
                            $data['myvi_long_term'] = $find_vica_data->vica_long_term; //是否长期1是2否
                            $data['myvi_carnum'] = $find_order->orcl_car_num; //绑定车牌
                            $data['myvi_sepr_id'] = $find_vica_data->vica_sepr_id; //服务项目id
                            $data['myvi_sepr_name'] = $find_vica_data->vica_sepr_name; //服务项目名称
                            $data['myvi_name'] = $find_vica_data->vica_name; //会员卡名称
                            $data['myvi_explain'] = $find_vica_data->vica_instructions; //会员卡使用说明
                            $data['myvi_vica_id'] = $find_vica_data->vica_id; //会员卡id
                            $data['myvi_create_time'] = date('Y-m-d H:i:s');
                            if ($find_vica_data->vica_long_term == 2) {//是否长期1是2否
                                $data['myvi_effective_date'] = $find_vica_data->vica_effective_date; //有效天数
                                $data['myvi_end_time'] = date('Y-m-d', strtotime(date('Y-m-d').'23:59:59') + ($find_vica_data->vica_effective_date)*60*60*24).' 23:59:59';
                            } else {
                                $data['myvi_end_time'] = date('Y-m-d', time() + 99999*60*60*24);
                            }
                            $myvi_id = DB::table('myvip')->insertGetId($data);
                        } else {
                            $myvi_id = true;
                        }
                    } else {
                        $myvi_id = true;
                    }
                } else {
                    $myvi_id = true;
                }
            } else {
                $myvi_id = true;
            }
            if ($update_res && $update_pait && $myvi_id) {
                DB::commit();
                return response()->json(['code'=>200, 'message'=>'成功']);
            } else {
                DB::rollBack();
                return response()->json(['code'=>200, 'message'=>'成功']);
            }
        }
        if ($find_data->ord_shop_id) {
            $shop_data = DB::table('shop')
                ->where('shop_id', $find_data->ord_shop_id)
                ->select('shop_id', 'shop_machid')
                ->first();
            if ($shop_data->shop_machid) {
                $XCX_MCHID = strval($shop_data->shop_machid);
            } else {
                $XCX_MCHID = config('apisystem.XCX_MCHID');
            }
        } else {
            return ['code'=>500, 'msg'=>'请选择门店', 'data'=>''];
        }
        $url = 'https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi';
        $settle_info = 
            [
                    'profit_sharing' => false, //是否分账，与外层profit_sharing同时存在时，以本字段为准。 示例值：true
                ];
        $description = '半径服务-'.$find_data->orgo_goo_name;
        $out_trade_no = $find_data->ord_order_number;
        $amount = [
                    'total' => $find_data->ord_order_price, //订单总金额，单位为分。 
                    'currency' => 'CNY' //CNY：人民币，境内商户号仅支持人民币。
                ];
        $payer = [
                    'sub_openid' => $request['weus_openid'] //用户在子商户appid下的唯一标识。若传sub_openid，那sub_appid必填 
                ];
        // $notify_url = 'https://xm.bluearp.com/index.php/api/u1/Pay/notifyPay';
        $notify_url = config('apisystem.server_name_https').'/index.php/api/u1/Pay/notifyPay';
        $paramData = [
            //服务商应用ID
            'sp_appid' => config('apisystem.COMBINE_APPID'),  //合单发起方的appid  示例值：wxd678efh567hg6787
            //服务商户号
            'sp_mchid' => config('apisystem.MCHID'), //合单发起方商户号。示例值：1900000109
            'sub_appid' => config('apisystem.XCX_APPID'), //二级商户应用ID
            // 'sub_mchid' => config('apisystem.XCX_MCHID'), //二级商户号
            'sub_mchid' => $XCX_MCHID, //二级商户号
            'description' => $description,
            'out_trade_no' => $out_trade_no,//商户订单号
            'settle_info' => $settle_info,
            'amount' => $amount,
            'payer' => $payer,
            //交易结束时间
            // 'time_expire'    => $time_expire,//订单失效时间，遵循rfc3339标准格式，格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE，YYYY-MM-DD表示年月日，T出现在字符串中，表示time元素的开头，HH:mm:ss表示时分秒，TIMEZONE表示时区（+08:00表示东八区时间，领先UTC 8小时，即北京时间）。例如：2015-05-20T13:29:35+08:00表示，北京时间2015年5月20日 13点29分35秒。示例值：2019-12-31T15:59:60+08:00
            //通知地址
            'notify_url'    => $notify_url, //接收微信支付异步通知回调地址，通知url必须为直接可访问的URL，不能携带参数。格式: URL 示例值：https://yourapp.com/notify
        ];
        $parameters = json_encode($paramData);
        return self::_Postresponse($url, $parameters);
    }

    // 其他小程序统一下单
    public static function otherTransactionsJsapi ($request)
    {
        if (!isset($request['weus_openid'])) {
            return ['code'=>500, 'msg'=>'请选择小程序', 'data'=>''];
        }

        
        if ($request['XCX_MCHID']) {
            $XCX_MCHID = $request['XCX_MCHID'];
        } else {
            $XCX_MCHID = config('apisystem.XCX_MCHID');
        }
        $url = 'https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi';
        if ($request['profit_sharing'] == 1) {
            $settle_info = 
                [
                        'profit_sharing' => true, //是否分账，与外层profit_sharing同时存在时，以本字段为准。 示例值：true
                    ];
        } elseif ($request['profit_sharing'] == 2) {
            $settle_info = 
                [
                        'profit_sharing' => false, //是否分账，与外层profit_sharing同时存在时，以本字段为准。 示例值：true
                    ];
        }
        $description = $request['XCX_Name'].'-'.$request['orgo_goo_name'];
        $out_trade_no = $request['ord_order_number'];
        $amount = [
                    'total' => (int)$request['ord_order_price'], //订单总金额，单位为分。 
                    'currency' => 'CNY' //CNY：人民币，境内商户号仅支持人民币。
                ];
        $payer = [
                    'sub_openid' => $request['weus_openid'] //用户在子商户appid下的唯一标识。若传sub_openid，那sub_appid必填 
                ];
        // $notify_url = 'https://xm.bluearp.com/index.php/api/u1/Pay/notifyPay';
        $notify_url = config('apisystem.server_name_https').'/index.php/api/u1/Pay/otherNotifyPay';
        $paramData = [
            //服务商应用ID
            'sp_appid' => config('apisystem.COMBINE_APPID'),  //合单发起方的appid  示例值：wxd678efh567hg6787
            //服务商户号
            'sp_mchid' => config('apisystem.MCHID'), //合单发起方商户号。示例值：1900000109
            // 'sub_appid' => config('apisystem.XCX_APPID'), //二级商户应用ID
            'sub_appid' => $request['XCX_APPID'], //二级商户应用ID
            // 'sub_mchid' => config('apisystem.XCX_MCHID'), //二级商户号
            'sub_mchid' => $XCX_MCHID, //二级商户号
            'description' => $description,
            'out_trade_no' => $out_trade_no,//商户订单号
            'settle_info' => $settle_info,
            'amount' => $amount,
            'payer' => $payer,
            //交易结束时间
            // 'time_expire'    => $time_expire,//订单失效时间，遵循rfc3339标准格式，格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE，YYYY-MM-DD表示年月日，T出现在字符串中，表示time元素的开头，HH:mm:ss表示时分秒，TIMEZONE表示时区（+08:00表示东八区时间，领先UTC 8小时，即北京时间）。例如：2015-05-20T13:29:35+08:00表示，北京时间2015年5月20日 13点29分35秒。示例值：2019-12-31T15:59:60+08:00
            //通知地址
            'notify_url'    => $notify_url, //接收微信支付异步通知回调地址，通知url必须为直接可访问的URL，不能携带参数。格式: URL 示例值：https://yourapp.com/notify
        ];
        $parameters = json_encode($paramData);
        return self::_Postresponse($url, $parameters);
    }

    // 请求分账
    public static function reqAccount($out_order_no, $transaction_id, $sub_mchid, $receivers,$finish)
    {
        $url = 'https://api.mch.weixin.qq.com/v3/ecommerce/profitsharing/orders';
        $post = [
            // 'appid' => Config::COMBINE_APPID,
            'appid' => config('apisystem.COMBINE_APPID'),
            'out_order_no' => $out_order_no,
            'transaction_id' => $transaction_id,
            'sub_mchid' => $sub_mchid,
            'receivers' => $receivers,
            'finish' => $finish
        ];
        $post = json_encode($post);
        $ret = self::_Postresponse($url, $post);
        return $ret;
    }

    /**
     * 完结分账 post  收付通
     * 不需要分账的账单直接把二级商户里面的金额直接解冻给二级商户
     * @param string out_order_no  商户分帐单号 唯一 自己生成
     * @param string transaction_id 微信支付子单号的流水号
     * @param string sub_mchid 电商平台二级商户
     * @param string description 描述
     */
    public static function finishAccount($out_order_no, $transaction_id, $sub_mchid, $description)
    {
        $url = 'https://api.mch.weixin.qq.com/v3/ecommerce/profitsharing/finish-order';
        $post = [
            'sub_mchid' => $sub_mchid,
            'transaction_id' => $transaction_id,
            'out_order_no' => $out_order_no,
            'description' => $description ?: '分账完结'
        ];
        $ret = self::_Postresponse($url, $post);
        return $ret;
    }

    /**
     * 退款申请API 收付通 
     * 备注：交易时间超过一年的订单无法提交退款；每个支付订单的部分退款次数不能超过50次
     * @param string sub_mchid 微信支付分配二级商户的商户号
     * @param string out_refund_no 商户系统内部的退款单号
     * @param string transaction_id 原支付交易对应的微信订单号  子流水号
     * @param string out_trade_no 原支付交易对应的商户订单
     * @param string refund_fee 子单的申请的退款金额 分
     * @param string total_fee 子单的全部金额  分
     * @param string notify_url 退款回调地址
     * @param string reason 退款原因
     * @param string sign 退款单来源 weixin xcx h5 等，后期会涉及到使用
     */
    public static function applyRefund($sub_mchid, $out_refund_no, $transaction_id, $out_trade_no, $refund_fee, $total_fee, $notify_url = '', $reason = '', $sign = '')
    {
        $post = [
            'sub_mchid' => $sub_mchid, //二级商户号
            // 'sub_appid' => $sub_appid, //二级商户APPID 可空
            'sp_appid' => config('apisystem.COMBINE_APPID'),// Config::COMBINE_APPID, //电商平台APPID 可空
            'transaction_id' => $transaction_id,
            'out_trade_no' => $out_trade_no,
            'out_refund_no' => $out_refund_no,
            'reason' => $reason,
            'amount' => [
                'refund' => (int)$refund_fee,
                'total' => (int)$total_fee,
                'currency' => 'CNY'
            ],
            'notify_url' => $notify_url
        ];
        $url = 'https://api.mch.weixin.qq.com/v3/ecommerce/refunds/apply';
        $ret = self::_Postresponse($url, json_encode($post));
        return $ret;
    }
 
    /**
    * 发起订单
    * @param float $totalFee 收款总费用 单位元
    * @param string $outTradeNo 唯一的订单号
    * @param string $orderName 订单名称
    * @param string $notifyUrl 支付结果通知url 不要有问号
    * @param string $timestamp 订单发起时间
    * @return array
    */
    public static function createJsBizPackage($totalFee, $outTradeNo, $orderName, $notifyUrl, $timestamp, $config)
    {
        // $orderName = iconv('GBK','UTF-8',$orderName);
        $unified = array(
          'appid' => $config['appid'],
          'attach' => 'pay',       //商家数据包，原样返回，如果填写中文，请注意转换为utf-8
          'body' => $orderName,
          'mch_id' => $config['mch_id'],
          'nonce_str' => self::createNonceStr(),
          'notify_url' => $notifyUrl,
          'out_trade_no' => $outTradeNo,
          'spbill_create_ip' => '127.0.0.1',
          'total_fee' => $totalFee,    //单位 转为分
          'trade_type' => 'NATIVE',
        );
        $unified['sign'] = self::getSign($unified, $config['key']);
        $responseXml = self::curlPost('https://api.mch.weixin.qq.com/pay/unifiedorder', self::arrayToXml($unified));
        $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);
        if ($unifiedOrder === false) {
            return ['code' => 500, 'msg' => '付款失败', 'data' => ''];
            die('parse xml error');
        }
        if ($unifiedOrder->return_code != 'SUCCESS') {
            return ['code' => 500, 'msg' => '付款失败'.$unifiedOrder->return_msg, 'data' => ''];
        }
        if ($unifiedOrder->result_code != 'SUCCESS') {
            return ['code' => 500, 'msg' => '付款失败'.$unifiedOrder->result_code, 'data' => ''];
        }
        $codeUrl = (array)($unifiedOrder->code_url);
        if(!$codeUrl[0]) {
            return ['code' => 500, 'msg' => '获取付款码失败', 'data' => ''];
        }
        $arr = array(
          "appId" => $config['appid'],
          "timeStamp" => $timestamp,
          "nonceStr" => self::createNonceStr(),
          "package" => "prepay_id=" . $unifiedOrder->prepay_id,
          "signType" => 'MD5',
          "code_url" => $codeUrl[0],
        );
        $arr['paySign'] = self::getSign($arr, $config['key']);
        $return_data = $codeUrl[0];
        return ['code' => 200, 'msg' => 'success', 'data' => $return_data];
    }

    public static function notify($request, $config)
    {
        $postObj = simplexml_load_string($request, 'SimpleXMLElement', LIBXML_NOCDATA);
        if ($postObj === false) {
            die('parse xml error');
        }
        if ($postObj->return_code != 'SUCCESS') {
           die($postObj->return_msg);
        }
        if ($postObj->result_code != 'SUCCESS') {
           die($postObj->err_code);
        }
        $arr = (array)$postObj;
        unset($arr['sign']);
        if (self::getSign($arr, $config['key']) == $postObj->sign) {
            $find_order = DB::table('recharge_order')
                ->where('reo_order_number', $postObj->out_trade_no)
                ->first();
            if ($find_order->reo_pay_state == 1) {
                return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
            }
            $update_were = DB::table('weixin_record')
                ->where('out_trade_no', $postObj->out_trade_no)
                ->update(['were_pay_state'=>2,'were_transaction_id'=>$postObj->transaction_id]);
            $update_order = DB::table('recharge_order')
                ->where('reo_order_number', $postObj->out_trade_no)
                ->update(['reo_pay_state'=>2,'reo_paid_money'=>$find_order['reo_price'],'reo_pay_way'=>1,'reo_pay_check'=>1, 'reo_pay_time'=>date('Y-m-d H:i:s')]);
            $find_data = DB::table('admin_recharge')
                ->where('adre_company_id', $find_order['reo_company_id'])
                ->first();
            if ($find_data) {
                $adre_banlance = $find_data['adre_banlance'] + $find_order['reo_price'];
                $update_res = DB::table('admin_recharge')
                    ->where('adre_id', $find_data['adre_id'])
                    ->update(['adre_banlance'=>$adre_banlance]);
            }
            return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
        }
    }

    /**
    * curl get
    *
    * @param string $url
    * @param array $options
    * @return mixed
    */
    public static function curlGet($url = '', $options = array())
    {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        if (!empty($options)) {
          curl_setopt_array($ch, $options);
        }
        //https请求 不验证证书和host
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $data = curl_exec($ch);
        curl_close($ch);
        return $data;
    }

    public static function curlPost($url = '', $postData = '', $options = array())
    {
        if (is_array($postData)) {
          $postData = http_build_query($postData);
        }
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数
        if (!empty($options)) {
          curl_setopt_array($ch, $options);
        }
        //https请求 不验证证书和host
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $data = curl_exec($ch);
        curl_close($ch);
        return $data;
    }

    public static function arrayToXml($arr)
    {
        $xml = "<xml>";
        foreach ($arr as $key => $val) {
          if (is_numeric($val)) {
            $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
          } else
            $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
    * 获取签名
    */
    public static function getSign($params, $key)
    {
        ksort($params, SORT_STRING);
        $unSignParaString = self::formatQueryParaMap($params, false);
        $signStr = strtoupper(md5($unSignParaString . "&key=" . $key));
        return $signStr;
    }

    protected static function formatQueryParaMap($paraMap, $urlEncode = false)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
          if (null != $v && "null" != $v) {
            if ($urlEncode) {
              $v = urlencode($v);
            }
            $buff .= $k . "=" . $v . "&";
          }
        }
        $reqPar = '';
        if (strlen($buff) > 0) {
          $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }
}
