<?PHP
/**
 * Name:微信支付类
 * Version:1.0
 * Author:阳光男孩
 * Author QQ:51154393
 * Author Url:www.uephp.com
 **/

class Wxpay
{

    public    $error;//错误信息
    protected $mchid;
    protected $appid;
    protected $appKey;
    protected $apiKey;
    protected $apiclient_cert;//商户API证书文件路径
    protected $apiclient_key; //商户API证书密钥文件路径

    public function __construct ($mchid , $appid , $key)
    {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    //初始化
        $this->mchid  = $mchid;                                                                                                                                                                                                                                                                                                                                                 //微信支付商户号 PartnerID 通过微信支付商户资料审核后邮件发送
        $this->appid = $appid;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           //微信支付申请对应的公众号的APPID
        $this->apiKey = $key;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            //https://pay.weixin.qq.com 帐户设置-安全设置-API安全-API密钥-设置API密钥
    }

    /**
     * 设置API证书
     *
     * @return $this
     */
    public function setCert ($apiclient_cert , $apiclient_key)
    {
        $this->apiclient_cert = $apiclient_cert;
        $this->apiclient_key  = $apiclient_key;
        return $this;
    }

    /**
     * 扫码发起订单
     *
     * @param float  $payAmount  收款总费用 单位元
     * @param string $outTradeNo 唯一的订单号
     * @param string $orderName  订单名称
     * @param string $notifyUrl  支付结果通知url 不要有问号
     *
     * @return array
     */
    public function qrcodePay ($outTradeNo , $orderName , $payAmount , $notifyUrl)
    {
        $unified           = [
            'appid'            => $this->appid ,
            'attach'           => 'pay' ,
            //商家数据包，原样返回，如果填写中文，请注意转换为utf-8
            'body'             => $orderName ,
            'mch_id'           => $this->mchid ,
            'nonce_str'        => self::createNonceStr() ,
            'notify_url'       => $notifyUrl ,
            'out_trade_no'     => $outTradeNo ,
            'spbill_create_ip' => '127.0.0.1' ,
            'total_fee'        => floatval($payAmount) * 100 ,
            //单位 转为分
            'trade_type'       => 'NATIVE' ,
        ];
        $unified[ 'sign' ] = self::getSign($unified , $this->apiKey);
        $responseXml       = $this->curlPost('https://api.mch.weixin.qq.com/pay/unifiedorder' , self::arrayToXml($unified));
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $Result = simplexml_load_string($responseXml , 'SimpleXMLElement' , LIBXML_NOCDATA);
        if ($Result === false) {
            return [
                'code' => 201 ,
                'msg'  => 'parse xml error'
            ];
        }
        if ($Result->return_code != 'SUCCESS') {
            return [
                'code' => 201 ,
                'msg'  => $Result->return_msg
            ];
        }
        if ($Result->result_code != 'SUCCESS') {
            return [
                'code' => 201 ,
                'msg'  => $Result->err_code
            ];
        }
        $codeUrl = (array)( $Result->code_url );
        if ( !$codeUrl[ 0 ]) {
            return [
                'code' => 201 ,
                'msg'  => 'get code_url error'
            ];
        }
        $arr              = [
            "appId"     => $this->appid ,
            "timeStamp" => (string)time() ,
            "nonceStr"  => self::createNonceStr() ,
            "package"   => "prepay_id=" . $Result->prepay_id ,
            "signType"  => 'MD5' ,
            "code_url"  => $codeUrl[ 0 ] ,
        ];
        $arr[ 'paySign' ] = self::getSign($arr , $this->apiKey);
        $arr[ 'code' ]    = 200;
        return $arr;
    }

