<?php
/**
 * 微信刷卡支付(被扫支付)
*/
namespace Common\Vendor\Pay\Driver;
class Weixinmicropay extends \Common\Vendor\Pay\Pay {
    protected $parameters;
    public $config = array(
        'SSLCERT_PATH' => 'cacert/apiclient_cert.pem',
        'SSLKEY_PATH' => 'cacert/apiclient_key.pem',
        'CURL_TIMEOUT' => '60',
        'CURL_PROXY_HOST' => '0.0.0.0',
        'CURL_PROXY_PORT' => '0',
        'REPORT_LEVENL' => '1',
    );
    public function check() {		
        if (!$this->config['config_account'] || !$this->config['config_partner'] || !$this->config['config_key'] || !$this->config['config_other']) {
            E("参数有误！");
        }
        return true;
    }
	/**
	 * 
	 * 生成扫描支付URL,模式一
	 * @param BizPayUrlInput $bizUrlInfo
	 */
	public function buildRequestForm(\Common\Vendor\Pay\PayVo $vo)
	{
	   $return = array('status'=>0,'info'=>'');
        $body=$vo->getBody();if(empty($body))$body='微信刷卡支付';
        $money=$vo->getFee();
        $this->parameters=array(
            'body'=>$body,
            'out_trade_no'=>$vo->getOrderNo(),
            'total_fee'=>($money*100),
            'spbill_create_ip'=>get_client_ip(),
        	'auth_code'=>$vo->getProduct_id(),
        );
		$result = $this->micropay($this->parameters);
        if(!array_key_exists("return_code", $result) || !array_key_exists("result_code", $result))
		{$return['info']='接口调用失败,请确认是否输入是否有误！';return $return;}        
        if($result['return_code']=='SUCCESS' && $result['result_code']=='SUCCESS'){//支付成功
            $return['status']=1;                
            $return['info']=$this->info;
            return $return;
        }
        $return['info']=$result['err_code_des']?$result['err_code_des']:'请求失败';
        return $return;
	}
    
