<?php
namespace wechatpay;
/**
* 
*/
class WechatPay{
    
        /**
     * 配置信息
     * @var object
     */
    public $config;
    
    public $values = [];
    public $store_id;
    /**
     * 支付订单.
     *
     * @var string
     */
    protected $unifiedorder = 'https://api.mch.weixin.qq.com/pay/unifiedorder';

    /**
     * 查询订单.
     * 
     * @var string
     */
    protected $orderquery = 'https://api.mch.weixin.qq.com/pay/orderquery';

    /**
     * 关闭订单.
     * 
     * @var string
     */
    protected $closeorder = 'https://api.mch.weixin.qq.com/pay/closeorder';

    /**
     * 退款.
     * 
     * @var string
     */
    protected $refund = 'https://api.mch.weixin.qq.com/secapi/pay/refund';

    /**
     * 单次分账.
     * 
     * @var string
     */
    protected $profitsharing="https://api.mch.weixin.qq.com/secapi/pay/profitsharing";

    /**
     * 添加分账账号.
     * 
     * @var string
     */
    protected $profitsharingaddreceiver="https://api.mch.weixin.qq.com/pay/profitsharingaddreceiver";

    /*
    完成分賬
     */
    protected $profitsharingfinish="https://api.mch.weixin.qq.com/secapi/pay/profitsharingfinish";
    /*
    查询分账结果

     */
    protected $profitsharingquery="https://api.mch.weixin.qq.com/pay/profitsharingquery";

