微信支付分支付免押订单租赁订单thinkphp5

智慧零售和先享后付使用微信支付V3版接口规则参见:https://wechatpay-api.gitbook.io/wechatpay-api-v3/

免押租借和免押速住使用微信支付V2版接口规则参见:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=4_3

https://pay.weixin.qq.com/wiki/doc/apiv3/payscore.php?chapter=18_1&index=2

微信支付提供V2和V3这两种接口方式

微信支付分支付免押订单租赁订单thinkphp5  

本地项目路径:application\api\controller\Orderzfbwx.php

详情见QQ网盘:mg/tianjieb.../

    /**
     * https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter3_1.shtml
     * 微信支付分支付
     * @param string $orderzfb_id
     * @return bool
     */
    public function h5_wx($orderzfb_id='')
    {
        require_once "../extend/wxpay_lib/WxPay.Api.php";
        require_once "../extend/wxpay_lib/WxPay.JsApiPay.php";
        require_once "../extend/wxpay_lib/WxPay.Config.php";

//        $order      = \WxPayApi::getcertificates();
        $input  = [
            'out_order_no' =>'1594286271',//商户订单号
            'service_id'   => '500001',
            'service_introduction' =>'设备租赁',
            'time_range'    =>['start_time'=>date("YmdHis"), 'end_time'=>date("YmdHis",strtotime("+30 day")), ],
            'risk_fund'     =>["name"=> "DEPOSIT", "amount"=> 100,"description"=>"设备租赁的费用"],
            'notify_url'      =>"https://app.tianjiebao.com/api/wx/notify",
            'openid'         => 'oBWgc8x1wEBZPsiYdQXuMJ4',
            'need_user_confirm'=>false,
        ];
        $config     = new \WxPayConfig();
        $order      = \WxPayApi::createrentbill($config, $input);
        echo($order);
        exit;
    }