    /**
	 * 提交被扫支付API
	 * 收银员使用扫码设备读取微信用户刷卡授权码以后，二维码或条码信息传送至商户收银台，
	 * 由商户收银台或者商户后台调用该接口发起支付。
	 * WxPayWxPayMicroPay中body、out_trade_no、total_fee、auth_code参数必填
	 * appid、mchid、spbill_create_ip、nonce_str不需要填入
	 * @param WxPayWxPayMicroPay $inputObj
	 * @param int $timeOut
	 */
	private function micropay($inputObj, $timeOut = 10)
	{
	    $return =array('return_code'=>'SUCCESS','result_code'=>'FAIL','err_code_des'=>'');
		$url = "https://api.mch.weixin.qq.com/pay/micropay";
		//检测必填参数
		if(!$inputObj['body']) {
			$inputObj['body']='刷卡支付';
		} else if(!$inputObj['out_trade_no']) {
			$return['err_code_des']="提交被扫支付API接口中，缺少必填参数out_trade_no！";return $return;
		} else if(!$inputObj['total_fee']) {
			$return['err_code_des']="提交被扫支付API接口中，缺少必填参数total_fee！";return $return;
		} else if(!$inputObj['auth_code']) {
			$return['err_code_des']="提交被扫支付API接口中，缺少必填参数auth_code！";return $return;
		}
		$inputObj["appid"]=$this->config['config_account'];//公众账号ID
		$inputObj["mch_id"] = $this->config['config_partner'];//店铺号
		$inputObj["nonce_str"] = $this->getNonceStr();//随机字符串
		$inputObj["sign"] = $this->getSign($inputObj);//签名
        $xml = $this->arrayToXml($inputObj);
		
		$startTimeStamp = $this->getMillisecond();//请求开始时间
		$response = $this->postXmlCurl($xml, $url, false, $timeOut);
		//$result = WxPayResults::Init($response);
        $result = $this->xmlToArray($response);
        //file_put_contents('applog/'.NOW_TIME.'.txt',var_export($result,1));
        //②、接口调用成功，明确返回调用失败
		if($result["return_code"] == "SUCCESS" && $result["result_code"] == "FAIL" && $result["err_code"] != "USERPAYING" && $result["err_code"] != "SYSTEMERROR")
		{return $result;}
        $out_trade_no=$inputObj['out_trade_no'];
        //③、确认支付是否成功
		$queryTimes = 10;
		while($queryTimes > 0)
		{
            //file_put_contents('applog/query'.NOW_TIME.'.txt',$out_trade_no.', '.$succResult);
			$succResult = 0;
			$queryResult = $this->query($out_trade_no, $succResult);
			//如果需要等待1s后继续
			if($succResult == 2){
				sleep(2);continue;
			} else if($succResult == 1){//查询成功
                //file_put_contents('applog/queryResult'.NOW_TIME.'.txt',var_export($queryResult,1));
                $this->setInfo($queryResult);
				return $queryResult;
			} else {break;}//订单交易失败$queryTimes--;
		}		
		//④、次确认失败，则撤销订单
		if(!$this->cancel($out_trade_no)){$return['err_code_des']="撤销单失败！";return $return;}
	//	$this->reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间
        return $result;
	}
    private function query($out_trade_no, &$succCode,$transaction_id='')
	{
	    $inputObj=array();
        $return =array('return_code'=>'SUCCESS','result_code'=>'FAIL','err_code_des'=>'');
        $url = "https://api.mch.weixin.qq.com/pay/orderquery";
        $inputObj['out_trade_no']=$out_trade_no;
        if($transaction_id)$inputObj['transaction_id']=$transaction_id;//微信订单号
		//检测必填参数
		if(!$inputObj['out_trade_no'] && !$inputObj['transaction_id']) {
            $return['err_code_des']="订单查询接口中，out_trade_no、transaction_id至少填一个！";return $return;
		}
		$inputObj["appid"]=$this->config['config_account'];//公众账号ID
		$inputObj["mch_id"] = $this->config['config_partner'];//店铺号
		$inputObj["nonce_str"] = $this->getNonceStr();//随机字符串
		$inputObj["sign"] = $this->getSign($inputObj);//签名
        $xml = $this->arrayToXml($inputObj);
		$startTimeStamp = $this->getMillisecond();//请求开始时间
		$response = $this->postXmlCurl($xml, $url, false, $timeOut);
		$result = $this->xmlToArray($response);
	//	self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间		
		if($result["return_code"] == "SUCCESS" && $result["result_code"] == "SUCCESS")
		{
			//支付成功
			if($result["trade_state"] == "SUCCESS"){
				$succCode = 1;return $result;
			} else if($result["trade_state"] == "USERPAYING"){//用户支付中
				$succCode = 2;return false;
			}
		}		
		//如果返回错误码为“此交易订单号不存在”则直接认定失败
		if($result["err_code"] == "ORDERNOTEXIST"){$succCode = 0;} else {$succCode = 2;}	//如果是系统错误，则后续继续		
		return false;
	}
    /**
	 * 
	 * 撤销订单，如果失败会重复调用10次
	 * @param string $out_trade_no
	 * @param 调用深度 $depth
	 */
	public function cancel($out_trade_no, $depth = 0)
	{
		if($depth > 10){return false;}
		$result = $this->reverse($out_trade_no);		
		//接口调用失败
		if($result["return_code"] != "SUCCESS"){return false;}		
		//如果结果为success且不需要重新调用撤销，则表示撤销成功
		if($result["result_code"] != "SUCCESS" && $result["recall"] == "N"){
			return true;
		} else if($result["recall"] == "Y") {
			return $this->cancel($out_trade_no, ++$depth);
		}return false;
	}
    /**
	 * 
	 * 撤销订单API接口，WxPayReverse中参数out_trade_no和transaction_id必须填写一个
	 * appid、mchid、spbill_create_ip、nonce_str不需要填入
	 * @param WxPayReverse $inputObj
	 * @param int $timeOut
	 * @throws WxPayException
	 */
	public static function reverse($out_trade_no, $timeOut = 6,$transaction_id='')
	{
	    $inputObj=array();
        $inputObj['out_trade_no']=$out_trade_no;
        if($transaction_id)$inputObj['transaction_id']=$transaction_id;//微信订单号
        $return =array('return_code'=>'SUCCESS','result_code'=>'FAIL','err_code_des'=>'');
		$url = "https://api.mch.weixin.qq.com/secapi/pay/reverse";
		//检测必填参数
        if(!$inputObj['out_trade_no'] && !$inputObj['transaction_id']) {
            $return['err_code_des']="撤销订单API接口中，参数out_trade_no和transaction_id必须填写一个！";return $return;
		}
		$inputObj["appid"]=$this->config['config_account'];//公众账号ID
		$inputObj["mch_id"] = $this->config['config_partner'];//店铺号
		$inputObj["nonce_str"] = $this->getNonceStr();//随机字符串
		$inputObj["sign"] = $this->getSign($inputObj);//签名
        $xml = $this->arrayToXml($inputObj);
		
		$startTimeStamp = $this->getMillisecond();//请求开始时间
		$response = $this->postXmlCurl($xml, $url, true, $timeOut);
		$result = $this->xmlToArray($response);
		//self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间
		
		return $result;
	}
	