    /*
    *直连分账
     */
    protected $wxapp_profitsharing="https://api.mch.weixin.qq.com/v3/profitsharing/orders";
    /*
    //转账金额
    $amount 发送的金额（分）目前发送金额不能少于1元
    $re_openid, 发送人的 openid
    $desc  //  企业付款描述信息 (必填)
    $check_name    收款用户姓名 (选填)
    */
    public function transfersMoney($amount=1,$re_openid,$desc='测试',$check_name=''){
        if($amount<1){
           die();
        }
        $total_amount = (100) * $amount;
        $data=array(
            'mch_appid'=>$this->config->appid,//商户账号appid
            'mchid'=> $this->config->mchid,//商户号
            'nonce_str'=>self::createNoncestr(),//随机字符串
            'partner_trade_no'=> date('YmdHis').rand(1000, 9999),//商户订单号
            'openid'=> $re_openid,//用户openid
            'check_name'=>'NO_CHECK',//校验用户姓名选项,
            're_user_name'=> $check_name,//收款用户姓名
            'amount'=>$total_amount,//金额
            'desc'=> $desc,//企业付款描述信息
            'spbill_create_ip'=> get_client_ip(),//Ip地址
        );
        $secrect_key=$this->config->key;///这个就是个API密码。MD5 32位。
        $data=array_filter($data);
        ksort($data);
        $str='';
        foreach($data as $k=>$v) {
           $str.=$k.'='.$v.'&';
        }

        $str.='key='.$secrect_key;
        $data['sign']=md5($str);
        $xml=self::arraytoxml($data);
      
        $url='https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'; //调用接口
        $res=$this->postXmlCurl($xml,$url,true);
        $return=self::xmltoarray($res);
        if(isset($return['result_code'])){
            if($return['result_code']=='SUCCESS'){
                return ['code'=>0,'msg'=>'提现成功','data'=>['payment_no'=>$return['payment_no'],'partner_trade_no'=>$return['partner_trade_no']]];
            }
            return ['code'=>1,'msg'=>$return['return_msg'].$return['err_code_des']];
        }
        return ['code'=>1,'msg'=>'系统错误'];
        // //返回来的结果
        // // [return_code] => SUCCESS [return_msg] => Array ( ) [mch_appid] => wxd44b890e61f72c63 [mchid] => 1493475512 [nonce_str] => 616615516 [result_code] => SUCCESS [partner_trade_no] => 20186505080216815 
        // // [payment_no] => 1000018361251805057502564679 [payment_time] => 2018-05-15 15:29:50
        
        
        // $responseObj = simplexml_load_string($res, 'SimpleXMLElement', LIBXML_NOCDATA);
        // echo $res= $responseObj->return_code;  //SUCCESS  如果返回来SUCCESS,则发生成功，处理自己的逻辑
        
        // // return $res;
    }
    function curl_post_ssl($url, $vars, $second = 30, $aHeader = array()){
        $isdir = $_SERVER['DOCUMENT_ROOT']."/cert/";//证书位置

        $ch = curl_init();//初始化curl

        curl_setopt($ch, CURLOPT_TIMEOUT, $second);//设置执行最长秒数
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);//要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_URL, $url);//抓取指定网页
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);// 终止从服务端进行验证
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);//
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');//证书类型
        curl_setopt($ch,CURLOPT_SSLCERT,Env::get('root_path').'public/wxpay/cacert/apiclient_cert.pem'); //这个是证书的位置绝对路径
        curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');//CURLOPT_SSLKEY中规定的私钥的加密类型
        curl_setopt($ch,CURLOPT_SSLKEY,Env::get('root_path').'public/wxpay/cacert/apiclient_key.pem'); //这个也是证书的位置绝对路径
        if (count($aHeader) >= 1) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);//设置头部
        }
        curl_setopt($ch, CURLOPT_POST, 1);//post提交方式
        curl_setopt($ch, CURLOPT_POSTFIELDS, $vars);//全部数据使用HTTP协议中的"POST"操作来发送

        $data = curl_exec($ch);//执行回话
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            echo "call faild, errorCode:$error\n";
            curl_close($ch);
            return false;
        }
    }

    function arraytoxml($data){
        $str='<xml>';
        foreach($data as $k=>$v) {
            $str.='<'.$k.'>'.$v.'</'.$k.'>';
        }
        $str.='</xml>';
        return $str;
    }
    function xmltoarray($xml) { 
        //禁止引用外部xml实体 
        libxml_disable_entity_loader(true); 
        $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA); 
        $val = json_decode(json_encode($xmlstring),true); 
        return $val;
    } 
    public function __construct($config) {   
        $this->config = (object)$config;
    }
    public function pay($paydata=[]){
        $payResult = $this->unifiedorder($paydata);
        if (!isset($payResult['return_code']) || $payResult['return_code'] != 'SUCCESS') {
            $info='支付失败，' . (isset($payResult['return_msg']) ? $payResult['return_msg'] : '');
            die(json_encode([
                'code'=>1,
                'msg'=>$info
            ]));
        }
        if (!isset($payResult['result_code']) || $payResult['result_code'] != 'SUCCESS') {
             $info='交易失败，'. (isset($payResult['err_code_des']) ? isset($payResult['err_code_des']) : '');
            die(json_encode([
                'code'=>1,
                'msg'=>$info
            ]));
        }
        $res = [
            'appId' => $this->config->appid,
            'timeStamp' => ''.time(),
            'nonceStr' => $this->createNonceStr(),
            'package' => 'prepay_id='.$payResult['prepay_id'],
            'signType' => 'MD5',
        ];
        $res['paySign'] = $this->createSign($res);
        $res['prepay_id']=$payResult['prepay_id'];
        return $res;
    }
    public function appPay($paydata=[]){
       $request_data = array(
             'appid' => $this->config->appid,  //应用APPID
             'mch_id' => $this->config->mchid, //              #商户号
             'trade_type' => 'APP',            //        #支付类型
             'nonce_str' => $this->createNonceStr(), // #随机字符串 不长于32位
             'body' =>mb_substr($paydata['body'],1,10,"UTF-8"),       //        #商品名称
             'out_trade_no' => $paydata['out_trade_no'],//#商户后台订单号
             'total_fee' => $paydata['total_fee'], //#商品价格
             'spbill_create_ip' => get_client_ip(),            #用户端实际ip
             'notify_url' => $paydata['notify_url'], #异步通知回调地址
        );        
        // 获取签名
        $request_data['sign']=$this->createSign($request_data);
        // 拼装数据
        $xml = $this->ToXml($request_data);
        // 发送请求
        $res = $this->postXmlCurl($xml, $this->unifiedorder);
        $res=$this->FromXml($res);
        if($res['return_code'] == 'SUCCESS' && $res['result_code'] == 'SUCCESS'){
            $two_data['appid'] =$this->config->appid;  #APPID
            $two_data['partnerid'] = $this->config->mchid;  #商户号
            $two_data['prepayid'] = $res['prepay_id'];  //预支付交易会话标识
            $two_data['noncestr'] = $this->createNonceStr();  
            $two_data['timestamp'] = time();
            $two_data['package'] = "Sign=WXPay";
            $two_data['sign'] = $this->createSign($two_data);
            return $two_data;
        }else{
            die(json_encode([
                'code'=>1,
                'msg'=>$res['return_msg']
            ]));
        }
    }
    
    //外部浏览器支付
    public function h5pay($paydata=[]){
        $appid=$paydata['appid'];
        $subject = mb_substr($paydata['body'],1,10,"UTF-8");//商品描述
        $total_amount=$paydata['total_fee'];
        $additional = $paydata['attach']; ////附加数据
        $order_id = $paydata['out_trade_no']; ////订单号
        $nonce_str=MD5($order_id);  //随机字符串
        $spbill_create_ip = get_client_ip(); //终端ip
        $trade_type = 'MWEB';//交易类型 具体看API 里面有详细介绍
        $notify_url = $paydata['notify_url'];//回调地址
        $mch_id=$this->config->mchid;
        $scene_info =$paydata['scene_info']; 
        $signA = "appid=$appid&body=$subject&mch_id=$mch_id&nonce_str=$nonce_str&notify_url=$notify_url&out_trade_no=$order_id&scene_info=$scene_info&spbill_create_ip=$spbill_create_ip&total_fee=$total_amount&trade_type=$trade_type";
        $strSignTmp = $signA."&key={$this->config->key}";
        $sign = strtoupper(MD5($strSignTmp));
        $xml = "<xml>
               <appid>$appid</appid>
               <body>$subject</body>
               <mch_id>$mch_id</mch_id>
               <nonce_str>$nonce_str</nonce_str>
               <notify_url>$notify_url</notify_url>
               <out_trade_no>$order_id</out_trade_no>
               <scene_info>$scene_info</scene_info>
               <spbill_create_ip>$spbill_create_ip</spbill_create_ip>
               <total_fee>$total_amount</total_fee>
               <trade_type>$trade_type</trade_type>
               <sign>$sign</sign>
           </xml>";//拼接成XML 格式
      
        $response = $this->postXmlCurl($xml, $this->unifiedorder);
        $objectxml = $this->FromXml($response);
        if($objectxml['return_code'] == 'SUCCESS'){
            return [
                'code'=>0,
                'data'=>$objectxml['mweb_url']
            ];
        }
        if($objectxml['result_code'] == 'FAIL'){
            return [
                'code'=>1,
                'data'=>'',
                'msg'=>$objectxml['err_code_des']
            ];
        } 
    }
    public function toutiaoh5pay($paydata=[],$ttapp=null){
        $appid=$paydata['appid'];
        $subject = mb_substr($paydata['body'],1,10,"UTF-8");//商品描述
        $total_amount=$paydata['total_fee'];
        $additional = $paydata['attach']; ////附加数据
        $order_id = $paydata['out_trade_no']; ////订单号
        $nonce_str=MD5($order_id);  //随机字符串
        $spbill_create_ip = get_client_ip(); //终端ip
        $trade_type = 'MWEB';//交易类型 具体看API 里面有详细介绍
        $notify_url = $paydata['notify_url'];//回调地址
        $mch_id=$this->config->mchid;
        $scene_info =$paydata['scene_info']; 
        $signA = "appid=$appid&body=$subject&mch_id=$mch_id&nonce_str=$nonce_str&notify_url=$notify_url&out_trade_no=$order_id&scene_info=$scene_info&spbill_create_ip=$spbill_create_ip&total_fee=$total_amount&trade_type=$trade_type";
        $strSignTmp = $signA."&key={$this->config->key}";
        $sign = strtoupper(MD5($strSignTmp));
        $xml = "<xml>
               <appid>$appid</appid>
               <body>$subject</body>
               <mch_id>$mch_id</mch_id>
               <nonce_str>$nonce_str</nonce_str>
               <notify_url>$notify_url</notify_url>
               <out_trade_no>$order_id</out_trade_no>
               <scene_info>$scene_info</scene_info>
               <spbill_create_ip>$spbill_create_ip</spbill_create_ip>
               <total_fee>$total_amount</total_fee>
               <trade_type>$trade_type</trade_type>
               <sign>$sign</sign>
           </xml>";//拼接成XML 格式
      
        $response = $this->postXmlCurl($xml, $this->unifiedorder);
        $objectxml = $this->FromXml($response);
        if($objectxml['return_code'] == 'SUCCESS'){
              $now=time();
              $orderInfo=[
                    'app_id'=>isset($ttapp->pay_appid)?$ttapp->pay_appid:'',
                    'merchant_id'=>isset($ttapp->pay_mch_id)?$ttapp->pay_mch_id:'',
                    'out_order_no'=>$order_id,
                    'timestamp'=> $now,
                    'trade_time'=> $now,
                    'notify_url'=>$notify_url,
                    'wx_url'=>$objectxml['mweb_url'],
                    'wx_type'=>'MWEB',
                    'sign_type'=>'MD5',
                    'product_code'=>"pay",
                    "payment_type"=>"direct",
                    'total_amount'=>$total_amount,
                    'trade_type'=>'H5',
                    'uid'=>isset($ttapp->pay_appid)?$ttapp->pay_appid:'',//$paydata['openid'],//待检测
                    'version'=>"2.0",
                    'currency'=>"CNY",
                    'subject'=>$subject,
                    "body"=>$subject,
                    'valid_time'=>300,
            ];
            $pay_app_secret=isset($ttapp->pay_app_secret)?$ttapp->pay_app_secret:'';
            $orderInfo['sign']=self::getSignContent($orderInfo,$pay_app_secret);
            $orderInfo["risk_info"]  = json_encode(['ip' => get_client_ip()]);
            return [
                'code'=>0,
                'data'=>[
                    'orderinfo'=>$orderInfo,
                    'mweb_url'=>$objectxml['mweb_url'],
                    'service'=>3
                ]
           ];
        }
        if($objectxml['result_code'] == 'FAIL'){
            return [
                'code'=>1,
                'data'=>'',
                'msg'=>$objectxml['err_code_des']
            ];
        } 
        
    }
    /**
     * 字节跳动签名处理
     * @param $params
     * @param $charset
     * @return string
     */
    public function getSignContent($params , $app_secret='', $charset='UTF-8') {
        ksort($params);
        $stringToBeSigned = "";
        $i = 0;
        foreach ($params as $k => $v) {
            if(!isset($v) || $v === null || trim($v) === "" || "@" == substr($v, 0, 1)){
                continue;
            }
            // 转换成目标字符集
            if (!empty($v)) {
                $fileType = "UTF-8";
                if (strcasecmp($fileType, $charset) != 0) {
                    $v = mb_convert_encoding($v, $charset, $fileType);
                }
            }
            if ($i == 0) {
                $stringToBeSigned .= "$k" . "=" . "$v";
            } else {
                $stringToBeSigned .= "&" . "$k" . "=" . "$v";
            }
            $i++;
        }
        unset ($k, $v);
        return md5($stringToBeSigned.$app_secret);
    }
    /**
     * 获取微信支付中间页deepLink参数
     * 获取deepLink客户端通过次链接可直接调起支付
     * @param string $url 微信返回的mweb_url
     * @param string $ip 用户端IP
     */
    public function getDeepLink($url='', $ip='',$auth_url="qianya.whfjkj.com")
    {
        $headers = ["X-FORWARDED-FOR:$ip", "CLIENT-IP:$ip"];

        ob_start();
        $ch = curl_init();
        curl_setopt ($ch, CURLOPT_URL, $url);
        curl_setopt ($ch, CURLOPT_HTTPHEADER , $headers );
        curl_setopt ($ch, CURLOPT_REFERER, $auth_url);
        curl_setopt( $ch, CURLOPT_HEADER, 1);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Linux; Android 6.0.1; OPPO R11s Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36');
        curl_exec($ch);
        curl_close ($ch);
        $out = ob_get_contents();
        ob_clean();

        $a = preg_match('/weixin:\/\/wap.*/',$out, $str);
        if ($a) {
            return substr($str[0], 0, strlen($str[0])-1);
        } else {
            return '';
        }
    }
    /**
     * 统一下单
     * @param  array $params 请求参数
     * @return mixed         结果
     */
    public function unifiedorder($params) {
        
        if (is_null($this->config->appid)) {
             die('缺少参数appid');
        }

        if (is_null($this->config->mchid)) {
            die('缺少参数mchid');
        }
        $params['body']=mb_substr($params['body'],1,15,"UTF-8");
        $params['appid'] = $this->config->appid;
        $params['mch_id'] = $this->config->mchid;
        $params['nonce_str'] = $this->createNonceStr();
        $params['sign_type'] = 'MD5';
        $params['spbill_create_ip'] = get_client_ip();
        if(isset($this->config->wx_pay_type)){
            //服务商模式
            if($this->config->wx_pay_type==2){
                $params['appid']=$this->config->wx_server_appid;//服务商的公众号appid
                $params['mch_id']=$this->config->wx_server_mch_id;//服务商的商户ID
                $params["sub_mch_id"] = $this->config->mchid;//子商户号
                $params["sub_appid"] = $this->config->appid;
                $params["sub_openid"] = $params["openid"];
                unset($params["openid"]);//去掉原来的openid
            }

        }
        //是否需要分账
        if(isset($this->config->wx_profit_sharing)){
            $params["profit_sharing"] =$this->config->wx_profit_sharing==1?"Y":"N";
        }
        
        $params['sign'] = $this->createSign($params);
        $xml = $this->ToXml($params);
        $response = $this->postXmlCurl($xml, $this->unifiedorder);
        $result = $this->FromXml($response);
        return $result;
    }
    //添加分账账号
    public function profitsharingaddreceiver($account="1615272225",$companyName="厦门创神奇网络科技有限公司",$is_server=0){
        if($is_server){
            $params['appid']=$this->config->wx_server_appid;
            $params['mch_id']=$this->config->wx_server_mch_id;
            $params['sub_appid']=$this->config->sub_appid;
            $params['sub_mch_id']=$this->config->sub_mch_id;
        }else{
            $params['appid'] = $this->config->appid;
            $params['mch_id'] = $this->config->mchid;
        }
        $params['nonce_str']=$this->createNonceStr();
        $params['sign_type']="HMAC-SHA256";
        $params['receiver']=json_encode([
            'type'=>"MERCHANT_ID",
            'account'=>$account,
            'name'=>$companyName,
            'relation_type'=>"STORE_OWNER",
        ],JSON_UNESCAPED_UNICODE);
        $params['sign']=$this->createSign($params,"HMAC-SHA256");
        $xml=$this->ToXml($params);
        $response = $this->postXmlCurl($xml, $this->profitsharingaddreceiver);
        $result = $this->FromXml($response);
        if($result['result_code']=='SUCCESS'){
            return ['code'=>0,'msg'=>'添加成功'];
        }else{
            return ['code'=>1,'msg'=>$result];
        }
    }
    //完成分账
    public function profitsharingfinish($out_trade_no="",$transaction_id=""){
        $params['appid']=$this->config->wx_server_appid;
        $params['mch_id']=$this->config->wx_server_mch_id;
        $params['sub_mch_id']=$this->config->sub_mch_id;
        $params['nonce_str']=$this->createNonceStr();
        $params['out_order_no']=$out_trade_no;
        $params['transaction_id']=$transaction_id;
        $params['sign_type']="HMAC-SHA256";
        $params['description']="分账已完成";
        $params['sign']=$this->createSign($params,"HMAC-SHA256");

        $xml=$this->ToXml($params);
        $response = $this->postXmlCurl($xml, $this->profitsharingfinish, true);
        $result = $this->FromXml($response);
        if($result['return_code']=='SUCCESS'){
            return ['code'=>0,'data'=>$result];
        }else{
            return ['code'=>1,'msg'=>$result['return_msg']];
        }
    }
    //单个分账
    public function  profitsharing($out_trade_no="",$transaction_id="",$account="1603740948",$amount=1,$companyName="福州芊雅软件技术有限公司",$is_server=0){
        if($is_server){
            $params['appid']=$this->config->wx_server_appid;
            $params['mch_id']=$this->config->wx_server_mch_id;
            $params['sub_appid']=$this->config->sub_appid;
            $params['sub_mch_id']=$this->config->sub_mch_id;
        }else{
            $params['appid'] =  $this->config->appid;
            $params['mch_id'] = $this->config->mchid;
        }
        $params['out_order_no']=$out_trade_no;
        $params['transaction_id']=$transaction_id;
        $params['nonce_str']=$this->createNonceStr();
        $params['sign_type']="HMAC-SHA256";
        $receivers[] = [
            'type'=>"MERCHANT_ID",
            'account'=>$account,
            'amount'=>intval($amount),
            'description'=>$companyName,
        ];
        $receivers = json_encode($receivers,JSON_UNESCAPED_UNICODE);
        $params['receivers']=$receivers;
        $params['sign']=$this->createSign($params,"HMAC-SHA256");
       
        $xml = $this->ToXml($params);
        $response = $this->postXmlCurl($xml, $this->profitsharing, true);
        $result = $this->FromXml($response);
        if($result['return_code']=='SUCCESS'){
           return ['code'=>0,'data'=>$result];
        }else{
            return ['code'=>1,'msg'=>$result['return_msg']];
        }
    }
    //查询分账结果
    public function profitsharingquery($order_id="分账后返回的order_id",$transaction_id=""){
        $params['mch_id']=$this->config->wx_server_mch_id;
        $params['sub_mch_id']=$this->config->sub_mch_id;
        $params['nonce_str']=$this->createNonceStr();
        $params['out_order_no']=$order_id;
        $params['transaction_id']=$transaction_id;
        $params['sign_type']="HMAC-SHA256";
        $params['sign']=$this->createSign($params,"HMAC-SHA256");
        $xml=$this->ToXml($params);
        $response = $this->postXmlCurl($xml, $this->profitsharingquery, true);
        $result = $this->FromXml($response);
        if($result['return_code']=='SUCCESS'){
            return $result;
        }else{
            return ['code'=>1,'msg'=>$result['return_msg']];
        }
    }

    /**
     * 生成签名
     * @return 签名，本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
     */
    public function createSign($params,$sign_type="MD5") {

        if (is_null($this->config->key)) {
            die('缺少参数key 41004');
        }
        

        //签名步骤一：按字典序排序参数
        ksort($params);
        $string = $this->ToUrlParams($params);
         $string = $string . "&key=".$this->config->key;
        //签名步骤三：MD5加密
        if($sign_type=="MD5"){
            //签名步骤二：在string后加入KEY
            $string = md5($string);
        }else{
            $string = hash_hmac('sha256', $string,$this->config->key);
        }
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }
    public function notify() {
        $xml = file_get_contents('php://input');
        $rsArr = $this->FromXml($xml);
        if (!isset($rsArr['return_code']) || $rsArr['return_code'] != 'SUCCESS') {
            die('支付失败');
        }

        if (!isset($rsArr['result_code']) || $rsArr['result_code'] != 'SUCCESS') {
            die('交易失败'. (isset($rsArr['err_code_des']) ? isset($rsArr['err_code_des']) : ''));
        }

        // 验证签名
        $sign = $this->createSign($rsArr);
        if (!isset($rsArr['sign']) || $rsArr['sign'] != $sign) {
            die('签名错误');
        }

        if (!isset($rsArr['out_trade_no']) || !isset($rsArr['transaction_id'])) {
            die('参数不完整');
        }
        
        return true;
    }
    
    /**
     * 退款
     * @param  array $params 请求参数
     * @return mixed         结果
     */
    public function refund($params) {

        if (is_null($this->config->appid)) {
            die('缺少参数appid 41004');
        }

        if (is_null($this->config->mchid)) {
            die('缺少参数mchid 41004');
        }
        $params['appid'] = $this->config->appid;
        $params['mch_id'] = $this->config->mchid;

        if(isset($this->config->wx_pay_type)){
            //服务商模式
            if($this->config->wx_pay_type==2){
                $params['appid']=$this->config->appid;//服务商的公众号appid
                $params['mch_id']=$this->config->mchid;//服务商的商户ID
                $params["sub_mch_id"] = $this->config->sub_mch_id;//子商户号
                $params["sub_appid"] = $this->config->sub_appid;
            }
        }
        $params['nonce_str'] = $this->createNonceStr();
        unset($params['transaction_id']);
        $params['sign'] = $this->createSign($params);

        $xml = $this->ToXml($params);
        $response = $this->postXmlCurl($xml, $this->refund, true);
        $result = $this->FromXml($response);
        return $result;
    }

    public function refundNotify() {
        $xml = file_get_contents('php://input');

        $rsArr = $this->FromXml($xml);
        if (!isset($rsArr['return_code']) || $rsArr['return_code'] != 'SUCCESS') {
            die('通信失败');
        }

        // 解密
        // （1）对加密串A做base64解码，得到加密串B
        // （2）对商户key做md5，得到32位小写key* ( key设置路径：微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
        // （3）用key*对加密串B做AES-256-ECB解密（PKCS7Padding）
        $str_a = $rsArr['req_info'];
        $str_b = base64_decode($str_a, true);
        $md5_key = strtolower(md5($this->config->key));
        $req_info = openssl_decrypt($str_b, 'aes-256-ecb', $md5_key, OPENSSL_RAW_DATA);
        $req_info_arr = $this->FromXml($req_info);

        $refund_status = [
            'SUCCESS' => '退款成功',
            'CHANGE' => '退款异常',
            'REFUNDCLOSE' => '退款关闭',
        ];
        if (!isset($req_info_arr['refund_status']) || $req_info_arr['refund_status'] != 'SUCCESS') {
            die($refund_status[$req_info_arr['refund_status']]);
        }
        
        if (!isset($req_info_arr['out_refund_no']) || !isset($req_info_arr['refund_id'])) {
            die('参数不完整');
        }
        
        return true;
    }


    public 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;
    }

    
    /**
     * 格式化参数格式化成url参数
     */
    public function ToUrlParams($params)
    {
        $buff = "";
        foreach ($params as $k => $v)
        {
            if($k != "sign" && $v != "" && !is_array($v)){
                $buff .= $k . "=" . $v . "&";
            }
        }
        
        $buff = trim($buff, "&");
        return $buff;
    }

    /**
     * 输出xml字符
     * @throws WxPayException
    **/
    public function ToXml($params) {
        if(!is_array($params) || count($params) <= 0) {
            die("数组数据异常！");
        }
        
        $xml = "<xml>";
        foreach ($params as $key=>$val) {
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml; 
    }
    
    /**
     * 将xml转为array
     * @param string $xml
     * @throws WxPayException
     */
    public function FromXml($xml) {   
        if(!$xml) {
            die("xml数据异常！");
        }
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $this->values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);        
        return $this->values;
    }

    /**
     * 以post方式提交xml到对应的接口url
     * 
     * @param string $xml  需要post的xml数据
     * @param string $url  url
     * @param bool $useCert 是否需要证书，默认不需要
     * @param int $second   url执行超时时间，默认30s
     * @throws WxPayException
     */
    public function postXmlCurl($xml, $url, $useCert = false, $second = 30) {       
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        
        //如果有配置代理这里就设置代理
        // if(WxPayConfig::CURL_PROXY_HOST != "0.0.0.0" 
        //     && WxPayConfig::CURL_PROXY_PORT != 0){
        //     curl_setopt($ch,CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
        //     curl_setopt($ch,CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
        // }
        curl_setopt($ch,CURLOPT_URL, $url);
        // curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
        // curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验

        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);//严格校验2

        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        if($useCert == true){
            $cert=app()->getRuntimePath().'cert'.'/'.$this->config->appid.'/wx_cert.pem';
            $key=app()->getRuntimePath().'cert'.'/'.$this->config->appid.'/wx_key.pem';
            if(!is_dir(app()->getRuntimePath().'cert/'.$this->config->appid)){
                if(!is_dir(app()->getRuntimePath().'cert'))
                mkdir(app()->getRuntimePath().'cert');
                mkdir(app()->getRuntimePath().'cert/'.$this->config->appid);
            }
            file_put_contents($cert, $this->config->sslcert);
            file_put_contents($key, $this->config->sslkey);
            //默认格式为PEM，可以注释
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT,$cert);
            //默认格式为PEM，可以注释
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY,$key);
        }
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
    
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        } else { 
            $error = curl_errno($ch);
            curl_close($ch);
            $res='curl出错，错误码:'.$error;
            die(json_encode(['code'=>1,'msg'=>$res]));
        }
    }
    
}
function get_client_ip()
{
    $ip='unknown';
    if($_SERVER['REMOTE_ADDR']){
         $ip = $_SERVER['REMOTE_ADDR'];
    }else if(getenv("REMOTE_ADDR")){
        $ip = getenv("REMOTE_ADDR");
    }
    return $ip;
}