IsOut_trade_noSet()) {
			throw new WxPayException("缺少统一支付接口必填参数out_trade_no!");
		}else if(!$inputObj->IsBodySet()){
			throw new WxPayException("缺少统一支付接口必填参数body!");
		}else if(!$inputObj->IsTotal_feeSet()) {
			throw new WxPayException("缺少统一支付接口必填参数total_fee!");
		}else if(!$inputObj->IsTrade_typeSet()) {
			throw new WxPayException("缺少统一支付接口必填参数trade_type!");
		}
		
		//关联参数
		if($inputObj->GetTrade_type() == "JSAPI" && !$inputObj->IsOpenidSet()){
			throw new WxPayException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!");
		}
		if($inputObj->GetTrade_type() == "NATIVE" && !$inputObj->IsProduct_idSet()){
			throw new WxPayException("统一支付接口中,缺少必填参数product_id!trade_type为JSAPI时,product_id为必填参数!");
		}
		
		//异步通知url未设置,则使用配置文件中的url
		if(!$inputObj->IsNotify_urlSet() && $config->GetNotifyUrl() != ""){
			$inputObj->SetNotify_url($config->GetNotifyUrl());//异步通知url
		}
		
		$inputObj->SetAppid($config->GetAppId());//公众账号ID
		$inputObj->SetMch_id($config->GetMerchantId());//商户号
		$inputObj->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//终端ip	   	    
		$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
		
		//签名
		$inputObj->SetSign($config);
		$xml = $inputObj->ToXml();
		
		$startTimeStamp = self::getMillisecond();//请求开始时间
		$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
		$result = WxPayResults::Init($config, $response);
		self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
		
		return $result;
	}
	
	/**
	 * 
	 * 查询订单,WxPayOrderQuery中out_trade_no、transaction_id至少填一个
	 * appid、mchid、spbill_create_ip、nonce_str不需要填入
	 * @param WxPayConfigInterface $config  配置对象
	 * @param WxPayOrderQuery $inputObj
	 * @param int $timeOut
	 * @throws WxPayException
	 * @return 成功时返回,其他抛异常
	 */
	public static function orderQuery($config, $inputObj, $timeOut = 6)
	{
		$url = "https://api.mch.weixin.qq.com/pay/orderquery";
		//检测必填参数
		if(!$inputObj->IsOut_trade_noSet() && !$inputObj->IsTransaction_idSet()) {
			throw new WxPayException("订单查询接口中,out_trade_no、transaction_id至少填一个!");
		}
		$inputObj->SetAppid($config->GetAppId());//公众账号ID
		$inputObj->SetMch_id($config->GetMerchantId());//商户号
		$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
		
		$inputObj->SetSign($config);//签名
		$xml = $inputObj->ToXml();
		
		$startTimeStamp = self::getMillisecond();//请求开始时间
		$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
		$result = WxPayResults::Init($config, $response);
		self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
		
		return $result;
	}

    public static function set_error($error)
    {
        self::$error = $error;
    }

    public static function get_error()
    {
        return self::$error;
    }

    /**
     * 新请求方式。 直接调用当前面的签名 V2
     * https://api.mch.weixin.qq.com/pay/orderquery
     * @param $config
     * @param $values  请求前设置的一些参数,如:$values['out_order_no']  //商户订单号
     * @param int $timeOut
     * @return array
     * @throws WxPayException
     */
    public static function orderQueryNew($config, $values, $timeOut = 6)
    {
        $url = "https://api.mch.weixin.qq.com/pay/orderquery";
        //检测必填参数
        if(!$values['out_order_no']) {
            self::set_error('"订单查询接口中,out_order_no必填!"');
            return false;
        }
        $values['appid']    = $config->GetAppId();
        $values['sign']     = self::MakeSign($config, $values);//生成签名
        self::$values       = $values;
        $xml                = self::ToXml($values);

        $startTimeStamp     = self::getMillisecond();//请求开始时间
        $response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
        $result             = WxPayResults::Init($config, $response);//将xml转为array
        self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间

        return $result;
    }

    /**
     * 创建支付分订单API
     * https://wechatpay-api.gitbook.io/wechatpay-api-v3/
     * https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter3_1.shtml
     * @param $config
     * @param $values
     * @param int $timeOut
     * @return array|bool
     */
    public static function createrentbill($config, $values, $timeOut = 6)
    {
        $url = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder";
        //检测必填参数
        if(!$values['out_order_no']) {
            self::set_error('"订单查询接口中,out_order_no必填!"');
            return false;
        }
        $values['appid']    = $config->GetAppId();


        self::$values       = $values;
        $serial_no      = config("weixin.WXPT_SERIAL_NO");//平台证书序列号
        //生成V3请求 header认证信息
        $header         = self::createAuthorization( $url ,$values, 'POST' );
        $post_data      = json_encode($values , JSON_UNESCAPED_UNICODE);
        //增加平台证书序列号 , 平台证书序列号方法 getcertificates()
        $header[]       = 'Wechatpay-Serial:' . $serial_no;

        $response       = self::httpRequest($config, $post_data, $url, false, "POST", $header);
        var_dump($response);exit;
        return json_decode($response , true);




        $values['sign']     = self::MakeSign($config, $values);//生成签名
        self::$values       = $values;
        $xml                = self::ToXml($values);


        $startTimeStamp     = self::getMillisecond();//请求开始时间
        $response           = self::postXmlCurl($config, $xml, $url, false, $timeOut);//pos请求 postXmlCurl
        $result             = WxPayResults::Init($config, $response);//将xml转为array
        var_dump($result);exit;
        self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间

        return $result;
    }


    public static function pay_v3($config, $values, $timeOut = 6)
    {
        $serial_no      = config("weixin.WXPT_SERIAL_NO");//平台证书序列号
        $url = "https://api.mch.weixin.qq.com/pay/orderquery";
        //检测必填参数
        if(!$values['out_order_no']) {
            self::set_error('"订单查询接口中,out_order_no必填!"');
            return false;
        }
        $values['appid']    = $config->GetAppId();
        $values['sign']     = self::MakeSign($config, $values);//生成签名
        self::$values       = $values;

        //生成V3请求 header认证信息
        $header         = self::createAuthorization( $url ,$values, 'POST' );
        $post_data      = json_encode($values , JSON_UNESCAPED_UNICODE);
        //增加平台证书序列号 , 平台证书序列号方法 getcertificates()
        $header[]       = 'Wechatpay-Serial:' . $serial_no;

        $response       = self::httpRequest($config, $post_data, $url, false, "POST", $header);
        var_dump($response);exit;
        return json_decode($response , true);
    }

    //生成v3 Authorization
    public static function createAuthorization($url, $body='', $method = 'GET'){
        if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true)))
        {
            exit("当前PHP环境不支持SHA256withRSA");
        }

        $url_parts          = parse_url($url);
        $canonical_url      = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));

        //私钥地址
        $mch_private_key    = config('weixin.PRIVATE_KEY');
        //商户号
        $merchant_id        = config('weixin.MCHID');
        //当前时间戳
        $timestamp          = time();
        //随机字符串
        $nonce              = self::getNonceStr();
        //POST请求时 需要 转JSON字符串
        if(is_array($body)) $body = json_encode($body,JSON_UNESCAPED_UNICODE);
        $message = "{$method}\n".
            $canonical_url."\n".
            $timestamp."\n".
            $nonce."\n".
            $body."\n";
        $message    = ltrim($message);
        //生成签名
        openssl_sign($message, $raw_sign, openssl_get_privatekey(file_get_contents($mch_private_key)), 'sha256WithRSAEncryption');
        $sign               = base64_encode($raw_sign);
        //Authorization 类型
        $schema             = 'WECHATPAY2-SHA256-RSA2048';
        $serial_no          = config('weixin.SERIAL_NO');
        //生成token
        $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $merchant_id, $nonce, $timestamp, $serial_no, $sign);
        $header = [
            'Content-Type:application/json',
            'Accept:application/json',
            'User-Agent:*/*',
            'Authorization: '.  $schema . ' ' . $token
        ];
        echo $message;
        print_r($header);
        return $header;
    }
    /**
     * 获取平台证书, 与商户证书不是一个内容。暂时理解为用于解密和验签使用吧
     {"data":[{"effective_time":"2020-07-03T16:32:55+08:00","encrypt_certificate":{"algorithm":"AEAD_AES_256_GCM","associated_data":"certificate","ciphertext":"/h9rWmNFUMgCKMR5WgblfMM6tXjO5qQ657Jaj5Z9eVB4uKqqSe1fMJmmFruKfVgIMAY+oyWnEUehTO22Eg/r048ba4UeceY0DPSg+/GCLeXaOVw690q7HI3V3GA/RrS5hG3XpyXaBnA8KNsBwWiNR8ddkK5pFGeeg+DzuGgn+xdo2tvmTJabwjUGZQl3U1sTbRLJ7m6X3yc59WeBCRFPBm3ACY7nDzhRs0Bge8ODZSZExheWrCbDKEkwdHY6gFkB+/utf2gH0l4HvBorRNaF8+WO/vK4/SrPHphc14leYDXzXY4P1V6kAdebgJQ+45dHdg1S6XV9Jguf4aC2CU7IxZzkcUIJLtSmwMsjlYLr8asHh598DrQNOfPMkQzf9arcybvLgXPG2oOo1SWBaB9Z37N7LDEMopxcRkxi+qamb/BtIHZobdjdiqf3YRuTwWmJ9n7p7pvMlNxo1C0zyH9HRAcIKP++EOiID7J3mSdpHGjndze5UZllioHPsscJ0WpWk0v6LwNN6ByMMh49csDzuFhYBBbRytnIVrA/yoTR3R0SKuLgj53Cm1ZvNoauaBDzJnfUG6pBTll5gGMo3WTZvoic7Uwplo9XhDcw8JLZFj7/5c6qoQZHfdeEwBfMDGCUONOm7cPIjToZwCdA8NYEdEfOqK9kBkhfUmcS+w4muIB2idHxvDmrB6IkDUpGT/fOdO+Wd5yseXjm0peJxU3mwRwcb87Zl2+hGwGSIHGNjDcKRmzF6xAtq1Flhh5Ve+jukNsTVTWQfXJY/CxrE/T0Kce7I3NNJMzNGNzECLKUe00OB+Oa9h7xqoJizsNLTNHjkRV+ZuLmcN5zh4NNyv3q4v8MkHj8APoFLsDfD6r6SR4BQ4oWIO6ZBcoc+3pCKboqrAKcLdQuBk8DHjy8oX30pqEAsnDJapEtyE7BW/kqc7xXDjmiNTeRBw6zQ4jphsbNpXeQeJJgYb9XefdPwa7ZkuKL/On8CnCHQFw+Q1qxCn0YwdzMUFRKg0XxWY+tKGArPK5e9qqveddvOKEzSemaSeUFadwE+AKh9YxEnbAo/QKyJtY7+ZUhkfIJLrQmpEiBonNpIreexYCLhe/X7id31zwxP/9wYxV92FVUsz5PkIZCH0NDyU+kUUTfVMZXG25XJFcagM+EVg2FhKMeSLBqkl/5kqkJ8jJdAYOcvIdSOmf1Gz4ZtHASdhZ6R39xNx9inKWMC8TgdD4SmmzBptW0Kf+czjRGPlcy3wyofUTnG9cOHM+SMJqqeHQ9XysQnIcCytc9sOLv8fj0LRqdiSwOWhnrUa6RBXjsjGwfFanTsXbpRkhGRmbEsL7H3Fp0+Gl7FloVy/2IQVaUowvQcaHcIhDPUl4W49ts3QJrSUgfRRu7WzyTjT4Xe+pfsa02vMG41l38OsUANmn34XDKsmsJP3okzHe+nSntxqbGWioxB40qH19jvQB3Fmb/Peuzj3UrAtjKTDmzxx9uCBEdVEet2mWx1j7icYn1jJSETKmSFIfb8VqlFWZLveKs7YFCFZYIP7Zg2F2+fPtd8Zu81Xf1V/6WFjf2ZkKdr5BRk2JE6l5fhP3ILlPtlvI9IkhKvyHO98FKqnGOIcR9oe7+R2AIqQMcw2pMmqZa7K8V3jmbiFH7cLSMCtVfNG8gz7SYatlaglHZ9mc69xJDoMrw47s7lUalS42ZwuADR7Z1Oe5Z05edtIk47SVH70ST1SlOhcgklqaWGsU8tnE0kXKuTOYrrv06Dnmd5a2SjPs2LTgyqcAevsCzk2xm3c26DIOIWHSK5SZH0gKKEMur9L+QxW3fCUI2elIZjjBxwM8s3vGIZw1OdvY80xF+UvbTK9PV5wux0k53QEWXgCKIBT7veKR+Avh8FXYooQ==","nonce":"4b2368dfde75"},"expire_time":"2025-07-02T16:32:55+08:00","serial_no":"35DE58A6C50152902117A138F527EFD430934D7A"}]}
     */
    public static function getcertificates(){

        $url="https://api.mch.weixin.qq.com/v3/certificates";
        //生成V3请求 header认证信息
        $header = self::createAuthorization( $url );
        $header[] = 'User-Agent : https://zh.wikipedia.org/wiki/User_agent';
        $data = self::httpRequest([], [], $url, false, "GET", $header);

echo($data);exit;
        return json_decode($data , true);

    }

    /**
     * 支付及服务 - 服务人员注册
     * 1. 获取平台证书序列号  serial_no与 商户支付证书不是一个
     * 2. 解密平台证书,拿到平台证书信息
     * 3. 加密请求参数时 需要用户 平台证书进行加密
     */
    public static function regguide( $config, $post ,$serial_no='')
    {
        $serial_no      = config("weixin.WXPT_SERIAL_NO");//平台证书序列号
        $url            = "https://api.mch.weixin.qq.com/v3/smartguide/guides";

        //生成V3请求 header认证信息
        $header         = self::createAuthorization( $url ,$post, 'POST' );
        $post_data      = json_encode($post , JSON_UNESCAPED_UNICODE);
        //增加平台证书序列号 , 平台证书序列号方法 getcertificates()
        $header[]       = 'Wechatpay-Serial:' . $serial_no;
        $response       = self::httpRequest($config, $post_data, $url, false);
        return json_decode($response , true);
    }

    /**
     * V3加密
     */
    public static function getEncrypt($str){
        //$str是待加密字符串
        $public_key_path = '证书地址'; //看情况使用证书, 个别接口证书 使用的是 平台证书而不是 api证书
        $public_key = file_get_contents($public_key_path);
        $encrypted = '';

        if (openssl_public_encrypt($str,$encrypted,$public_key,OPENSSL_PKCS1_OAEP_PADDING)) {
            //base64编码
            $sign = base64_encode($encrypted);
        } else {
            throw new Exception('encrypt failed');
        }
        return $sign;
    }


    /**
     * Decrypt AEAD_AES_256_GCM ciphertext  V3签名解密
     * @param stingr    $aesKey             V3签名
     * @param string    $associatedData     AES GCM additional authentication data
     * @param string    $nonceStr           AES GCM nonce
     * @param string    $ciphertext         AES GCM cipher text
     *
     * @return string|bool      Decrypted string on success or FALSE on failure
     */
    public static function decryptToString($aesKey ,$associatedData, $nonceStr, $ciphertext)
    {

        if (strlen($aesKey) != 32 ) {
            throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
        }

        $ciphertext = \base64_decode($ciphertext , true);
        if (strlen($ciphertext) <= 16) {
            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, $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);
        }

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

    public static function getNonce()
    {
        static $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $charactersLength = strlen($characters);
        $randomString = '';
        for ($i = 0; $i < 32; $i++) {
            $randomString .= $characters[rand(0, $charactersLength - 1)];
        }
        return $randomString;
    }

    function GetValues()
    {
        return self::$values;
    }

    /**
     * 生成签名
     * @param WxPayConfigInterface $config  配置对象
     * @param bool $needSignType  是否需要补signtype
     * @return 签名,本函数不覆盖sign成员变量,如要设置签名需要调用SetSign方法赋值
     */
    public static function MakeSign($config, $values)
    {
        //签名步骤一:按字典序排序参数
        ksort($values);
        $string = self::ToUrlParams($values);
        //签名步骤二:在string后加入KEY
        $string = $string . "&key=".$config->GetKey();
        //签名步骤三:MD5加密或者HMAC-SHA256
        if($config->GetSignType() == "MD5"){
            $string = md5($string);
        } else if($config->GetSignType() == "HMAC-SHA256") {
            $string = hash_hmac("sha256",$string ,$config->GetKey());
        } else {
            self::set_error("签名类型不支持!");
            return false;
        }

        //签名步骤四:所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    public static function ToUrlParams($values)
    {
        $buff = "";
        foreach ($values as $k => $v)
        {
            if($k != "sign" && $v != "" && !is_array($v)){
                $buff .= $k . "=" . $v . "&";
            }
        }

        $buff = trim($buff, "&");
        return $buff;
    }

    /**
     * 输出xml字符
     * @throws WxPayException
     **/
    public static function ToXml($values)
    {
        if(!is_array($values) || count($values) <= 0)
        {
            throw new WxPayException("数组数据异常!");
        }

        $xml = "";
        foreach ($values as $key=>$val)
        {
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."";
            }else{
                $xml.="<".$key.">";
            }
        }
        $xml.="";
        return $xml;
    }

	/**
	 * 
	 * 关闭订单,WxPayCloseOrder中out_trade_no必填
	 * appid、mchid、spbill_create_ip、nonce_str不需要填入
	 * @param WxPayConfigInterface $config  配置对象
	 * @param WxPayCloseOrder $inputObj
	 * @param int $timeOut
	 * @throws WxPayException
	 * @return 成功时返回,其他抛异常
	 */
	public static function closeOrder($config, $inputObj, $timeOut = 6)
	{
		$url = "https://api.mch.weixin.qq.com/pay/closeorder";
		//检测必填参数
		if(!$inputObj->IsOut_trade_noSet()) {
			throw new WxPayException("订单查询接口中,out_trade_no必填!");
		}
		$inputObj->SetAppid($config->GetAppId());//公众账号ID
		$inputObj->SetMch_id($config->GetMerchantId());//商户号
		$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
		
		$inputObj->SetSign($config);//签名
		$xml = $inputObj->ToXml();
		
		$startTimeStamp = self::getMillisecond();//请求开始时间
		$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
		$result = WxPayResults::Init($config, $response);
		self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
		
		return $result;
	}

	/**
	 * 
	 * 申请退款,WxPayRefund中out_trade_no、transaction_id至少填一个且
	 * out_refund_no、total_fee、refund_fee、op_user_id为必填参数
	 * appid、mchid、spbill_create_ip、nonce_str不需要填入
	 * @param WxPayConfigInterface $config  配置对象
	 * @param WxPayRefund $inputObj
	 * @param int $timeOut
	 * @throws WxPayException
	 * @return 成功时返回,其他抛异常
	 */
	public static function refund($config, $inputObj, $timeOut = 6)
	{
		$url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
		//检测必填参数
		if(!$inputObj->IsOut_trade_noSet() && !$inputObj->IsTransaction_idSet()) {
			throw new WxPayException("退款申请接口中,out_trade_no、transaction_id至少填一个!");
		}else if(!$inputObj->IsOut_refund_noSet()){
			throw new WxPayException("退款申请接口中,缺少必填参数out_refund_no!");
		}else if(!$inputObj->IsTotal_feeSet()){
			throw new WxPayException("退款申请接口中,缺少必填参数total_fee!");
		}else if(!$inputObj->IsRefund_feeSet()){
			throw new WxPayException("退款申请接口中,缺少必填参数refund_fee!");
		}else if(!$inputObj->IsOp_user_idSet()){
			throw new WxPayException("退款申请接口中,缺少必填参数op_user_id!");
		}
		$inputObj->SetAppid($config->GetAppId());//公众账号ID
		$inputObj->SetMch_id($config->GetMerchantId());//商户号
		$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
		
		$inputObj->SetSign($config);//签名
		$xml = $inputObj->ToXml();
		$startTimeStamp = self::getMillisecond();//请求开始时间
		$response = self::postXmlCurl($config, $xml, $url, true, $timeOut);
		$result = WxPayResults::Init($config, $response);
		self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
		
		return $result;
	}
	
	/**
	 * 
	 * 查询退款
	 * 提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,
	 * 用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。
	 * WxPayRefundQuery中out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个
	 * appid、mchid、spbill_create_ip、nonce_str不需要填入
	 * @param WxPayConfigInterface $config  配置对象
	 * @param WxPayRefundQuery $inputObj
	 * @param int $timeOut
	 * @throws WxPayException
	 * @return 成功时返回,其他抛异常
	 */
	public static function refundQuery($config, $inputObj, $timeOut = 6)
	{
		$url = "https://api.mch.weixin.qq.com/pay/refundquery";
		//检测必填参数
		if(!$inputObj->IsOut_refund_noSet() &&
			!$inputObj->IsOut_trade_noSet() &&
			!$inputObj->IsTransaction_idSet() &&
			!$inputObj->IsRefund_idSet()) {
			throw new WxPayException("退款查询接口中,out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个!");
		}
		$inputObj->SetAppid($config->GetAppId());//公众账号ID
		$inputObj->SetMch_id($config->GetMerchantId());//商户号
		$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
		
		$inputObj->SetSign($config);//签名
		$xml = $inputObj->ToXml();
		
		$startTimeStamp = self::getMillisecond();//请求开始时间
		$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
		$result = WxPayResults::Init($config, $response);
		self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
		
		return $result;
	}
	
	/**
	 * 下载对账单,WxPayDownloadBill中bill_date为必填参数
	 * appid、mchid、spbill_create_ip、nonce_str不需要填入
	 * @param WxPayConfigInterface $config  配置对象
	 * @param WxPayDownloadBill $inputObj
	 * @param int $timeOut
	 * @throws WxPayException
	 * @return 成功时返回,其他抛异常
	 */
	public static function downloadBill($config, $inputObj, $timeOut = 6)
	{
		$url = "https://api.mch.weixin.qq.com/pay/downloadbill";
		//检测必填参数
		if(!$inputObj->IsBill_dateSet()) {
			throw new WxPayException("对账单接口中,缺少必填参数bill_date!");
		}
		$inputObj->SetAppid($config->GetAppId());//公众账号ID
		$inputObj->SetMch_id($config->GetMerchantId());//商户号
		$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
		
		$inputObj->SetSign($config);//签名
		$xml = $inputObj->ToXml();
		
		$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
		if(substr($response, 0 , 5) == ""){
			return "";
		}
		return $response;
	}
	
	/**
	 * 提交被扫支付API
	 * 收银员使用扫码设备读取微信用户刷卡授权码以后,二维码或条码信息传送至商户收银台,
	 * 由商户收银台或者商户后台调用该接口发起支付。
	 * WxPayWxPayMicroPay中body、out_trade_no、total_fee、auth_code参数必填
	 * appid、mchid、spbill_create_ip、nonce_str不需要填入
	 * @param WxPayConfigInterface $config  配置对象
	 * @param WxPayWxPayMicroPay $inputObj
	 * @param int $timeOut
	 */
	public static function micropay($config, $inputObj, $timeOut = 10)
	{
		$url = "https://api.mch.weixin.qq.com/pay/micropay";
		//检测必填参数
		if(!$inputObj->IsBodySet()) {
			throw new WxPayException("提交被扫支付API接口中,缺少必填参数body!");
		} else if(!$inputObj->IsOut_trade_noSet()) {
			throw new WxPayException("提交被扫支付API接口中,缺少必填参数out_trade_no!");
		} else if(!$inputObj->IsTotal_feeSet()) {
			throw new WxPayException("提交被扫支付API接口中,缺少必填参数total_fee!");
		} else if(!$inputObj->IsAuth_codeSet()) {
			throw new WxPayException("提交被扫支付API接口中,缺少必填参数auth_code!");
		}
		
		$inputObj->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//终端ip
		$inputObj->SetAppid($config->GetAppId());//公众账号ID
		$inputObj->SetMch_id($config->GetMerchantId());//商户号
		$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
		
		$inputObj->SetSign($config);//签名
		$xml = $inputObj->ToXml();
		
		$startTimeStamp = self::getMillisecond();//请求开始时间
		$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
		$result = WxPayResults::Init($config, $response);
		self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
		
		return $result;
	}
	
	/**
	 * 
	 * 撤销订单API接口,WxPayReverse中参数out_trade_no和transaction_id必须填写一个
	 * appid、mchid、spbill_create_ip、nonce_str不需要填入
	 * @param WxPayConfigInterface $config  配置对象
	 * @param WxPayReverse $inputObj
	 * @param int $timeOut
	 * @throws WxPayException
	 */
	public static function reverse($config, $inputObj, $timeOut = 6)
	{
		$url = "https://api.mch.weixin.qq.com/secapi/pay/reverse";
		//检测必填参数
		if(!$inputObj->IsOut_trade_noSet() && !$inputObj->IsTransaction_idSet()) {
			throw new WxPayException("撤销订单API接口中,参数out_trade_no和transaction_id必须填写一个!");
		}
		
		$inputObj->SetAppid($config->GetAppId());//公众账号ID
		$inputObj->SetMch_id($config->GetMerchantId());//商户号
		$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
		
		$inputObj->SetSign($config);//签名
		$xml = $inputObj->ToXml();
		
		$startTimeStamp = self::getMillisecond();//请求开始时间
		$response = self::postXmlCurl($config, $xml, $url, true, $timeOut);
		$result = WxPayResults::Init($config, $response);
		self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
		
		return $result;
	}
	
	/**
	 * 
	 * 测速上报,该方法内部封装在report中,使用时请注意异常流程
	 * WxPayReport中interface_url、return_code、result_code、user_ip、execute_time_必填
	 * appid、mchid、spbill_create_ip、nonce_str不需要填入
	 * @param WxPayConfigInterface $config  配置对象
	 * @param WxPayReport $inputObj
	 * @param int $timeOut
	 * @throws WxPayException
	 * @return 成功时返回,其他抛异常
	 */
	public static function report($config, $inputObj, $timeOut = 1)
	{
		$url = "https://api.mch.weixin.qq.com/payitil/report";
		//检测必填参数
		if(!$inputObj->IsInterface_urlSet()) {
			throw new WxPayException("接口URL,缺少必填参数interface_url!");
		} if(!$inputObj->IsReturn_codeSet()) {
			throw new WxPayException("返回状态码,缺少必填参数return_code!");
		} if(!$inputObj->IsResult_codeSet()) {
			throw new WxPayException("业务结果,缺少必填参数result_code!");
		} if(!$inputObj->IsUser_ipSet()) {
			throw new WxPayException("访问接口IP,缺少必填参数user_ip!");
		} if(!$inputObj->IsExecute_time_Set()) {
			throw new WxPayException("接口耗时,缺少必填参数execute_time_!");
		}
		$inputObj->SetAppid($config->GetAppId());//公众账号ID
		$inputObj->SetMch_id($config->GetMerchantId());//商户号
		$inputObj->SetUser_ip($_SERVER['REMOTE_ADDR']);//终端ip
		$inputObj->SetTime(date("YmdHis"));//商户上报时间	 
		$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
		
		$inputObj->SetSign($config);//签名
		$xml = $inputObj->ToXml();
		
		$startTimeStamp = self::getMillisecond();//请求开始时间
		$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
		return $response;
	}
	
	/**
	 * 
	 * 生成二维码规则,模式一生成支付二维码
	 * appid、mchid、spbill_create_ip、nonce_str不需要填入
	 * @param WxPayConfigInterface $config  配置对象
	 * @param WxPayBizPayUrl $inputObj
	 * @param int $timeOut
	 * @throws WxPayException
	 * @return 成功时返回,其他抛异常
	 */
	public static function bizpayurl($config, $inputObj, $timeOut = 6)
	{
		if(!$inputObj->IsProduct_idSet()){
			throw new WxPayException("生成二维码,缺少必填参数product_id!");
		}

		$inputObj->SetAppid($config->GetAppId());//公众账号ID
		$inputObj->SetMch_id($config->GetMerchantId());//商户号
		$inputObj->SetTime_stamp(time());//时间戳	 
		$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
		
		$inputObj->SetSign($config);//签名
		
		return $inputObj->GetValues();
	}
	
	/**
	 * 
	 * 转换短链接
	 * 该接口主要用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX),
	 * 减小二维码数据量,提升扫描速度和精确度。
	 * appid、mchid、spbill_create_ip、nonce_str不需要填入
	 * @param WxPayConfigInterface $config  配置对象
	 * @param WxPayShortUrl $inputObj
	 * @param int $timeOut
	 * @throws WxPayException
	 * @return 成功时返回,其他抛异常
	 */
	public static function shorturl($config, $inputObj, $timeOut = 6)
	{
		$url = "https://api.mch.weixin.qq.com/tools/shorturl";
		//检测必填参数
		if(!$inputObj->IsLong_urlSet()) {
			throw new WxPayException("需要转换的URL,签名用原串,传输需URL encode!");
		}
		$inputObj->SetAppid($config->GetAppId());//公众账号ID
		$inputObj->SetMch_id($config->GetMerchantId());//商户号
		$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
		
		$inputObj->SetSign($config);//签名
		$xml = $inputObj->ToXml();
		
		$startTimeStamp = self::getMillisecond();//请求开始时间
		$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
		$result = WxPayResults::Init($config, $response);
		self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
		
		return $result;
	}
	
 	/**
 	 * 
 	 * 支付结果通用通知
 	 * @param function $callback
 	 * 直接回调函数使用方法: notify(you_function);
 	 * 回调类成员函数方法:notify(array($this, you_function));
 	 * $callback  原型为:function function_name($data){}
 	 */
	public static function notify($config, $callback, &$msg)
	{
		if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
			# 如果没有数据,直接返回失败
			return false;
		}

		//如果返回成功则验证签名
		try {
			//获取通知的数据
			$xml = $GLOBALS['HTTP_RAW_POST_DATA'];
			$result = WxPayNotifyResults::Init($config, $xml);
		} catch (WxPayException $e){
			$msg = $e->errorMessage();
			return false;
		}
		
		return call_user_func($callback, $result);
	}
	
	/**
	 * 
	 * 产生随机字符串,不长于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;
	}
	
	/**
	 * 直接输出xml
	 * @param string $xml
	 */
	public static function replyNotify($xml)
	{
		echo $xml;
	}
	
	/**
	 * 
	 * 上报数据, 上报的时候将屏蔽所有异常流程
	 * @param WxPayConfigInterface $config  配置对象
	 * @param string $usrl
	 * @param int $startTimeStamp
	 * @param array $data
	 */
	private static function reportCostTime($config, $url, $startTimeStamp, $data)
	{
		//如果不需要上报数据
		$reportLevenl = $config->GetReportLevenl();
		if($reportLevenl == 0){
			return;
		} 
		//如果仅失败上报
		if($reportLevenl == 1 &&
			 array_key_exists("return_code", $data) &&
			 $data["return_code"] == "SUCCESS" &&
			 array_key_exists("result_code", $data) &&
			 $data["result_code"] == "SUCCESS")
		 {
		 	return;
		 }
		 
		//上报逻辑
		$endTimeStamp = self::getMillisecond();
		$objInput = new WxPayReport();
		$objInput->SetInterface_url($url);
		$objInput->SetExecute_time_($endTimeStamp - $startTimeStamp);
		//返回状态码
		if(array_key_exists("return_code", $data)){
			$objInput->SetReturn_code($data["return_code"]);
		}
		//返回信息
		if(array_key_exists("return_msg", $data)){
			$objInput->SetReturn_msg($data["return_msg"]);
		}
		//业务结果
		if(array_key_exists("result_code", $data)){
			$objInput->SetResult_code($data["result_code"]);
		}
		//错误代码
		if(array_key_exists("err_code", $data)){
			$objInput->SetErr_code($data["err_code"]);
		}
		//错误代码描述
		if(array_key_exists("err_code_des", $data)){
			$objInput->SetErr_code_des($data["err_code_des"]);
		}
		//商户订单号
		if(array_key_exists("out_trade_no", $data)){
			$objInput->SetOut_trade_no($data["out_trade_no"]);
		}
		//设备号
		if(array_key_exists("device_info", $data)){
			$objInput->SetDevice_info($data["device_info"]);
		}
		
		try{
			self::report($config, $objInput);
		} catch (WxPayException $e){
			//不做任何处理
		}
	}

    /**
     * CURL请求
     * @param $url 请求url地址
     * @param $method 请求方法 get post
     * @param null $postfields post数据数组
     * @param array $headers 请求header信息
     * @param bool|false $debug 调试开启 默认false
     * @return mixed
     */
    public static function httpRequest($config, $postfields, $url, $useCert = false, $method = "POST", $headers = array(), $debug = false)
    {
        $method = strtoupper($method);
        $ci = curl_init();
        /* Curl settings */
        curl_setopt($ci, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
        curl_setopt($ci, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0");
        curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, 60); /* 在发起连接前等待的时间,如果设置为0,则无限等待 */
        curl_setopt($ci, CURLOPT_TIMEOUT, 7); /* 设置cURL允许执行的最长秒数 */
        curl_setopt($ci, CURLOPT_RETURNTRANSFER, true);
        switch ($method) {
            case "POST":
                curl_setopt($ci, CURLOPT_POST, true);
                if (!empty($postfields)) {
                    $tmpdatastr = is_array($postfields) ? http_build_query($postfields) : $postfields;
                    curl_setopt($ci, CURLOPT_POSTFIELDS, $tmpdatastr);
                }
                break;
            default:
                curl_setopt($ci, CURLOPT_CUSTOMREQUEST, $method); /* //设置请求方式 */
                break;
        }
        $ssl = preg_match('/^https:\/\//i', $url) ? TRUE : FALSE;
        curl_setopt($ci, CURLOPT_URL, $url);
        if ($ssl) {
            curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, FALSE); // https请求 不验证证书和hosts
            curl_setopt($ci, CURLOPT_SSL_VERIFYHOST, FALSE); // 不从证书中检查SSL加密算法是否存在
        }
        curl_setopt($ci, CURLOPT_HEADER, FALSE); /*启用时会将头文件的信息作为数据流输出*/

        $ch     = $ci;
        if($useCert == true){
            //设置证书
            //使用证书:cert 与 key 分别属于两个.pem文件
            //证书文件请放入服务器的非web目录下
            $sslCertPath    = "../extend/wxpay_lib/cert/apiclient_cert.pem";
            $sslKeyPath     = "../extend/wxpay_lib/cert/apiclient_key.pem";
            $config->GetSSLCertPath($sslCertPath, $sslKeyPath);
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT, $sslCertPath);
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY, $sslKeyPath);
        }


        curl_setopt($ci, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($ci, CURLOPT_MAXREDIRS, 2);/*指定最多的HTTP重定向的数量,这个选项是和CURLOPT_FOLLOWLOCATION一起使用的*/
        curl_setopt($ci, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ci, CURLINFO_HEADER_OUT, true);
        /*curl_setopt($ci, CURLOPT_COOKIE, $Cookiestr); * *COOKIE带过去** */
        $response = curl_exec($ci);
        $requestinfo = curl_getinfo($ci);
        $http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);
        if ($debug) {
            echo "=====post data======\r\n";
            var_dump($postfields);
            echo "=====info===== \r\n";
            print_r($requestinfo);
            echo "=====response=====\r\n";
            print_r($response);
        }
        curl_close($ci);
        return $response;
        //return array($http_code, $response,$requestinfo);
    }

	/**
	 * 以post方式提交xml到对应的接口url
	 * 
	 * @param WxPayConfigInterface $config  配置对象
	 * @param string $xml  需要post的xml数据
	 * @param string $url  url
	 * @param bool $useCert 是否需要证书,默认不需要
	 * @param int $second   url执行超时时间,默认30s
	 * @throws WxPayException
	 */
	private static function postXmlCurl($config, $xml, $url, $useCert = false, $second = 30)
	{		
		$ch = curl_init();
		$curlVersion = curl_version();
		$ua = "WXPaySDK/3.0.9 (".PHP_OS.") PHP/".PHP_VERSION." CURL/".$curlVersion['version']." "
		.$config->GetMerchantId();

		//设置超时
		curl_setopt($ch, CURLOPT_TIMEOUT, $second);

		$proxyHost = "0.0.0.0";
		$proxyPort = 0;
		$config->GetProxy($proxyHost, $proxyPort);
		//如果有配置代理这里就设置代理
		if($proxyHost != "0.0.0.0" && $proxyPort != 0){
			curl_setopt($ch,CURLOPT_PROXY, $proxyHost);
			curl_setopt($ch,CURLOPT_PROXYPORT, $proxyPort);
		}
		curl_setopt($ch,CURLOPT_URL, $url);
		curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
		curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
		curl_setopt($ch,CURLOPT_USERAGENT, $ua); 
		//设置header
		curl_setopt($ch, CURLOPT_HEADER, FALSE);
		//要求结果为字符串且输出到屏幕上
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
	
		if($useCert == true){
			//设置证书
			//使用证书:cert 与 key 分别属于两个.pem文件
			//证书文件请放入服务器的非web目录下
			$sslCertPath = "";
			$sslKeyPath = "";
			$config->GetSSLCertPath($sslCertPath, $sslKeyPath);
			curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLCERT, $sslCertPath);
			curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLKEY, $sslKeyPath);
		}
		//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);
			throw new WxPayException("curl出错,错误码:$error");
		}
	}
	
	/**
	 * 获取毫秒级别的时间戳
	 */
	private static function getMillisecond()
	{
		//获取毫秒的时间戳
		$time = explode ( " ", microtime () );
		$time = $time[1] . ($time[0] * 1000);
		$time2 = explode( ".", $time );
		$time = $time2[0];
		return $time;
	}
}

 

 

你可能感兴趣的:(微信平台)