    public function get_pay_code($info=''){}
    /**
     * 针对notify_url验证消息是否是发出的合法消息
     * @return 验证结果
     */
    public function verifyNotify($notify) {
        if(empty($notify))return false;
        //生成签名结果
        $notify=$this->xmlToArray($notify);unset($notify['s']);
        $isSign = $this->getSignVeryfy($notify, $notify["sign"]);
        if ($isSign) {$this->setInfo($notify);return true;}else {return false;}
    }
    /**
     * 获取返回时的签名验证结果

     * @param $para_temp 通知返回来的参数数组
     * @param $sign 返回的签名结果
     * @return 签名验证结果
     */    
    protected function getSignVeryfy($param, $sign) {
        unset($param['sign']);
        $mysgin =$this->getSign($param);
        if ($mysgin == $sign){return true;}else{
            $result['return_code']='FAIL';$result['return_msg']='签名失败';
            echo $this->arrayToXml($result);exit;}
    }
    public function notifySuccess(){
        $result['return_code']='SUCCESS';
        $result['return_msg']='OK';
        echo $this->arrayToXml($result);exit;
    }
    protected function setInfo($notify) {
        $info = array();
        //支付状态
        $info['status'] = true;
        $info['money'] = $notify['total_fee']/100;
        $info['out_trade_no'] = $notify['out_trade_no'];
        $info['pay_sn'] = $notify['transaction_id'];
        $this->info = $info;
    }
	/**
	 * 	作用：将xml转为array
	 */
	public function xmlToArray($xml)
	{		
        //将XML转为array        
        $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);		
		return $array_data;
	}
  	/**
	 * 
	 * 产生随机字符串，不长于32位
	 * @param int $length
	 * @return 产生的随机字符串
	 */
	public static function getNonceStr($length = 32) 
	{
		$chars = "abcdefghijklmnopqrstuvwxyz0123456789";$str ="";
		for ( $i = 0; $i < $length; $i++ )  {  
			$str .= substr($chars,mt_rand(0,strlen($chars)-1),1);  
		}return $str;
	}
	/**
	 * 以post方式提交xml到对应的接口url
	 * 
	 * @param string $xml  需要post的xml数据
	 * @param string $url  url
	 * @param bool $useCert 是否需要证书，默认不需要
	 * @param int $second   url执行超时时间，默认30s
	 * @throws WxPayException
	 */
	private function postXmlCurl($xml, $url, $useCert = false, $second = 30)
	{		
		$ch = curl_init();//初始化一个cURL会话
		
		//设置超时
		curl_setopt($ch, CURLOPT_TIMEOUT, $second);//设置一个cURL传输选项   设置cURL允许执行的最长秒数。
		
		//如果有配置代理这里就设置代理
		if($this->config['CURL_PROXY_HOST'] != "0.0.0.0" 
			&& $this->config['CURL_PROXY_HOST'] != 0){
			curl_setopt($ch,CURLOPT_PROXY, $this->config['CURL_PROXY_HOST']);
			curl_setopt($ch,CURLOPT_PROXYPORT, $this->config['CURL_PROXY_HOST']);
		}
		curl_setopt($ch,CURLOPT_URL, $url);//需要获取的URL地址，也可以在curl_init()函数中设置。 
		curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
		curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);//严格校验
		//设置header
		curl_setopt($ch, CURLOPT_HEADER, FALSE);
		//要求结果为字符串且输出到屏幕上
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
	
		if($useCert == true){
			//设置证书
			//使用证书：cert 与 key 分别属于两个.pem文件
			curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLCERT, $this->config['SSLKEY_PATH']);
			curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLKEY, $this->config['SSLKEY_PATH']);
		}
		//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);
			E("curl出错，错误码:$error");
		}
	}
	/**
	 * 	作用：array转xml
	 */
	function arrayToXml($arr){
		
		if(!is_array($arr) || count($arr) <= 0)
		{
    		E("数组数据异常！");
    	}
    	
    	$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; 
    }
	
	private function formatBizQueryParaMap($paraMap, $urlencode)
	{
		$buff = "";
		ksort($paraMap);
		foreach ($paraMap as $k => $v)
		{
		if($urlencode){$v = urlencode($v);}
		$buff .= $k . "=" . $v . "&";
		}
		$reqPar = "";
		if (strlen($buff) > 0){$reqPar = substr($buff, 0, strlen($buff)-1);}
		return $reqPar;
	}
	/**
	* 	作用：生成签名
	*/
	public function getSign($Obj)
	{
		foreach ($Obj as $k => $v)
		{
		  $Parameters[$k] = $v;
		}
		//签名步骤一：按字典序排序参数
		ksort($Parameters);
		$String = $this->formatBizQueryParaMap($Parameters, false);
		//echo '【string1】'.$String.'</br>';
		//签名步骤二：在string后加入KEY
		$String = $String."&key=".$this->config['config_key'];
		//echo "【string2】".$String."</br>";
		//签名步骤三：MD5加密
		$String = md5($String);
		//echo "【string3】 ".$String."</br>";
		//签名步骤四：所有字符转为大写
		$result_ = strtoupper($String);
		//echo "【result】 ".$result_."</br>";
		return $result_;
	}
	/**
	 * 获取毫秒级别的时间戳
	 */
	private static function getMillisecond()
	{
		//获取毫秒的时间戳
		$time = explode ( " ", microtime () );
		$time = $time[1] . ($time[0] * 1000);
		$time2 = explode( ".", $time );
		$time = $time2[0];
		return $time;
	}
}
?>