    /**
     * h5发起订单
     *
     * @param float  $payAmount  收款总费用 单位元
     * @param string $outTradeNo 唯一的订单号
     * @param string $orderName  订单名称
     * @param string $notifyUrl  支付结果通知url 不要有问号
     *
     * @return array
     */
    public function h5Pay ($outTradeNo , $orderName , $payAmount , $notifyUrl , $returnUrl , $wapUrl = '' , $wapName = '')
    {
        $unified           = [
            'appid'            => $this->appid ,
            'attach'           => 'pay' ,
            //商家数据包，原样返回，如果填写中文，请注意转换为utf-8
            'body'             => $orderName ,
            'mch_id'           => $this->mchid ,
            'nonce_str'        => self::createNonceStr() ,
            'notify_url'       => $notifyUrl ,
            'out_trade_no'     => $outTradeNo ,
            'spbill_create_ip' => $_SERVER[ 'REMOTE_ADDR' ] ,
            'total_fee'        => floatval($payAmount) * 100 ,
            //单位 转为分
            'trade_type'       => 'MWEB' ,
            'scene_info'       => json_encode([
                'h5_info' => [
                    'type'     => 'Wap' ,
                    'wap_url'  => $wapUrl ,
                    'wap_name' => $wapName
                ]
            ])
        ];
        $unified[ 'sign' ] = self::getSign($unified , $this->apiKey);
        $responseXml       = $this->curlPost('https://api.mch.weixin.qq.com/pay/unifiedorder' , self::arrayToXml($unified));
        $Result            = simplexml_load_string($responseXml , 'SimpleXMLElement' , LIBXML_NOCDATA);
        if ($Result->return_code != 'SUCCESS') {
            return [
                'code' => 201 ,
                'msg'  => $Result->return_msg
            ];
        }
        if ($Result->mweb_url) {
            return [
                'code' => 200 ,
                'msg'  => 'success' ,
                'url'  => $Result->mweb_url . '&redirect_url=' . urlencode($returnUrl)
            ];
        }
        return [
            'code' => 201 ,
            'msg'  => 'fail'
        ];
    }

    /**
     * jsApi统一下单
     *
     * @param string $openid     调用【网页授权获取用户信息】接口获取到用户在该公众号下的Openid
     * @param float  $payAmount  收款总费用 单位元
     * @param string $outTradeNo 唯一的订单号
     * @param string $orderName  订单名称
     * @param string $notifyUrl  支付结果通知url 不要有问号
     *
     * @return string
     */
    public function jsApiPay ($openid , $outTradeNo , $orderName , $payAmount , $notifyUrl)
    {
        //$orderName = iconv('GBK','UTF-8',$orderName);
        $unified           = [
            'appid'            => $this->appid ,
            'attach'           => 'pay' ,
            //商家数据包，原样返回，如果填写中文，请注意转换为utf-8
            'body'             => $orderName ,
            'mch_id'           => $this->mchid ,
            'nonce_str'        => self::createNonceStr() ,
            'notify_url'       => $notifyUrl ,
            'openid'           => $openid ,
            //rade_type=JSAPI，此参数必传
            'out_trade_no'     => $outTradeNo ,
            'spbill_create_ip' => '127.0.0.1' ,
            'total_fee'        => floatval($payAmount) * 100 ,
            //单位 转为分
            'trade_type'       => 'JSAPI' ,
        ];
        $unified[ 'sign' ] = self::getSign($unified , $this->apiKey);
        $responseXml       = $this->curlPost('https://api.mch.weixin.qq.com/pay/unifiedorder' , self::arrayToXml($unified));
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $Result = simplexml_load_string($responseXml , 'SimpleXMLElement' , LIBXML_NOCDATA);
        if ($Result === false) {
            return [
                'code' => 201 ,
                'msg'  => 'parse xml error'
            ];
        }
        if ($Result->return_code != 'SUCCESS') {
            return [
                'code' => 201 ,
                'msg'  => $Result->return_msg
            ];
        }
        if ($Result->result_code != 'SUCCESS') {
            return [
                'code' => 201 ,
                'msg'  => $Result->err_code
            ];
        }
        $arr              = [
            "appId"     => $this->appid ,
            "timeStamp" => (string)time() ,
            //这里是字符串的时间戳，不是int，所以需加引号
            "nonceStr"  => self::createNonceStr() ,
            "package"   => "prepay_id=" . $Result->prepay_id ,
            "signType"  => 'MD5' ,
        ];
        $arr[ 'paySign' ] = self::getSign($arr , $this->apiKey);
        $arr[ 'code' ]    = 200;
        return $arr;
    }

    /**
     * 退款
     *
     * @param float  $payAmount    订单金额 单位元
     * @param float  $refundAmount 退款金额 单位元
     * @param string $refundNo     退款单号
     * @param string $wxOrderNo    微信订单号
     * @param string $orderNo      商户订单号
     *
     * @return string
     */
    public function PayRefund ($refundNo , $payAmount , $refundAmount , $orderNo , $tradeNo = false)
    {
        $unified = [
            'appid'         => $this->appid ,
            'mch_id'        => $this->mchid ,
            'nonce_str'     => self::createNonceStr() ,
            'total_fee'     => floatval($payAmount) * 100 ,
            //订单金额	 单位 转为分
            'refund_fee'    => floatval($refundAmount) * 100 ,
            //退款金额 单位 转为分
            'sign_type'     => 'MD5' ,
            //签名类型 支持HMAC-SHA256和MD5，默认为MD5
            'out_refund_no' => $refundNo ,
            //商户退款单号
        ];
        if ($tradeNo) {
            $unified[ 'transaction_id' ] = $orderNo;//使用支付宝交易号
        } else {
            $unified[ 'out_trade_no' ] = $orderNo;//使用商户订单号
        }

        if (empty($this->apiclient_cert) || !is_file($this->apiclient_cert) || empty($this->apiclient_key) || !is_file($this->apiclient_key)) {
            return [
                'code' => 201 ,
                'msg'  => '该接口需要先设置API证书'
            ];
        }

        $unified[ 'sign' ] = self::getSign($unified , $this->apiKey);
        $responseXml       = $this->curlPost('https://api.mch.weixin.qq.com/secapi/pay/refund' , self::arrayToXml($unified));
        $Result            = simplexml_load_string($responseXml , 'SimpleXMLElement' , LIBXML_NOCDATA);
        if ($Result === false) {
            return [
                'code' => 201 ,
                'msg'  => 'parse xml error'
            ];
        }
        if ($Result->return_code != 'SUCCESS') {
            return [
                'code' => 201 ,
                'msg'  => $Result->return_msg
            ];
        }
        if ($Result->result_code != 'SUCCESS') {
            return [
                'code' => 201 ,
                'msg'  => $Result->err_code
            ];
        }
        return [
            'code' => 200 ,
            'msg'  => 'success'
        ];
    }

    /**
     * 关闭订单
     */
    public function PayClose ($outTradeNo)
    {
        $unified           = [
            'appid'        => $this->appid ,
            'mch_id'       => $this->mchid ,
            'out_trade_no' => $outTradeNo ,
            'nonce_str'    => self::createNonceStr() ,
        ];
        $unified[ 'sign' ] = self::getSign($unified , $this->apiKey);

        $responseXml = $this->curlPost('https://api.mch.weixin.qq.com/pay/closeorder' , self::arrayToXml($unified));
        $Result      = simplexml_load_string($responseXml , 'SimpleXMLElement' , LIBXML_NOCDATA);
        if ($Result === false) {
            return [
                'code' => 201 ,
                'msg'  => 'parse xml error'
            ];
        }
        if ($Result->return_code != 'SUCCESS' || $Result->result_code != 'SUCCESS') {
            return [
                'code' => 201 ,
                'msg'  => $Result->return_msg
            ];
        }
        return [
            'code' => 200 ,
            'msg'  => 'success'
        ];
    }

    /*订单查询*/
    public function PayQuery ($outTradeNo)
    {
        $unified           = [
            'appid'        => $this->appid ,
            'mch_id'       => $this->mchid ,
            'out_trade_no' => $outTradeNo ,
            'nonce_str'    => self::createNonceStr() ,
        ];
        $unified[ 'sign' ] = self::getSign($unified , $this->apiKey);
        $responseXml       = $this->curlPost('https://api.mch.weixin.qq.com/pay/orderquery' , self::arrayToXml($unified));
        $Result            = simplexml_load_string($responseXml , 'SimpleXMLElement' , LIBXML_NOCDATA);
        if ($Result === false) {
            return [
                'code' => 201 ,
                'msg'  => 'parse xml error'
            ];
        }
        if ($Result->return_code != 'SUCCESS') {
            return [
                'code' => 201 ,
                'msg'  => $Result->return_msg
            ];
        }
        $trade_state    = $Result->trade_state;
        $data[ 'code' ] = $trade_state == 'SUCCESS' ? 200 : 201;
        $data[ 'data' ] = $trade_state;
        $data[ 'msg' ]  = $this->getTradeSTate($trade_state);
        $data[ 'time' ] = date('Y-m-d H:i:s');
        return $data;
    }

    public function getTradeSTate ($str)
    {
        switch ($str) {
            case 'SUCCESS';
                return '支付成功';
            case 'REFUND';
                return '转入退款';
            case 'NOTPAY';
                return '未支付';
            case 'CLOSED';
                return '已关闭';
            case 'REVOKED';
                return '已撤销（刷卡支付）';
            case 'USERPAYING';
                return '用户支付中';
            case 'PAYERROR';
                return '支付失败';
        }
    }


    /*异步通知验证*/
    public function notify ()
    {
        $postStr = file_get_contents('php://input');
        libxml_disable_entity_loader(true);//禁止引用外部xml实体
        $postObj = simplexml_load_string($postStr , 'SimpleXMLElement' , LIBXML_NOCDATA);
        if ($postObj === false) {
            $this->error = 'parse xml error';
            return false;
        }
        if ($postObj->return_code != 'SUCCESS') {
            $this->error = $postObj->return_msg;
            return false;
        }
        if ($postObj->result_code != 'SUCCESS') {
            $this->error = $postObj->err_code;
            return false;
        }
        $arr = (array)$postObj;
        unset($arr[ 'sign' ]);
        if (self::getSign($arr , $this->apiKey) == $postObj->sign) {
            echo '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
            return $arr;
        }
        return false;
    }

    /**
     * 通过跳转获取用户的openid，跳转流程如下：
     * 1、设置自己需要调回的url及其其他参数，跳转到微信服务器https://open.weixin.qq.com/connect/oauth2/authorize
     * 2、微信服务处理完成之后会跳转回用户redirect_uri地址，此时会带上一些参数，如：code
     *
     * @return 用户的openid
     */
    public function GetOpenid ($appKey)
    {
        $this->appKey = $appKey;
        if ( !isset($_GET[ 'code' ])) {//通过code获得openid
            //触发微信返回code码
            $scheme = $_SERVER[ 'HTTPS' ] == 'on' ? 'https://' : 'http://';
            $uri    = $_SERVER[ 'PHP_SELF' ] . $_SERVER[ 'QUERY_STRING' ];
            if ($_SERVER[ 'REQUEST_URI' ]) $uri = $_SERVER[ 'REQUEST_URI' ];
            $baseUrl = urlencode($scheme . $_SERVER[ 'HTTP_HOST' ] . $uri);
            $url     = $this->__CreateOauthUrlForCode($baseUrl);
            Header("Location: $url");
            exit();
        } else {
            $code   = $_GET[ 'code' ];
            $openid = $this->getOpenidFromMp($code);//获取code码，以获取openid
            return $openid;
        }
    }

    /**
     * 通过code从工作平台获取openid机器access_token
     *
     * @param string $code 微信跳转回来带上的code
     *
     * @return openid
     */
    public function GetOpenidFromMp ($code)
    {
        $url = $this->__CreateOauthUrlForOpenid($code);
        $res = self::curlGet($url);
        //取出openid
        $data       = json_decode($res , true);
        $this->data = $data;
        $openid     = $data[ 'openid' ];
        return $openid;
    }

    /**
     * 构造获取open和access_toke的url地址
     *
     * @param string $code，微信跳转带回的code
     *
     * @return 请求的url
     */
    private function __CreateOauthUrlForOpenid ($code)
    {
        $urlObj[ "appid" ]      = $this->appid;
        $urlObj[ "secret" ]     = $this->appKey;
        $urlObj[ "code" ]       = $code;
        $urlObj[ "grant_type" ] = "authorization_code";
        $bizString              = $this->ToUrlParams($urlObj);
        return "https://api.weixin.qq.com/sns/oauth2/access_token?" . $bizString;
    }

    /**
     * 构造获取code的url连接
     *
     * @param string $redirectUrl 微信服务器回跳的url，需要url编码
     *
     * @return 返回构造好的url
     */
    private function __CreateOauthUrlForCode ($redirectUrl)
    {
        $urlObj[ "appid" ]         = $this->appid;
        $urlObj[ "redirect_uri" ]  = "$redirectUrl";
        $urlObj[ "response_type" ] = "code";
        $urlObj[ "scope" ]         = "snsapi_base";
        $urlObj[ "state" ]         = "STATE" . "#wechat_redirect";
        $bizString                 = $this->ToUrlParams($urlObj);
        return "https://open.weixin.qq.com/connect/oauth2/authorize?" . $bizString;
    }

    /**
     * 拼接签名字符串
     *
     * @param array $urlObj
     *
     * @return 返回已经拼接好的字符串
     */
    private function ToUrlParams ($urlObj)
    {
        $buff = "";
        foreach ($urlObj as $k => $v) {
            if ($k != "sign") $buff .= $k . "=" . $v . "&";
        }
        $buff = trim($buff , "&");
        return $buff;
    }

    /**
     * curl get
     *
     * @param string $url
     * @param array  $options
     *
     * @return mixed
     */
    public static function curlGet ($url = '' , $options = [])
    {
        $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 function curlPost ($url = '' , $postData = '' , $options = [])
    {
        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);
        }

        if ( !empty($this->apiclient_cert) && is_file($this->apiclient_cert) && !empty($this->apiclient_key) && is_file($this->apiclient_key)) {
            curl_setopt($ch , CURLOPT_SSLCERTTYPE , 'PEM');
            curl_setopt($ch , CURLOPT_SSLCERT , $this->apiclient_cert);
            curl_setopt($ch , CURLOPT_SSLKEYTYPE , 'PEM');
            curl_setopt($ch , CURLOPT_SSLKEY , $this->apiclient_key);
        } else {
            //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 createNonceStr ($length = 16)
    {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $str   = '';
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars , mt_rand(0 , strlen($chars) - 1) , 1);
        }
        return $str;
    }

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