Undefined index: expired_at
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (257)
#1{closure}(8, Undefined index: expired_at, /usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php, 257, Array(8))
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (257)
<?php
 
/**
 * weixin library
 * @author geekzhumail@gmail.com
 * @since 2014-09-24
 * @edit 2015-03-05
 */
class SDKRuntimeException extends Exception {
 
    public function errorMessage() {
        return $this->getMessage();
    }
}
 
class WechatSDK extends WechatBase{
 
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => 'wxdf0a039cf4ea94d1',
        'appsecret' => '7100f0fe837a5287f075b6d2d9814ca0',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/';
    /**
     * @var access_token
     */
    protected $accessToken = '';
 
    /**
     * 是否从远程服务器拿token
     */
    protected $isRemote = false;
 
    public function __construct() {
        parent::__construct();
        $this->isRemote = $this->config->is_remote;
        $this->accessToken = $this->getToken();
    }
 
    /**
     * 初始化数据
     */
    public function init($config) {
        $this->config = $config;
    }
 
    /**
     * 获取Appid
     */
    public function getAppid() {
        return $this->config['appid'];
    }
 
    /**
     * weixin signature
     */
    public function checkSignature($signature, $timestamp, $nonce) {
        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
 
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
 
    /**
     * web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function webOauth2($redirectUri, $state = [], $type = 'snsapi_base') {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = $type;
        if (empty($state['type'])) {
            $state['type'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * pc web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function pcWebOauth2($redirectUri, $state = []) {
        $params = [];
        $params['appid'] = $this->config['open_web_appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = 'snsapi_login';
        if (empty($state['env'])) {
            $state['env'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * get access token by code
     */
    public function getUserTokenByCode($code) {
        $params = array();
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $params['code'] = $code;
        $params['grant_type'] = 'authorization_code';
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * refresh access token
     * @param string $token 填写通过access_token获取到的refresh_token参数
     */
    public function refreshUserToken($token) {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['grant_type'] = 'refresh_token';
        $params['refresh_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 根据网页授权拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function getUserInfoByOauth($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('sns/userinfo', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 验证授权凭证(access_token)是否有效
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function validUserToken($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/auth?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 获取公众号token
     */
    public function getToken() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        if ($this->isRemote) {
            return $this->getTokenFromRemote();
        }
 
        $params = [];
        $params['grant_type'] = 'client_credential';
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $url = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        // 写入缓存
        $expiredAt = $response['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $response['access_token'], $expiredAt);
        return $response['access_token'];
    }
 
    /**
     * 获取access_token from remote
     */
    public function getTokenFromRemote() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/token/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $cache->save('wechat_access_token_expired', $resp['expired_at'], $expiredAt);
            $expiredAt = 120;
            $cache->save($key, $resp['access_token'], $expiredAt);
            return $resp['access_token'];
        } else {
            throw new Exception('wx admin get token error');
        }
    }
 
    /**
     * 获取用户基本信息(包括UnionID机制)
     * @param string $openid 用户的唯一标识
     * @param string $token 调用接口凭证,默认值为Wechat::getToken()
     */
    public function getUserInfo($openid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('cgi-bin/user/info', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 下载已经上传到微信的资源文件
     */
    public function downloadMedia($mediaid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['access_token'] = $token;
        $params['media_id'] = $mediaid;
        $url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $content = curl_exec($ch);
        $info = curl_getinfo($ch);
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            // 正常则分割header和body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($content, 0, $headerSize);
            $body = substr($content, $headerSize);
        }
        curl_close($ch);
        $resp['info'] = $info;
        $resp['header'] = $header;
        $resp['body'] = $body;
        return $resp;
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicket()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        if ($this->isRemote) {
            return $this->getJsapiTicketFromRemote();
        }
        $resp = json_decode($this->get('cgi-bin/ticket/getticket', ['access_token' => $this->accessToken, 'type' => 'jsapi']), true);
 
        // 写入缓存
        $expiredAt = $resp['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $resp['ticket'], $expiredAt);
        return $resp['ticket'];
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicketFromRemote()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/jsticket/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $expiredAt = 120;
            $cache->save($key, $resp['ticket'], $expiredAt);
            return $resp['ticket'];
        } else {
            throw new Exception('wx admin get ticket error');
        }
    }
    /**
     * 获取js api 签名
     */
    public function getSignPackage($url = '')
    {
        $jsticket = $this->getJsapiTicket();
 
        if (! $url) {
            $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
        }
        $timestamp = time();
        $nonceStr = $this->createNoncestr(16);
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket={$jsticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        return [
            "appId"     => $this->config['appid'],
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }
 
 
    /**
     * 新建一个类,由于PSR-4无法获取不以文件名命名的类;
     */
    public function newClass($name)
    {
        return new $name();
    }
 
    // 微信支付
 
    /**
     * 获取微信付款码的prepayid
     * @params array $params 参数
     */
    public function getPrepayId($params) {
        $sdk = new UnifiedOrderSDK;
        $sdk->setParameter("trade_type","JSAPI");//交易类型
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $prepayid = $sdk->getPrepayId();
        return $prepayid;
    }
 
    /**
     * 获取支付js api 签名
     */
    public function getPaySignPackage($prepayid)
    {
        $jsApiObj["appId"] = $this->config['appid'];
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->createNoncestr();
        $jsApiObj["package"] = "prepay_id=$prepayid";
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->getSign($jsApiObj);
        $signPackage = $jsApiObj;
 
        return $signPackage;
    }
 
    /**
     * 发送红包接口
     * @params array $params 参数
     */
    public function sendRedPack($params) {
        $sdk = new RedPackSDK;
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $result = $sdk->send();
        return $result;
    }
}
 
/**
 * 所有接口的基类
 */
class WechatBase
{
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => '',
        'appsecret' => '',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/cgi-bin/';
 
    public function __construct() {
        $this->di = Phalcon\DI::getDefault();
        $this->config = $this->di->getConfig()['wechat'];
    }
 
    protected function get($url, $params = '')
    {
        if (strpos($url, 'https://') === false && strpos($url, 'http://') === false) {
            $url = $this->apiPrefix.$url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl get error');
        }
    }
 
    protected function post($url, $params = '', $data = '')
    {
        if (strpos($url, 'https://') === false) {
            $url = $this->apiPrefix . $url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        if ($data) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl post error');
        }
    }
 
    public function trimString($value)
    {
        $ret = null;
        if (null != $value)
        {
            $ret = $value;
            if (strlen($ret) == 0)
            {
                $ret = null;
            }
        }
        return $ret;
    }
 
    /**
     *   作用:产生随机字符串,不长于32位
     */
    public function createNoncestr( $length = 32, $type = 'all')
    {
        if ($type == 'all') {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        } else if($type == 'number') {
            $chars = "0123456789";
        }
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
 
    /**
     *   作用:格式化参数,签名过程需要使用
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $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['key'];
        //echo "【string2】".$String."</br>";
        //签名步骤三:MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }
 
    /**
     *   作用:array转xml
     */
    public 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;
    }
 
    /**
     *   作用:将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;
    }
 
    /**
     *   作用:以post方式提交xml到对应的接口url
     */
    public function postXmlCurl($xml,$url,$second=30)
    {
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        //返回结果
        if($data)
        {
            return $data;
        }
        else
        {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            return false;
        }
    }
 
    /**
     *   作用:使用证书,以post方式提交xml到对应的接口url
     */
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, $this->config['sslcert_path']);
        //默认格式为PEM,可以注释
        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);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return false;
        }
    }
 
}
 
/**
 * 请求型接口的基类
 */
class WechatPayRequestBase extends WechatBase
{
    var $parameters;//请求参数,类型为关联数组
    public $response;//微信返回的响应
    public $result;//返回参数,类型为关联数组
    var $url;//接口链接
    var $curl_timeout;//curl超时时间
 
    /**
     *   作用:设置请求参数
     */
    function setParameter($parameter, $parameterValue)
    {
        $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
    }
 
    /**
     *   作用:设置标配的请求参数,生成签名,生成接口参数xml
     */
    function createXml()
    {
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     *   作用:post请求xml
     */
    function postXml()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:使用证书post请求xml
     */
    function postXmlSSL()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:获取结果,默认不使用证书
     */
    function getResult()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        return $this->result;
    }
}
 
/**
 * 统一支付接口类
 */
class UnifiedOrderSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        //检测必填参数
        if(empty($this->parameters["out_trade_no"]))
        {
            throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
        }elseif(empty($this->parameters["body"])){
            throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
        }elseif (empty($this->parameters["total_fee"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
        }elseif (empty($this->parameters["notify_url"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
        }elseif (empty($this->parameters["trade_type"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
        }elseif ($this->parameters["trade_type"] == "JSAPI" &&
            empty($this->parameters["openid"])){
            throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
        }
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["spbill_create_ip"] = $this->di->getRequest()->getClientAddress();//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 获取prepay_id
     */
    function getPrepayId()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        $prepay_id = $this->result["prepay_id"];
        return $prepay_id;
    }
 
}
 
/**
 * 响应型接口基类
 */
class WechatPayServerBase extends WechatBase
{
  public $data;//接收到的数据,类型为关联数组
  var $returnParameters;//返回参数,类型为关联数组
 
  /**
   * 将微信的请求xml转换成关联数组,以方便数据处理
   */
  function saveData($xml)
  {
    $this->data = $this->xmlToArray($xml);
  }
 
  function checkSign()
  {
    $tmpData = $this->data;
    unset($tmpData['sign']);
    $sign = $this->getSign($tmpData);//本地签名
        if (isLocal()) {
            echo $sign;
        }
    if ($this->data['sign'] == $sign) {
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * 获取微信的请求数据
   */
  function getData()
  {
    return $this->data;
  }
 
  /**
   * 设置返回微信的xml数据
   */
  function setReturnParameter($parameter, $parameterValue)
  {
    $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
  }
 
  /**
   * 生成接口参数xml
   */
  function createXml()
  {
    return $this->arrayToXml($this->returnParameters);
  }
 
  /**
   * 将xml数据返回微信
   */
  function returnXml()
  {
    $returnXml = $this->createXml();
    return $returnXml;
  }
}
 
/**
 * 红包发送接口类
 */
class RedPackSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        $requiredParams = [
            'mch_billno',
            'nick_name',
            'send_name',
            're_openid',
            'total_amount',
            'min_value',
            'max_value',
            'total_num',
            'wishing',
            'act_name',
            'remark',
        ];
        //检测必填参数
        foreach ($requiredParams as $val) {
            if (empty($this->parameters[$val])) {
                throw new SDKRuntimeException("缺少红包接口必填参数{$val}!"."<br>");
            }
        }
        $this->parameters["wxappid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["client_ip"] = gethostbyname(gethostname());//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 发送红包
     *
     * curl 返回例子
     *  <xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[发放成功.]]></return_msg>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <err_code><![CDATA[0]]></err_code>
        <err_code_des><![CDATA[发放成功.]]></err_code_des>
        <mch_billno><![CDATA[1228563102201503261427336509]]></mch_billno>
        <mch_id>1228563102</mch_id>
        <wxappid><![CDATA[wxdceccb9098a978b8]]></wxappid>
        <re_openid><![CDATA[o55e0t1NHPxdcRCjbIu_xnjHc2u8]]></re_openid>
        <total_amount>100</total_amount>
        </xml>
     */
    public function send() {
        $this->postXmlSSL();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        return $this->result;
    }
#2WechatSDK->getTokenFromRemote()
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (209)
<?php
 
/**
 * weixin library
 * @author geekzhumail@gmail.com
 * @since 2014-09-24
 * @edit 2015-03-05
 */
class SDKRuntimeException extends Exception {
 
    public function errorMessage() {
        return $this->getMessage();
    }
}
 
class WechatSDK extends WechatBase{
 
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => 'wxdf0a039cf4ea94d1',
        'appsecret' => '7100f0fe837a5287f075b6d2d9814ca0',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/';
    /**
     * @var access_token
     */
    protected $accessToken = '';
 
    /**
     * 是否从远程服务器拿token
     */
    protected $isRemote = false;
 
    public function __construct() {
        parent::__construct();
        $this->isRemote = $this->config->is_remote;
        $this->accessToken = $this->getToken();
    }
 
    /**
     * 初始化数据
     */
    public function init($config) {
        $this->config = $config;
    }
 
    /**
     * 获取Appid
     */
    public function getAppid() {
        return $this->config['appid'];
    }
 
    /**
     * weixin signature
     */
    public function checkSignature($signature, $timestamp, $nonce) {
        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
 
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
 
    /**
     * web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function webOauth2($redirectUri, $state = [], $type = 'snsapi_base') {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = $type;
        if (empty($state['type'])) {
            $state['type'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * pc web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function pcWebOauth2($redirectUri, $state = []) {
        $params = [];
        $params['appid'] = $this->config['open_web_appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = 'snsapi_login';
        if (empty($state['env'])) {
            $state['env'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * get access token by code
     */
    public function getUserTokenByCode($code) {
        $params = array();
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $params['code'] = $code;
        $params['grant_type'] = 'authorization_code';
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * refresh access token
     * @param string $token 填写通过access_token获取到的refresh_token参数
     */
    public function refreshUserToken($token) {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['grant_type'] = 'refresh_token';
        $params['refresh_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 根据网页授权拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function getUserInfoByOauth($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('sns/userinfo', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 验证授权凭证(access_token)是否有效
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function validUserToken($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/auth?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 获取公众号token
     */
    public function getToken() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        if ($this->isRemote) {
            return $this->getTokenFromRemote();
        }
 
        $params = [];
        $params['grant_type'] = 'client_credential';
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $url = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        // 写入缓存
        $expiredAt = $response['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $response['access_token'], $expiredAt);
        return $response['access_token'];
    }
 
    /**
     * 获取access_token from remote
     */
    public function getTokenFromRemote() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/token/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $cache->save('wechat_access_token_expired', $resp['expired_at'], $expiredAt);
            $expiredAt = 120;
            $cache->save($key, $resp['access_token'], $expiredAt);
            return $resp['access_token'];
        } else {
            throw new Exception('wx admin get token error');
        }
    }
 
    /**
     * 获取用户基本信息(包括UnionID机制)
     * @param string $openid 用户的唯一标识
     * @param string $token 调用接口凭证,默认值为Wechat::getToken()
     */
    public function getUserInfo($openid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('cgi-bin/user/info', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 下载已经上传到微信的资源文件
     */
    public function downloadMedia($mediaid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['access_token'] = $token;
        $params['media_id'] = $mediaid;
        $url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $content = curl_exec($ch);
        $info = curl_getinfo($ch);
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            // 正常则分割header和body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($content, 0, $headerSize);
            $body = substr($content, $headerSize);
        }
        curl_close($ch);
        $resp['info'] = $info;
        $resp['header'] = $header;
        $resp['body'] = $body;
        return $resp;
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicket()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        if ($this->isRemote) {
            return $this->getJsapiTicketFromRemote();
        }
        $resp = json_decode($this->get('cgi-bin/ticket/getticket', ['access_token' => $this->accessToken, 'type' => 'jsapi']), true);
 
        // 写入缓存
        $expiredAt = $resp['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $resp['ticket'], $expiredAt);
        return $resp['ticket'];
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicketFromRemote()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/jsticket/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $expiredAt = 120;
            $cache->save($key, $resp['ticket'], $expiredAt);
            return $resp['ticket'];
        } else {
            throw new Exception('wx admin get ticket error');
        }
    }
    /**
     * 获取js api 签名
     */
    public function getSignPackage($url = '')
    {
        $jsticket = $this->getJsapiTicket();
 
        if (! $url) {
            $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
        }
        $timestamp = time();
        $nonceStr = $this->createNoncestr(16);
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket={$jsticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        return [
            "appId"     => $this->config['appid'],
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }
 
 
    /**
     * 新建一个类,由于PSR-4无法获取不以文件名命名的类;
     */
    public function newClass($name)
    {
        return new $name();
    }
 
    // 微信支付
 
    /**
     * 获取微信付款码的prepayid
     * @params array $params 参数
     */
    public function getPrepayId($params) {
        $sdk = new UnifiedOrderSDK;
        $sdk->setParameter("trade_type","JSAPI");//交易类型
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $prepayid = $sdk->getPrepayId();
        return $prepayid;
    }
 
    /**
     * 获取支付js api 签名
     */
    public function getPaySignPackage($prepayid)
    {
        $jsApiObj["appId"] = $this->config['appid'];
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->createNoncestr();
        $jsApiObj["package"] = "prepay_id=$prepayid";
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->getSign($jsApiObj);
        $signPackage = $jsApiObj;
 
        return $signPackage;
    }
 
    /**
     * 发送红包接口
     * @params array $params 参数
     */
    public function sendRedPack($params) {
        $sdk = new RedPackSDK;
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $result = $sdk->send();
        return $result;
    }
}
 
/**
 * 所有接口的基类
 */
class WechatBase
{
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => '',
        'appsecret' => '',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/cgi-bin/';
 
    public function __construct() {
        $this->di = Phalcon\DI::getDefault();
        $this->config = $this->di->getConfig()['wechat'];
    }
 
    protected function get($url, $params = '')
    {
        if (strpos($url, 'https://') === false && strpos($url, 'http://') === false) {
            $url = $this->apiPrefix.$url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl get error');
        }
    }
 
    protected function post($url, $params = '', $data = '')
    {
        if (strpos($url, 'https://') === false) {
            $url = $this->apiPrefix . $url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        if ($data) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl post error');
        }
    }
 
    public function trimString($value)
    {
        $ret = null;
        if (null != $value)
        {
            $ret = $value;
            if (strlen($ret) == 0)
            {
                $ret = null;
            }
        }
        return $ret;
    }
 
    /**
     *   作用:产生随机字符串,不长于32位
     */
    public function createNoncestr( $length = 32, $type = 'all')
    {
        if ($type == 'all') {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        } else if($type == 'number') {
            $chars = "0123456789";
        }
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
 
    /**
     *   作用:格式化参数,签名过程需要使用
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $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['key'];
        //echo "【string2】".$String."</br>";
        //签名步骤三:MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }
 
    /**
     *   作用:array转xml
     */
    public 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;
    }
 
    /**
     *   作用:将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;
    }
 
    /**
     *   作用:以post方式提交xml到对应的接口url
     */
    public function postXmlCurl($xml,$url,$second=30)
    {
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        //返回结果
        if($data)
        {
            return $data;
        }
        else
        {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            return false;
        }
    }
 
    /**
     *   作用:使用证书,以post方式提交xml到对应的接口url
     */
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, $this->config['sslcert_path']);
        //默认格式为PEM,可以注释
        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);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return false;
        }
    }
 
}
 
/**
 * 请求型接口的基类
 */
class WechatPayRequestBase extends WechatBase
{
    var $parameters;//请求参数,类型为关联数组
    public $response;//微信返回的响应
    public $result;//返回参数,类型为关联数组
    var $url;//接口链接
    var $curl_timeout;//curl超时时间
 
    /**
     *   作用:设置请求参数
     */
    function setParameter($parameter, $parameterValue)
    {
        $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
    }
 
    /**
     *   作用:设置标配的请求参数,生成签名,生成接口参数xml
     */
    function createXml()
    {
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     *   作用:post请求xml
     */
    function postXml()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:使用证书post请求xml
     */
    function postXmlSSL()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:获取结果,默认不使用证书
     */
    function getResult()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        return $this->result;
    }
}
 
/**
 * 统一支付接口类
 */
class UnifiedOrderSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        //检测必填参数
        if(empty($this->parameters["out_trade_no"]))
        {
            throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
        }elseif(empty($this->parameters["body"])){
            throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
        }elseif (empty($this->parameters["total_fee"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
        }elseif (empty($this->parameters["notify_url"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
        }elseif (empty($this->parameters["trade_type"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
        }elseif ($this->parameters["trade_type"] == "JSAPI" &&
            empty($this->parameters["openid"])){
            throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
        }
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["spbill_create_ip"] = $this->di->getRequest()->getClientAddress();//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 获取prepay_id
     */
    function getPrepayId()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        $prepay_id = $this->result["prepay_id"];
        return $prepay_id;
    }
 
}
 
/**
 * 响应型接口基类
 */
class WechatPayServerBase extends WechatBase
{
  public $data;//接收到的数据,类型为关联数组
  var $returnParameters;//返回参数,类型为关联数组
 
  /**
   * 将微信的请求xml转换成关联数组,以方便数据处理
   */
  function saveData($xml)
  {
    $this->data = $this->xmlToArray($xml);
  }
 
  function checkSign()
  {
    $tmpData = $this->data;
    unset($tmpData['sign']);
    $sign = $this->getSign($tmpData);//本地签名
        if (isLocal()) {
            echo $sign;
        }
    if ($this->data['sign'] == $sign) {
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * 获取微信的请求数据
   */
  function getData()
  {
    return $this->data;
  }
 
  /**
   * 设置返回微信的xml数据
   */
  function setReturnParameter($parameter, $parameterValue)
  {
    $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
  }
 
  /**
   * 生成接口参数xml
   */
  function createXml()
  {
    return $this->arrayToXml($this->returnParameters);
  }
 
  /**
   * 将xml数据返回微信
   */
  function returnXml()
  {
    $returnXml = $this->createXml();
    return $returnXml;
  }
}
 
/**
 * 红包发送接口类
 */
class RedPackSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        $requiredParams = [
            'mch_billno',
            'nick_name',
            'send_name',
            're_openid',
            'total_amount',
            'min_value',
            'max_value',
            'total_num',
            'wishing',
            'act_name',
            'remark',
        ];
        //检测必填参数
        foreach ($requiredParams as $val) {
            if (empty($this->parameters[$val])) {
                throw new SDKRuntimeException("缺少红包接口必填参数{$val}!"."<br>");
            }
        }
        $this->parameters["wxappid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["client_ip"] = gethostbyname(gethostname());//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 发送红包
     *
     * curl 返回例子
     *  <xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[发放成功.]]></return_msg>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <err_code><![CDATA[0]]></err_code>
        <err_code_des><![CDATA[发放成功.]]></err_code_des>
        <mch_billno><![CDATA[1228563102201503261427336509]]></mch_billno>
        <mch_id>1228563102</mch_id>
        <wxappid><![CDATA[wxdceccb9098a978b8]]></wxappid>
        <re_openid><![CDATA[o55e0t1NHPxdcRCjbIu_xnjHc2u8]]></re_openid>
        <total_amount>100</total_amount>
        </xml>
     */
    public function send() {
        $this->postXmlSSL();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        return $this->result;
    }
#3WechatSDK->getToken()
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (45)
<?php
 
/**
 * weixin library
 * @author geekzhumail@gmail.com
 * @since 2014-09-24
 * @edit 2015-03-05
 */
class SDKRuntimeException extends Exception {
 
    public function errorMessage() {
        return $this->getMessage();
    }
}
 
class WechatSDK extends WechatBase{
 
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => 'wxdf0a039cf4ea94d1',
        'appsecret' => '7100f0fe837a5287f075b6d2d9814ca0',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/';
    /**
     * @var access_token
     */
    protected $accessToken = '';
 
    /**
     * 是否从远程服务器拿token
     */
    protected $isRemote = false;
 
    public function __construct() {
        parent::__construct();
        $this->isRemote = $this->config->is_remote;
        $this->accessToken = $this->getToken();
    }
 
    /**
     * 初始化数据
     */
    public function init($config) {
        $this->config = $config;
    }
 
    /**
     * 获取Appid
     */
    public function getAppid() {
        return $this->config['appid'];
    }
 
    /**
     * weixin signature
     */
    public function checkSignature($signature, $timestamp, $nonce) {
        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
 
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
 
    /**
     * web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function webOauth2($redirectUri, $state = [], $type = 'snsapi_base') {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = $type;
        if (empty($state['type'])) {
            $state['type'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * pc web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function pcWebOauth2($redirectUri, $state = []) {
        $params = [];
        $params['appid'] = $this->config['open_web_appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = 'snsapi_login';
        if (empty($state['env'])) {
            $state['env'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * get access token by code
     */
    public function getUserTokenByCode($code) {
        $params = array();
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $params['code'] = $code;
        $params['grant_type'] = 'authorization_code';
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * refresh access token
     * @param string $token 填写通过access_token获取到的refresh_token参数
     */
    public function refreshUserToken($token) {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['grant_type'] = 'refresh_token';
        $params['refresh_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 根据网页授权拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function getUserInfoByOauth($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('sns/userinfo', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 验证授权凭证(access_token)是否有效
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function validUserToken($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/auth?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 获取公众号token
     */
    public function getToken() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        if ($this->isRemote) {
            return $this->getTokenFromRemote();
        }
 
        $params = [];
        $params['grant_type'] = 'client_credential';
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $url = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        // 写入缓存
        $expiredAt = $response['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $response['access_token'], $expiredAt);
        return $response['access_token'];
    }
 
    /**
     * 获取access_token from remote
     */
    public function getTokenFromRemote() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/token/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $cache->save('wechat_access_token_expired', $resp['expired_at'], $expiredAt);
            $expiredAt = 120;
            $cache->save($key, $resp['access_token'], $expiredAt);
            return $resp['access_token'];
        } else {
            throw new Exception('wx admin get token error');
        }
    }
 
    /**
     * 获取用户基本信息(包括UnionID机制)
     * @param string $openid 用户的唯一标识
     * @param string $token 调用接口凭证,默认值为Wechat::getToken()
     */
    public function getUserInfo($openid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('cgi-bin/user/info', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 下载已经上传到微信的资源文件
     */
    public function downloadMedia($mediaid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['access_token'] = $token;
        $params['media_id'] = $mediaid;
        $url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $content = curl_exec($ch);
        $info = curl_getinfo($ch);
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            // 正常则分割header和body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($content, 0, $headerSize);
            $body = substr($content, $headerSize);
        }
        curl_close($ch);
        $resp['info'] = $info;
        $resp['header'] = $header;
        $resp['body'] = $body;
        return $resp;
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicket()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        if ($this->isRemote) {
            return $this->getJsapiTicketFromRemote();
        }
        $resp = json_decode($this->get('cgi-bin/ticket/getticket', ['access_token' => $this->accessToken, 'type' => 'jsapi']), true);
 
        // 写入缓存
        $expiredAt = $resp['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $resp['ticket'], $expiredAt);
        return $resp['ticket'];
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicketFromRemote()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/jsticket/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $expiredAt = 120;
            $cache->save($key, $resp['ticket'], $expiredAt);
            return $resp['ticket'];
        } else {
            throw new Exception('wx admin get ticket error');
        }
    }
    /**
     * 获取js api 签名
     */
    public function getSignPackage($url = '')
    {
        $jsticket = $this->getJsapiTicket();
 
        if (! $url) {
            $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
        }
        $timestamp = time();
        $nonceStr = $this->createNoncestr(16);
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket={$jsticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        return [
            "appId"     => $this->config['appid'],
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }
 
 
    /**
     * 新建一个类,由于PSR-4无法获取不以文件名命名的类;
     */
    public function newClass($name)
    {
        return new $name();
    }
 
    // 微信支付
 
    /**
     * 获取微信付款码的prepayid
     * @params array $params 参数
     */
    public function getPrepayId($params) {
        $sdk = new UnifiedOrderSDK;
        $sdk->setParameter("trade_type","JSAPI");//交易类型
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $prepayid = $sdk->getPrepayId();
        return $prepayid;
    }
 
    /**
     * 获取支付js api 签名
     */
    public function getPaySignPackage($prepayid)
    {
        $jsApiObj["appId"] = $this->config['appid'];
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->createNoncestr();
        $jsApiObj["package"] = "prepay_id=$prepayid";
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->getSign($jsApiObj);
        $signPackage = $jsApiObj;
 
        return $signPackage;
    }
 
    /**
     * 发送红包接口
     * @params array $params 参数
     */
    public function sendRedPack($params) {
        $sdk = new RedPackSDK;
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $result = $sdk->send();
        return $result;
    }
}
 
/**
 * 所有接口的基类
 */
class WechatBase
{
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => '',
        'appsecret' => '',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/cgi-bin/';
 
    public function __construct() {
        $this->di = Phalcon\DI::getDefault();
        $this->config = $this->di->getConfig()['wechat'];
    }
 
    protected function get($url, $params = '')
    {
        if (strpos($url, 'https://') === false && strpos($url, 'http://') === false) {
            $url = $this->apiPrefix.$url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl get error');
        }
    }
 
    protected function post($url, $params = '', $data = '')
    {
        if (strpos($url, 'https://') === false) {
            $url = $this->apiPrefix . $url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        if ($data) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl post error');
        }
    }
 
    public function trimString($value)
    {
        $ret = null;
        if (null != $value)
        {
            $ret = $value;
            if (strlen($ret) == 0)
            {
                $ret = null;
            }
        }
        return $ret;
    }
 
    /**
     *   作用:产生随机字符串,不长于32位
     */
    public function createNoncestr( $length = 32, $type = 'all')
    {
        if ($type == 'all') {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        } else if($type == 'number') {
            $chars = "0123456789";
        }
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
 
    /**
     *   作用:格式化参数,签名过程需要使用
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $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['key'];
        //echo "【string2】".$String."</br>";
        //签名步骤三:MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }
 
    /**
     *   作用:array转xml
     */
    public 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;
    }
 
    /**
     *   作用:将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;
    }
 
    /**
     *   作用:以post方式提交xml到对应的接口url
     */
    public function postXmlCurl($xml,$url,$second=30)
    {
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        //返回结果
        if($data)
        {
            return $data;
        }
        else
        {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            return false;
        }
    }
 
    /**
     *   作用:使用证书,以post方式提交xml到对应的接口url
     */
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, $this->config['sslcert_path']);
        //默认格式为PEM,可以注释
        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);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return false;
        }
    }
 
}
 
/**
 * 请求型接口的基类
 */
class WechatPayRequestBase extends WechatBase
{
    var $parameters;//请求参数,类型为关联数组
    public $response;//微信返回的响应
    public $result;//返回参数,类型为关联数组
    var $url;//接口链接
    var $curl_timeout;//curl超时时间
 
    /**
     *   作用:设置请求参数
     */
    function setParameter($parameter, $parameterValue)
    {
        $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
    }
 
    /**
     *   作用:设置标配的请求参数,生成签名,生成接口参数xml
     */
    function createXml()
    {
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     *   作用:post请求xml
     */
    function postXml()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:使用证书post请求xml
     */
    function postXmlSSL()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:获取结果,默认不使用证书
     */
    function getResult()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        return $this->result;
    }
}
 
/**
 * 统一支付接口类
 */
class UnifiedOrderSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        //检测必填参数
        if(empty($this->parameters["out_trade_no"]))
        {
            throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
        }elseif(empty($this->parameters["body"])){
            throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
        }elseif (empty($this->parameters["total_fee"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
        }elseif (empty($this->parameters["notify_url"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
        }elseif (empty($this->parameters["trade_type"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
        }elseif ($this->parameters["trade_type"] == "JSAPI" &&
            empty($this->parameters["openid"])){
            throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
        }
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["spbill_create_ip"] = $this->di->getRequest()->getClientAddress();//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 获取prepay_id
     */
    function getPrepayId()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        $prepay_id = $this->result["prepay_id"];
        return $prepay_id;
    }
 
}
 
/**
 * 响应型接口基类
 */
class WechatPayServerBase extends WechatBase
{
  public $data;//接收到的数据,类型为关联数组
  var $returnParameters;//返回参数,类型为关联数组
 
  /**
   * 将微信的请求xml转换成关联数组,以方便数据处理
   */
  function saveData($xml)
  {
    $this->data = $this->xmlToArray($xml);
  }
 
  function checkSign()
  {
    $tmpData = $this->data;
    unset($tmpData['sign']);
    $sign = $this->getSign($tmpData);//本地签名
        if (isLocal()) {
            echo $sign;
        }
    if ($this->data['sign'] == $sign) {
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * 获取微信的请求数据
   */
  function getData()
  {
    return $this->data;
  }
 
  /**
   * 设置返回微信的xml数据
   */
  function setReturnParameter($parameter, $parameterValue)
  {
    $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
  }
 
  /**
   * 生成接口参数xml
   */
  function createXml()
  {
    return $this->arrayToXml($this->returnParameters);
  }
 
  /**
   * 将xml数据返回微信
   */
  function returnXml()
  {
    $returnXml = $this->createXml();
    return $returnXml;
  }
}
 
/**
 * 红包发送接口类
 */
class RedPackSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        $requiredParams = [
            'mch_billno',
            'nick_name',
            'send_name',
            're_openid',
            'total_amount',
            'min_value',
            'max_value',
            'total_num',
            'wishing',
            'act_name',
            'remark',
        ];
        //检测必填参数
        foreach ($requiredParams as $val) {
            if (empty($this->parameters[$val])) {
                throw new SDKRuntimeException("缺少红包接口必填参数{$val}!"."<br>");
            }
        }
        $this->parameters["wxappid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["client_ip"] = gethostbyname(gethostname());//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 发送红包
     *
     * curl 返回例子
     *  <xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[发放成功.]]></return_msg>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <err_code><![CDATA[0]]></err_code>
        <err_code_des><![CDATA[发放成功.]]></err_code_des>
        <mch_billno><![CDATA[1228563102201503261427336509]]></mch_billno>
        <mch_id>1228563102</mch_id>
        <wxappid><![CDATA[wxdceccb9098a978b8]]></wxappid>
        <re_openid><![CDATA[o55e0t1NHPxdcRCjbIu_xnjHc2u8]]></re_openid>
        <total_amount>100</total_amount>
        </xml>
     */
    public function send() {
        $this->postXmlSSL();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        return $this->result;
    }
#4WechatSDK->__construct()
/usr/local/www/liowang-shop-robotwar/shop/app/controllers/PageUserController.php (24)
<?php
 
use HomeSpace\Model;
 
class PageUserController extends PageBaseController {
 
    /**
     * 登录
     */
    public function loginAction()
    {
        $data = [];
        //echo '登录';
        $act = $this->request->getQuery('act');
        if ($act == 'not_active') {
            if (empty($_SESSION['tmp_email'])) {
                $act = '';
            }
            $data['email'] = $_SESSION['tmp_email'];
        }
        $data['act'] = $act;
        $data['errmsg'] = $this->flash->getMessages('error');
        $this->flash->clear();
        $wechat = new WechatSDK;
        //$url = $this->url->get('wechat/oauth2/callback');
        $url = 'http://www.liowang.com/shop/wechat/oauth2/callback';
        $state = ['env' => 'product'];
        if (isLocal()) {
            $state = ['env' => 'local'];
        }
        $data['wechat_login_url'] = $wechat->pcWebOauth2($url, $state);
        echo $this->viewoutput('pc/login', $data);
    }
 
    /**
     * 登录 POST
     */
    public function postLoginAction()
    {
        global $user, $_LANG;
        $errmsg = '';
        $account = $this->request->getPost('account');
        $password = $this->request->getPost('password');
        $remember = ! empty($this->request->getQuery('remember'));
        if (filter_var($account, FILTER_VALIDATE_EMAIL)) {
            // 邮箱登录
            $muser = Model\User::findFirst(['columns' => 'user_name', 'conditions' => "email = '$account'"]);
        } else if(is_numeric($account) && strlen($account) == 11) {
            // 手机登录
            $muser = Model\User::findFirst(['columns' => 'user_name', 'conditions' => "mobile_phone = '$account'"]);
        }
        if (! empty($muser) && ! empty($password)) {
            if ($user->login($muser['user_name'], $password, $remember)) {
                $row = Model\User::findFirstByUserId($_SESSION['user_id']);
                if ($row['is_validated'] == 1) {
                    update_user_info();
                    recalculate_price();
 
                    $this->auth->loginUsingId($_SESSION['user_id']);
                    $_SESSION['pc_login'] = true;
                    return $this->response->redirect('home/index');
                } else {
                    //$_SESSION['login_fail'] ++ ;
                    //$errmsg = '该账号尚未激活请查阅该账号邮箱的激活邮件,或者联系客服';
                    $_SESSION['tmp_email'] = $row['email'];
                    return $this->response->redirect('login?act=not_active');
                }
            } else {
                $_SESSION['login_fail'] ++ ;
                $errmsg = $_LANG['login_failure'];
            }
        }
        $errmsg = $errmsg ?: '账号或者密码错误';
        $this->flash->error($errmsg);
        return $this->response->redirect('login');
    }
 
    /**
     * 退出登录
     */
    public function logoutAction()
    {
        global $user;
        $user->logout();
        return $this->response->redirect($this->request->getHTTPReferer(), true);
    }
 
    /**
     * 邮箱注册验证
     */
    public function emailValidateAction()
    {
        include_once(ROOT_PATH . 'includes/lib_passport.php');
        $data = [];
        $data['status'] = 'user_not_exist';
        $hash = $this->request->getQuery('hash');
        if ($hash) {
            $id = intval(register_hash('decode', $hash));
            $user = Model\User::findFirstByUserId($id);
            if ($user) {
                if ($user['is_validated'] == 0) {
                    Model\User::update(['is_validated' => 1], ['user_id' => $id]);
                    $data['status'] = 'success';
                } else {
                    $data['status'] = 'validated';
                }
                $data['user'] = $user;
            }
        }
        echo $this->viewoutput('pc/email-validate', $data);
    }
 
    /**
     * PC 微信回调 oauth2 函数
     */
    public function wechatOauth2CallbackAction()
    {
        global $ecs, $db, $user;
        include_once(ROOT_PATH . 'includes/lib_passport.php');
        $code = $this->request->getQuery('code');
        if (empty($code)) {
            echo '授权失败';
            exit;
        }
        $state = $this->request->getQuery('state');
        parse_str($state, $params);
        if (! islocal() && ! empty($params['env']) && $params['env'] == 'local') {
            return $this->response->redirect('http://liowang.dev.seafarer.me/shop/wechat/oauth2/callback?' . http_build_query($this->request->getQuery()));
        }
 
        $wechat = new WechatSDK;
        $config = $this->config->wechat;
        $config->appid = $config->open_web_appid;
        $config->appsecret = $config->open_web_secret;
        $wechat->init($config);
        $response = $wechat->getUserTokenByCode($code);
        if (empty($response['errcode'])) {
            // 授权成功
            $ewxpcuser = Model\WxPcUser::findFirstByOpenid($response['openid']);
 
            $data = [];
            $isFirst = false;
            $isNeedCreate = false;
            $uuser = [];
            $wxpcuser = [];
            if ($ewxpcuser) {
                // 用户已经存在
                $euser = Model\User::findFirstByWxPcUserid($ewxpcuser['id']);
            } else {
                // 第一次访问
                $isFirst = true;
                $wxuser = Model\WxUser::findFirstByUnionid($response['unionid']);
                if (empty($wxuser)) {
                    $isNeedCreate = true;
                } else {
                    $euser = Model\User::findFirstByWxUserid($wxuser['id']);
                }
            }
            $wxpcuser['openid'] = $response['openid'];
            $wxpcuser['unionid'] = $response['unionid'];
            $wxpcuser['access_token'] = $response['access_token'];
            $wxpcuser['refresh_token'] = $response['refresh_token'];
            $wxpcuser['expired_at'] = $response['expires_in'] + time() - 200;
            $wxpcuser['scope'] = $response['scope'];
            $wxpcuser['ip'] = $this->request->getClientAddress();
            $info = $wechat->getUserInfoByOauth($response['openid'], $response['access_token']);
            $wxpcuser['nickname'] = $info['nickname'];
            $wxpcuser['gender'] = $info['sex'];
            $wxpcuser['language'] = $info['language'];
            $wxpcuser['city'] = $info['city'];
            $wxpcuser['country'] = $info['country'];
            $wxpcuser['province'] = $info['province'];
            $wxpcuser['headimgurl'] = $info['headimgurl'];
 
            if ($isFirst) {
                // 首次PC扫码登录
                $id = Model\WxPcUser::insert($wxpcuser);
                $_SESSION['wx_pc_user_id'] = $id;
                $uuser['wx_pc_userid'] = $id;
                if (! $isNeedCreate) {
                    Model\User::update($uuser, "user_id = {$euser['user_id']}");
                }
                /**
                if ($isNeedCreate) {
                    // 创建新用户
                    $uuser['avatar'] = 'shop/assets/pc/IMGS/defaultPhoto.png';
                    $name = '会员_' . time() . '_' . $id;
                    $uuser['nickname'] = $wxpcuser['nickname'];
                    $email = 'email_' . time() . '_' . $id . '@liowang.com';
                    if (register($name, '123123', $email) !== false) {;
                        $db->autoExecute($ecs->table('users'), $uuser, 'UPDATE', "user_id = {$_SESSION['user_id']}");
                    } else {
                        SystemException::error('授权失败');
                    }
                } else {
                    // 绑定已在微信授权的用户
                    Model\User::update($uuser, "user_id = {$euser['user_id']}");
                    Model\WxPcUser::update($wxpcuser, "openid = '{$response['openid']}'");
                    $user->set_session($euser['user_name']);
                    $user->set_cookie($euser['user_name']);
                    update_user_info();
                    recalculate_price();
                }**/
            } else {
                Model\WxPcUser::update($wxpcuser, "openid = '{$response['openid']}'");
                $_SESSION['wx_pc_user_id'] = $ewxpcuser['id'];
            }
            if (empty($euser)) {
                return $this->response->redirect('wechat/oauth2/register');
            }
            // 已注册直接登录
            $user->set_session($euser['user_name']);
            $user->set_cookie($euser['user_name']);
            update_user_info();
            recalculate_price();
            $this->auth->loginUsingId($_SESSION['user_id']);
            $_SESSION['pc_login'] = true;
        } else {
            SystemException::error($response['errmsg']);
        }
        $referer = $this->url->get('');
        return $this->response->redirect($referer, true);
    }
 
    /**
     * PC 微信账号登录绑定
     */
    public function wechatOauth2RegisterAction()
    {
        global $ecs, $db, $user;
 
        if (empty($_SESSION['wx_pc_user_id'])) {
            return $this->response->redirect('login');
        }
        $data = [];
        echo $this->viewoutput('pc/wechat-oauth2-register', $data);
    }
 
    /******** 需要登录授权 *********/
    /**
     * 个人信息
     */
    public function infoAction()
    {
        $user = $this->auth->user();
 
        list($user['birYear'], $user['birMonth'], $user['birDay']) = explode('-', $user['birthday']);
        $user['birMonth'] = ltrim($user['birMonth'], 0);
        $user['birDay'] = ltrim($user['birDay'], 0);
        if (strpos($user['avatar'], 'http://') === false && strpos($user['avatar'], 'https://') === false) {
            $user['avatar'] = 'http://' . $this->request->getHttpHost() . '/' . $user['avatar'];
        }
        $data = [];
        $data['user'] = $user;
        $data['navName'] = '';
        $data['userSidebar'] = 'user-info';
        echo $this->viewoutput('pc/user-info', $data);
    }
    /**
     * 购物车
     */
    public function cartAction()
    {
        require(ROOT_PATH . 'includes/lib_order.php');
        $user = $this->auth->user();
        $mcart = new Model\Cart;
        $cartCount = $mcart->getCount();
        $cartGoods = get_cart_goods();
        $discount = compute_discount();
 
        $goodsids = [];
        foreach ($cartGoods['goods_list'] as $goods) {
            $goodsids[] = $goods['goods_id'];
        }
        if ($goodsids) {
            $collectGoodsList = Model\CollectGoods::find(['conditions' => "user_id='$_SESSION[user_id]' AND " . db_create_in($goodsids, 'goods_id')]);
            foreach ($cartGoods['goods_list'] as &$goods) {
                $goods['is_collect'] = false;
                foreach ($collectGoodsList as $row) {
                    if ($row['goods_id'] == $goods['goods_id']) {
                        $goods['is_collect'] = true;
                        break;
                    }
                }
            }
            unset($goods);
        }
 
        $data = [];
        $data['user'] = $user;
        $data['cartGoods'] = $cartGoods;
        $data['navName'] = '';
        echo $this->viewoutput('pc/user-cart', $data);
    }
    /**
     * 购物车结算
     */
    public function cartSettleAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        require(ROOT_PATH . 'includes/lib_order.php');
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
        $user = $this->auth->user();
 
        $recids = $this->request->getQuery('rec_ids') ?: [];
 
        $cartGoods = get_cart_goods($recids);
        if (empty($cartGoods['goods_list'])) {
            // 购物车为空
            return $this->response->redirect('user/cart');
        }
        $discount = compute_discount();
 
        $data = [];
 
        $address_id = intval($this->request->getQuery('address_id'));
        if ($address_id == 0) {
            $address_id = $user['address_id'];
        }
        $address = Model\UserAddress::findFirst(['conditions' => "address_id = $address_id AND user_id = {$user['user_id']}"]);
        $addressList = Model\UserAddress::find(['conditions' => "address_id != $address_id AND user_id = {$user['user_id']}", 'order' => 'address_id DESC']);
        if ($address) {
            array_unshift($addressList, $address);
        }
        $regions = Model\Region::getAll();
        foreach ($addressList as &$row) {
            $country = $regions[$row['country']];
            $province = $country['list'][$row['province']];
            $city = $province['list'][$row['city']];
            $district = $city['list'][$row['district']];
            $row['address_detail'] = $province['region_name'] . $city['region_name'] . $district['region_name'] . $row['address'];
        }
        unset($row);
        $data['addressList'] = $addressList;
        foreach ($cartGoods['goods_list'] as &$goods) {
            //$goods['goods_name'] = mb_substr($goods['goods_name'], 0, 15, 'utf-8') . '......';
        }
        unset($goods);
 
        $regions = Model\Region::getAll();
 
        $data['user'] = $user;
        $data['regions'] = $regions;
        $data['cartGoods'] = $cartGoods;
        $data['navName'] = '';
        echo $this->viewoutput('pc/cart-settle', $data);
    }
 
    /**
     * 已购买的商品
     */
    public function boughtListAction()
    {
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'includes/lib_order.php');
        $user = $this->auth->user();
        $mcart = new Model\Cart;
 
        global $db, $ecs;
 
        $page = intval($this->request->getQuery('page')) ?: 1;
        $limit = 10;
        $offset = ($page - 1) * $limit;
        $userid = $_SESSION['user_id'];
        $morder = new Model\OrderInfo;
 
        $arr    = array();
        $osArr = [];
        $ssArr = [];
        $psArr = [];
        // 需要评论的order_id
        $result = $morder->needComment();
        $needCommentOrderids = [];
        foreach ($result as $row) {
            $needCommentOrderids[$row['order_id']] = $row['order_id'];
        }
 
        $osArr[] = OS_CONFIRMED;
        $osArr[] = OS_SPLITED;
        $ssArr[] = SS_RECEIVED;
        $psArr[] = PS_PAYED;
        $ossql = $sssql = $pssql = '';
        if ($osArr) {
            $ossql = ' AND ' . db_create_in($osArr, 'order_status');
        }
        if ($ssArr) {
            $sssql = ' AND ' . db_create_in($ssArr, 'shipping_status');
        }
        if ($psArr) {
            $pssql = ' AND ' . db_create_in($psArr, 'pay_status');
        }
        $sql = "SELECT order_id, order_sn, order_status, shipping_status, pay_status, add_time, " .
               "(goods_amount + shipping_fee + insure_fee + pay_fee + pack_fee + card_fee + tax - discount) AS total_fee ".
               " FROM " .$GLOBALS['ecs']->table('order_info') .
               ' WHERE 1 = 1';
        $sql .= " AND user_id = '$userid' $ossql $sssql $pssql ORDER BY add_time DESC";
        //$res = $GLOBALS['db']->SelectLimit($sql, 20, ($page - 1) * 20);
        $res = $GLOBALS['db']->query($sql);
 
        $orderids = [];
        while ($row = $GLOBALS['db']->fetchRow($res))
        {
            $orderids[] = $row['order_id'];
            $row['shipping_status'] = ($row['shipping_status'] == SS_SHIPPED_ING) ? SS_PREPARING : $row['shipping_status'];
 
            $arr[$row['order_id']] = array('order_id'       => $row['order_id'],
                           'order_sn'       => $row['order_sn'],
                           'order_time'     => local_date($GLOBALS['_CFG']['time_format'], $row['add_time']),
                           'order_status'   => $row['order_status'],
                           'shipping_status'   => $row['shipping_status'],
                           'pay_status'   => $row['pay_status'],
                           'total_fee'      => price_format($row['total_fee'], false),
                           'needComment' => isset($needCommentOrderids[$row['order_id']]) ? true : false,
                           'goods' => [],
                    );
        }
        $orders = $arr;
        $count = 0;
        if ($orderids) {
            $orderidsStr = implode(',', $orderids);
 
            /* 订单商品 */
            $params = [
                'alias' => 'og',
                'columns' => "og.rec_id, og.order_id, og.goods_id, og.goods_name, og.goods_sn, og.market_price, og.goods_number, " .
                    "og.goods_price, og.goods_attr, og.is_real, og.parent_id, og.is_gift, " .
                    "og.goods_price * og.goods_number AS subtotal, og.extension_code, g.goods_thumb, g.goods_img " .
                    ',c.comment_id ',
                'conditions' =>  "og.order_id IN ($orderidsStr)",
                'offset' => $offset,
                'limit' => $limit,
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = og.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'comment',
                        'alias' => 'c',
                        'on' => 'c.id_value = og.goods_id AND c.order_id = og.order_id '
                    ]
                ],
            ];
            $colls = Model\OrderGoods::find($params);
            unset($params['limit'], $params['offset']);
            $count = Model\OrderGoods::count($params);
            foreach ($colls as $row) {
                if (isset($orders[$row['order_id']])) {
                    $row['market_price'] = price_format($row['market_price'], false);
                    $row['goods_price']  = price_format($row['goods_price'], false);
                    $row['subtotal']     = price_format($row['subtotal'], false);
                    $orders[$row['order_id']]['goods'][] = $row;
                }
            }
        }
 
        $totalPage = $count ? intval(($count - 1) / $limit) + 1 : 1;
 
        $data = [];
        $data['user'] = $user;
        $data['page'] = $page;
        $data['totalPage'] = $totalPage;
        $data['count'] = $count;
        $data['orders'] = $orders;
        $data['navName'] = '';
        $data['userSidebar'] = 'bought-list';
        echo $this->viewoutput('pc/bought-list', $data);
    }
 
    /**
     * 我的收藏
     */
    public function collectListAction()
    {
        global $db, $ecs;
        $user = $this->auth->user();
        $page = intval($this->request->getQuery('page')) ?: 1;
        $ctype = $this->request->getQuery('collect_type') == 'brand' ? 'brand' : 'goods';
        $limit = 10;
        $offset = ($page - 1) * $limit;
        $userid = $user['user_id'];
 
        $data = [];
        if ($ctype == 'goods') {
            $params = [
                'alias' => 'c',
                'columns' => 'g.goods_id, g.goods_name, g.market_price, g.shop_price AS org_price, '.
                    "IFNULL(mp.user_price, g.shop_price * '$_SESSION[discount]') AS shop_price, g.goods_img, ".
                    'g.promote_price, g.promote_start_date,g.promote_end_date, c.rec_id, c.is_attention',
                'conditions' =>  "c.user_id = '$userid' ORDER BY c.rec_id DESC",
                'offset' => $offset,
                'limit' => $limit,
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = c.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'member_price',
                        'alias' => 'mp',
                        'on' => "mp.goods_id = g.goods_id AND mp.user_rank = '$_SESSION[user_rank]' "
                    ],
                ],
            ];
            $goodsList = Model\CollectGoods::find($params);
            foreach ($goodsList as &$row) {
                if ($row['promote_price'] > 0) {
                    $promote_price = bargain_price($row['promote_price'], $row['promote_start_date'], $row['promote_end_date']);
                    $row['promote_price'] = $promote_price > 0 ? price_format($promote_price) : '';
                } else {
                    $row['promote_price'] = '';
                }
                if ($row['promote_price'] > 0) {
                    $row['price'] = $row['promote_price'];
                } else {
                    $row['price'] = $row['shop_price'];
                }
            }
            unset($row);
            unset($params['limit'], $params['offset']);
            $count = Model\CollectGoods::count($params);
 
            $data['list'] = $goodsList;
        } else {
            $params = [
                'alias' => 'cb',
                'columns' => 'b.*',
                'conditions' =>  "cb.user_id = '$userid'",
                'offset' => $offset,
                'limit' => $limit,
                'order' => 'cb.id DESC',
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'brand',
                        'alias' => 'b',
                        'on' => 'b.brand_id = cb.brand_id'
                    ]
                ],
            ];
            $brandList = Model\CollectBrand::find($params);
            foreach ($brandList as &$row) {
 
            }
            unset($row);
            unset($params['limit'], $params['offset']);
            $count = Model\CollectBrand::count($params);
 
            $data['list'] = $brandList;
        }
        $paginator = new \Juice\Paginator(['type' => 'emptyArray', 'config' => [
            'count' => $count,
            'limit' => $limit,
            'page' => $page,
        ]]);
        $paginate = $paginator->getPaginate();
 
        $data['user'] = $user;
        $data['ctype'] = $ctype;
        $data['paginate'] = $paginate;
        $data['navName'] = '';
        $data['userSidebar'] = 'collect-list';
        echo $this->viewoutput('pc/collect-list', $data);
    }
    /**
     * 评论商品
     */
    public function commentAction()
    {
        global $db, $ecs;
        $orderid = intval($this->request->getQuery('order_id'));
        $goodsid = intval($this->request->getQuery('goods_id'));
        if (! $orderid || ! $goodsid) {
            return $this->response->redirect('user/bought/list');
        }
        $user = $this->auth->user();
        $goods = Model\OrderGoods::findFirst([
            'alias' => 'og',
            'columns' => "og.rec_id, og.goods_id, og.goods_name, og.goods_attr, og.goods_sn, og.goods_number, goods_price, g.goods_thumb, o.add_time, o.order_id",
            'conditions' =>  "og.order_id = $orderid AND og.goods_id = $goodsid AND o.user_id = " . $user['user_id'] . ' AND o.shipping_status = 2 AND c.comment_id IS NULL',
            'join' => [
                [
                    'type' => 'INNER',
                    'table' => 'order_info',
                    'alias' => 'o',
                    'on' => 'o.order_id = og.order_id'
                ],
                [
                    'type' => 'INNER',
                    'table' => 'goods',
                    'alias' => 'g',
                    'on' => 'g.goods_id = og.goods_id'
                ],
                [
                    'type' => 'LEFT',
                    'table' => 'comment',
                    'alias' => 'c',
                    'on' => 'c.order_id = og.order_id AND c.id_value = og.goods_id'
                ]
            ],
        ]);
        if (empty($goods)) {
            return $this->response->redirect('user/bought/list');
        }
        $goods['add_time_format'] = local_date('Y-m-d', $goods['add_time']);
 
        $data = [];
        $data['user'] = $user;
        $data['goods'] = $goods;
        $data['navName'] = '';
        $data['userSidebar'] = 'bought-list';
        echo $this->viewoutput('pc/comment', $data);
    }
 
    /**
     * 订单列表
     */
    public function orderListAction()
    {
        global $db, $ecs;
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'includes/lib_order.php');
 
        $data = [];
        $user = $this->auth->user();
        $mcart = new Model\Cart;
 
        $page = intval($this->request->getQuery('page')) ?: 1;
        $limit = 10;
        $offset = ($page - 1) * $limit;
 
        $status = $this->request->getQuery('status') ?: 0;
        $userid = $_SESSION['user_id'];
        $morder = new Model\OrderInfo;
 
        // 需要评论的order_id
        $result = $morder->needComment();
        $needCommentOrderids = [];
        foreach ($result as $row) {
            $needCommentOrderids[$row['order_id']] = $row['order_id'];
        }
 
        if (! empty($status)) {
            $orderAttr = Model\OrderInfo::$orderStatus[$status];
        }
        $ossql = $sssql = $pssql = '';
        if (! empty($orderAttr['os'])) {
            $ossql = ' AND ' . db_create_in($orderAttr['os'], 'o.order_status');
        }
        if (! empty($orderAttr['ss'])) {
            $sssql = ' AND ' . db_create_in($orderAttr['ss'], 'o.shipping_status');
        }
        if (! empty($orderAttr['ps'])) {
            $pssql = ' AND ' . db_create_in($orderAttr['ps'], 'o.pay_status');
        }
 
        $conditions = '1 = 1';
        if ($status == 4) {
            if (empty($needCommentOrderids)) {
                $conditions .= ' AND 1 = 2';
            } else {
                $conditions .= ' AND o.order_id IN (' . implode(',', $needCommentOrderids) . ')';
            }
        }
        $conditions .= " AND o.user_id = '$userid' $ossql $sssql $pssql ORDER BY o.add_time DESC LIMIT $offset, $limit";
        $colls = Model\OrderInfo::find([
            'alias' => 'o',
            'columns' => "o.order_id, o.order_sn, o.order_status, o.shipping_status, o.pay_status, o.add_time, " .
                         "(o.goods_amount + o.shipping_fee + o.insure_fee + o.pay_fee + o.pack_fee + o.card_fee + o.tax - o.discount) AS total_fee, " .
                         "do.delivery_id, do.delivery_sn"
            ,
            'conditions' => $conditions,
            'join' => [
                [
                    'type' => 'LEFT',
                    'table' => 'delivery_order',
                    'alias' => 'do',
                    'on' => 'o.order_id = do.order_id'
                ]
            ],
        ]);
 
        $orderids = [];
        $orders = [];
        foreach ($colls as $row) {
            $orderids[] = $row['order_id'];
            $row['shipping_status'] = ($row['shipping_status'] == SS_SHIPPED_ING) ? SS_PREPARING : $row['shipping_status'];
 
            $row['status'] = Model\OrderInfo::getOrderStatus($row);
            $orders[$row['order_id']] = [
                'order_id'       => $row['order_id'],
                'status' => $row['status'],
                'order_sn'       => $row['order_sn'],
                'order_time'     => local_date($GLOBALS['_CFG']['time_format'], $row['add_time']),
                'order_status'   => $row['order_status'],
                'shipping_status'   => $row['shipping_status'],
                'pay_status'   => $row['pay_status'],
                'total_fee'      => price_format($row['total_fee'], false),
                'delivery_id'       => $row['delivery_id'],
                'delivery_sn'       => $row['delivery_sn'],
                'needComment' => isset($needCommentOrderids[$row['order_id']]) ? true : false,
                'goods' => [],
            ];
        }
 
        $statistics = $morder->getAllCount();
        $count = empty($status) ? $statistics['total'] : $statistics['count'][$status];
        $paginator = new \Juice\Paginator(['type' => 'emptyArray', 'config' => [
            'count' => $count,
            'limit' => $limit,
            'page' => $page,
        ]]);
        $paginate = $paginator->getPaginate();
        $data['paginate'] = $paginate;
 
        if ($orderids) {
            $orderidsStr = implode(',', $orderids);
            $params = [
                'alias' => 'og',
                'columns' => "og.rec_id, og.order_id, og.goods_id, og.goods_name, og.goods_sn, og.market_price, og.goods_number, " .
                    "og.goods_price, og.goods_attr, og.is_real, og.parent_id, og.is_gift, " .
                    "og.goods_price * og.goods_number AS subtotal, og.extension_code, g.goods_thumb, g.goods_img " .
                    ',c.comment_id ',
                'conditions' =>  "og.order_id IN ($orderidsStr)",
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = og.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'comment',
                        'alias' => 'c',
                        'on' => 'c.id_value = og.goods_id AND c.order_id = og.order_id '
                    ],
                ],
            ];
 
            $colls = Model\OrderGoods::find($params);
 
            foreach ($colls as $row) {
                if (isset($orders[$row['order_id']])) {
                    $row['market_price'] = price_format($row['market_price'], false);
                    $row['goods_price']  = price_format($row['goods_price'], false);
                    $row['subtotal']     = price_format($row['subtotal'], false);
                    $orders[$row['order_id']]['goods'][] = $row;
                }
            }
        }
 
        $data['user'] = $user;
        $data['orders'] = $orders;
        $data['status'] = $status;
        $data['statistics'] = $statistics;
        $data['navName'] = '';
        $data['userSidebar'] = 'order-list';
        echo $this->viewoutput('pc/order-list', $data);
    }
 
    /**
     * 地址
     */
    public function addressListAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        $user = $this->auth->user();
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
 
        $addressList = Model\UserAddress::findByUserId($user['user_id']);
        $regionsids = [];
        foreach ($addressList as $region_id => $row) {
            $row['province'] = isset($row['province']) ? intval($row['province']) : 0;
            $row['city']     = isset($row['city'])     ? intval($row['city'])     : 0;
            $row['district']  = isset($row['district'])  ? intval($row['district'])  : 0;
 
            $regionsids[] = $row['district'];
            $regionsids[] = $row['province'];
            $regionsids[] = $row['city'];
        }
        $regions = [];
        if ($regionsids) {
            $idsstr = implode(', ', $regionsids);
            $sql = 'SELECT region_id, region_name FROM ' . $GLOBALS['ecs']->table('region') .
                    " WHERE region_id IN ($idsstr)";
            $res = $GLOBALS['db']->query($sql);
            while ($row = $GLOBALS['db']->fetchRow($res)) {
                $regions[$row['region_id']] = $row;
            }
        }
        foreach ($addressList as $key => &$row) {
            $row['province_name'] = isset($regions[$row['province']]) ? $regions[$row['province']]['region_name'] : '';
            $row['city_name']     = isset($regions[$row['city']])     ? $regions[$row['city']]['region_name']     : '';
            $row['district_name'] = isset($regions[$row['district']])  ? $regions[$row['district']]['region_name']  : '';
        }
        unset($row);
 
        $status = $this->request->getQuery('status');
        $_SESSION['origin_from'] = $status;
        $regions = Model\Region::getAll();
 
        $data = [];
        $data['user'] = $user;
        $data['addressList'] = $addressList;
        $data['regions'] = $regions;
        $data['navName'] = '';
        $data['userSidebar'] = 'address-list';
        echo $this->viewoutput('pc/address-list', $data);
    }
 
    /**
     * 代理信息
     */
    public function agentInfoAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
 
        $user = $this->auth->user();
        $data = [];
        $agent = Model\Agent::findFirstByAgentId($user['agent_id']);
        if (empty($agent)) {
            $data['isEdit'] = false;
        } else {
            $data['isEdit'] = true;
        }
 
        $data['user'] = $user;
        $data['agent'] = $agent;
        $data['navName'] = '';
        $data['userSidebar'] = 'agent';
        echo $this->viewoutput('pc/agent-info', $data);
    }
 
    /**
     * 代理订单信息
     */
    public function agentOrderListAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
 
        $user = $this->auth->user();
 
        $page = intval($this->request->getQuery('page')) ?: 1;
        $limit = 10;
        $offset = ($page - 1) * $limit;
 
        $ossql = $sssql = $pssql = '';
        if (! empty($orderAttr['os'])) {
            $ossql = ' AND ' . db_create_in($orderAttr['os'], 'order_status');
        }
        if (! empty($orderAttr['ss'])) {
            $sssql = ' AND ' . db_create_in($orderAttr['ss'], 'shipping_status');
        }
        if (! empty($orderAttr['ps'])) {
            $pssql = ' AND ' . db_create_in($orderAttr['ps'], 'pay_status');
        }
        $orderids = [];
        $arr = [];
        $count = 0;
        if ($user['agent_id']) {
            $params = [
                'alias' => 'o',
                'columns' => "o.order_id, o.order_sn, o.order_status, o.shipping_status, o.pay_status, o.add_time, " .
                   "(o.goods_amount + o.shipping_fee + o.insure_fee + o.pay_fee + o.pack_fee + o.card_fee + o.tax - o.discount) AS total_fee , u.user_name",
                'conditions' =>  "o.agent_id = '{$user['agent_id']}' $ossql $sssql $pssql",
                'offset' => $offset,
                'limit' => $limit,
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'users',
                        'alias' => 'u',
                        'on' => 'u.user_id = o.user_id'
                    ]
                ],
                'order' => 'add_time DESC',
            ];
            $colls = Model\OrderInfo::find($params);
 
            foreach ($colls as $row)
            {
                $orderids[] = $row['order_id'];
                $row['shipping_status'] = ($row['shipping_status'] == SS_SHIPPED_ING) ? SS_PREPARING : $row['shipping_status'];
 
                $row['status'] = Model\OrderInfo::getOrderStatus($row);
                $orderTime = local_date($GLOBALS['_CFG']['time_format'], $row['add_time']);
                $dateArr = explode(' ', $orderTime);
                $arr[$row['order_id']] = [
                    'order_id'       => $row['order_id'],
                    'user_name' => $row['user_name'],
                    'status' => $row['status'],
                    'order_sn'       => $row['order_sn'],
                    'order_time'     => $orderTime,
                    'order_time_date'     => $orderTime,
                    'order_time_arr'     => $dateArr,
                    'order_status'   => $row['order_status'],
                    'shipping_status'   => $row['shipping_status'],
                    'pay_status'   => $row['pay_status'],
                    'total_fee'      => price_format($row['total_fee'], false),
                    'goods' => [],
                ];
            }
            unset($params['limit'], $params['offset']);
            $count = Model\OrderInfo::count($params);
        }
 
        $paginator = new \Juice\Paginator(['type' => 'emptyArray', 'config' => [
            'count' => $count,
            'limit' => $limit,
            'page' => $page,
        ]]);
        $paginate = $paginator->getPaginate();
 
        $orders = $arr;
        if ($orderids) {
            $orderidsStr = implode(',', $orderids);
            $params = [
                'alias' => 'og',
                'columns' => "og.rec_id, og.order_id, og.goods_id, og.goods_name, og.goods_sn, og.market_price, og.goods_number, " .
                    "og.goods_price, og.goods_attr, og.is_real, og.parent_id, og.is_gift, " .
                    "og.goods_price * og.goods_number AS subtotal, og.extension_code, g.goods_thumb, g.goods_img " .
                    ',c.comment_id ',
                'conditions' =>  "og.order_id IN ($orderidsStr)",
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = og.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'comment',
                        'alias' => 'c',
                        'on' => 'c.id_value = og.goods_id AND c.order_id = og.order_id '
                    ]
                ],
            ];
            $colls = Model\OrderGoods::find($params);
 
            foreach ($colls as $row) {
                if (isset($orders[$row['order_id']])) {
                    $row['market_price'] = price_format($row['market_price'], false);
                    $row['goods_price']  = price_format($row['goods_price'], false);
                    $row['subtotal']     = price_format($row['subtotal'], false);
                    $orders[$row['order_id']]['goods'][] = $row;
                }
            }
        }
 
        $data = [];
        $data['user'] = $user;
        $data['orders'] = $orders;
        $data['paginate'] = $paginate;
        $data['navName'] = '';
        $data['userSidebar'] = 'agent-order-list';
        echo $this->viewoutput('pc/agent-order-list', $data);
    }
 
    /**
     * 绑定微信号
     */
    public function wechatBindAction()
    {
        $user = $this->auth->user();
 
        $wechat = new WechatSDK;
        $url = 'http://www.liowang.com/shop/wechat/oauth2/callback/bind';
        $state = ['env' => 'product'];
        if (isLocal()) {
            $state = ['env' => 'local'];
        }
        return $this->response->redirect($wechat->pcWebOauth2($url, $state), true);
    }
    /**
     * PC 微信回调 oauth2 绑定已登录用户
     */
    public function wechatOauth2CallbackBindAction()
    {
        global $ecs, $db, $user;
        include_once(ROOT_PATH . 'includes/lib_passport.php');
        $code = $this->request->getQuery('code');
        if (empty($code)) {
            echo '授权失败';
            exit;
        }
        $state = $this->request->getQuery('state');
        parse_str($state, $params);
        if (! islocal() && ! empty($params['env']) && $params['env'] == 'local') {
            return $this->response->redirect('http://liowang.dev.seafarer.me/shop/wechat/oauth2/callback/bind?' . http_build_query($this->request->getQuery()));
        }
 
        $wechat = new WechatSDK;
        $config = $this->config->wechat;
        $config->appid = $config->open_web_appid;
        $config->appsecret = $config->open_web_secret;
        $wechat->init($config);
        $response = $wechat->getUserTokenByCode($code);
        if (empty($response['errcode'])) {
            // 授权成功
            $ewxpcuser = Model\WxPcUser::findFirstByOpenid($response['openid']);
 
            $data = [];
            $isFirst = false;
            $isNeedCreate = false;
            $uuser = [];
            $wxpcuser = [];
            if ($ewxpcuser) {
                // 用户已经存在
                $euser = Model\User::findFirstByWxPcUserid($ewxpcuser['id']);
            } else {
                // 第一次访问
                $isFirst = true;
                $wxuser = Model\WxUser::findFirstByUnionid($response['unionid']);
                if (empty($wxuser)) {
                    $isNeedCreate = true;
                } else {
                    $euser = Model\User::findFirstByWxUserid($wxuser['id']);
                }
            }
            $wxpcuser['openid'] = $response['openid'];
            $wxpcuser['unionid'] = $response['unionid'];
            $wxpcuser['access_token'] = $response['access_token'];
            $wxpcuser['refresh_token'] = $response['refresh_token'];
            $wxpcuser['expired_at'] = $response['expires_in'] + time() - 200;
            $wxpcuser['scope'] = $response['scope'];
            $wxpcuser['ip'] = $this->request->getClientAddress();
            $info = $wechat->getUserInfoByOauth($response['openid'], $response['access_token']);
            $wxpcuser['nickname'] = $info['nickname'];
            $wxpcuser['gender'] = $info['sex'];
            $wxpcuser['language'] = $info['language'];
            $wxpcuser['city'] = $info['city'];
            $wxpcuser['country'] = $info['country'];
            $wxpcuser['province'] = $info['province'];
            $wxpcuser['headimgurl'] = $info['headimgurl'];
 
            if (! empty($euser) && $euser['user_id'] != $_SESSION['user_id']) {
                exit('该微信已被绑定');
            }
 
            if ($isFirst) {
                // 首次PC扫码登录
                $id = Model\WxPcUser::insert($wxpcuser);
                $uuser['wx_pc_userid'] = $id;
                Model\User::update($uuser, "user_id = {$_SESSION['user_id']}");
 
            } else {
                $uuser['wx_pc_userid'] = $ewxpcuser['id'];
                Model\User::update($uuser, "user_id = {$_SESSION['user_id']}");
                Model\WxPcUser::update($wxpcuser, "openid = '{$response['openid']}'");

Warning: Cannot modify header information - headers already sent by (output started at /usr/local/www/liowang-shop-robotwar/shop/app/libraries/PrettyExceptions/Library/Phalcon/Utils/PrettyExceptions.php:339) in /usr/local/www/liowang-shop-robotwar/shop/app/libraries/PrettyExceptions/Library/Phalcon/Utils/PrettyExceptions.php on line 440
Exception - Undefined index: expired_at
Undefined index: expired_at
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (258)
#1{closure}(8, Undefined index: expired_at, /usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php, 258, Array(9))
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (258)
<?php
 
/**
 * weixin library
 * @author geekzhumail@gmail.com
 * @since 2014-09-24
 * @edit 2015-03-05
 */
class SDKRuntimeException extends Exception {
 
    public function errorMessage() {
        return $this->getMessage();
    }
}
 
class WechatSDK extends WechatBase{
 
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => 'wxdf0a039cf4ea94d1',
        'appsecret' => '7100f0fe837a5287f075b6d2d9814ca0',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/';
    /**
     * @var access_token
     */
    protected $accessToken = '';
 
    /**
     * 是否从远程服务器拿token
     */
    protected $isRemote = false;
 
    public function __construct() {
        parent::__construct();
        $this->isRemote = $this->config->is_remote;
        $this->accessToken = $this->getToken();
    }
 
    /**
     * 初始化数据
     */
    public function init($config) {
        $this->config = $config;
    }
 
    /**
     * 获取Appid
     */
    public function getAppid() {
        return $this->config['appid'];
    }
 
    /**
     * weixin signature
     */
    public function checkSignature($signature, $timestamp, $nonce) {
        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
 
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
 
    /**
     * web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function webOauth2($redirectUri, $state = [], $type = 'snsapi_base') {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = $type;
        if (empty($state['type'])) {
            $state['type'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * pc web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function pcWebOauth2($redirectUri, $state = []) {
        $params = [];
        $params['appid'] = $this->config['open_web_appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = 'snsapi_login';
        if (empty($state['env'])) {
            $state['env'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * get access token by code
     */
    public function getUserTokenByCode($code) {
        $params = array();
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $params['code'] = $code;
        $params['grant_type'] = 'authorization_code';
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * refresh access token
     * @param string $token 填写通过access_token获取到的refresh_token参数
     */
    public function refreshUserToken($token) {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['grant_type'] = 'refresh_token';
        $params['refresh_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 根据网页授权拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function getUserInfoByOauth($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('sns/userinfo', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 验证授权凭证(access_token)是否有效
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function validUserToken($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/auth?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 获取公众号token
     */
    public function getToken() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        if ($this->isRemote) {
            return $this->getTokenFromRemote();
        }
 
        $params = [];
        $params['grant_type'] = 'client_credential';
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $url = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        // 写入缓存
        $expiredAt = $response['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $response['access_token'], $expiredAt);
        return $response['access_token'];
    }
 
    /**
     * 获取access_token from remote
     */
    public function getTokenFromRemote() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/token/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $cache->save('wechat_access_token_expired', $resp['expired_at'], $expiredAt);
            $expiredAt = 120;
            $cache->save($key, $resp['access_token'], $expiredAt);
            return $resp['access_token'];
        } else {
            throw new Exception('wx admin get token error');
        }
    }
 
    /**
     * 获取用户基本信息(包括UnionID机制)
     * @param string $openid 用户的唯一标识
     * @param string $token 调用接口凭证,默认值为Wechat::getToken()
     */
    public function getUserInfo($openid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('cgi-bin/user/info', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 下载已经上传到微信的资源文件
     */
    public function downloadMedia($mediaid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['access_token'] = $token;
        $params['media_id'] = $mediaid;
        $url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $content = curl_exec($ch);
        $info = curl_getinfo($ch);
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            // 正常则分割header和body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($content, 0, $headerSize);
            $body = substr($content, $headerSize);
        }
        curl_close($ch);
        $resp['info'] = $info;
        $resp['header'] = $header;
        $resp['body'] = $body;
        return $resp;
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicket()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        if ($this->isRemote) {
            return $this->getJsapiTicketFromRemote();
        }
        $resp = json_decode($this->get('cgi-bin/ticket/getticket', ['access_token' => $this->accessToken, 'type' => 'jsapi']), true);
 
        // 写入缓存
        $expiredAt = $resp['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $resp['ticket'], $expiredAt);
        return $resp['ticket'];
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicketFromRemote()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/jsticket/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $expiredAt = 120;
            $cache->save($key, $resp['ticket'], $expiredAt);
            return $resp['ticket'];
        } else {
            throw new Exception('wx admin get ticket error');
        }
    }
    /**
     * 获取js api 签名
     */
    public function getSignPackage($url = '')
    {
        $jsticket = $this->getJsapiTicket();
 
        if (! $url) {
            $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
        }
        $timestamp = time();
        $nonceStr = $this->createNoncestr(16);
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket={$jsticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        return [
            "appId"     => $this->config['appid'],
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }
 
 
    /**
     * 新建一个类,由于PSR-4无法获取不以文件名命名的类;
     */
    public function newClass($name)
    {
        return new $name();
    }
 
    // 微信支付
 
    /**
     * 获取微信付款码的prepayid
     * @params array $params 参数
     */
    public function getPrepayId($params) {
        $sdk = new UnifiedOrderSDK;
        $sdk->setParameter("trade_type","JSAPI");//交易类型
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $prepayid = $sdk->getPrepayId();
        return $prepayid;
    }
 
    /**
     * 获取支付js api 签名
     */
    public function getPaySignPackage($prepayid)
    {
        $jsApiObj["appId"] = $this->config['appid'];
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->createNoncestr();
        $jsApiObj["package"] = "prepay_id=$prepayid";
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->getSign($jsApiObj);
        $signPackage = $jsApiObj;
 
        return $signPackage;
    }
 
    /**
     * 发送红包接口
     * @params array $params 参数
     */
    public function sendRedPack($params) {
        $sdk = new RedPackSDK;
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $result = $sdk->send();
        return $result;
    }
}
 
/**
 * 所有接口的基类
 */
class WechatBase
{
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => '',
        'appsecret' => '',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/cgi-bin/';
 
    public function __construct() {
        $this->di = Phalcon\DI::getDefault();
        $this->config = $this->di->getConfig()['wechat'];
    }
 
    protected function get($url, $params = '')
    {
        if (strpos($url, 'https://') === false && strpos($url, 'http://') === false) {
            $url = $this->apiPrefix.$url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl get error');
        }
    }
 
    protected function post($url, $params = '', $data = '')
    {
        if (strpos($url, 'https://') === false) {
            $url = $this->apiPrefix . $url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        if ($data) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl post error');
        }
    }
 
    public function trimString($value)
    {
        $ret = null;
        if (null != $value)
        {
            $ret = $value;
            if (strlen($ret) == 0)
            {
                $ret = null;
            }
        }
        return $ret;
    }
 
    /**
     *   作用:产生随机字符串,不长于32位
     */
    public function createNoncestr( $length = 32, $type = 'all')
    {
        if ($type == 'all') {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        } else if($type == 'number') {
            $chars = "0123456789";
        }
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
 
    /**
     *   作用:格式化参数,签名过程需要使用
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $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['key'];
        //echo "【string2】".$String."</br>";
        //签名步骤三:MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }
 
    /**
     *   作用:array转xml
     */
    public 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;
    }
 
    /**
     *   作用:将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;
    }
 
    /**
     *   作用:以post方式提交xml到对应的接口url
     */
    public function postXmlCurl($xml,$url,$second=30)
    {
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        //返回结果
        if($data)
        {
            return $data;
        }
        else
        {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            return false;
        }
    }
 
    /**
     *   作用:使用证书,以post方式提交xml到对应的接口url
     */
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, $this->config['sslcert_path']);
        //默认格式为PEM,可以注释
        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);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return false;
        }
    }
 
}
 
/**
 * 请求型接口的基类
 */
class WechatPayRequestBase extends WechatBase
{
    var $parameters;//请求参数,类型为关联数组
    public $response;//微信返回的响应
    public $result;//返回参数,类型为关联数组
    var $url;//接口链接
    var $curl_timeout;//curl超时时间
 
    /**
     *   作用:设置请求参数
     */
    function setParameter($parameter, $parameterValue)
    {
        $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
    }
 
    /**
     *   作用:设置标配的请求参数,生成签名,生成接口参数xml
     */
    function createXml()
    {
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     *   作用:post请求xml
     */
    function postXml()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:使用证书post请求xml
     */
    function postXmlSSL()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:获取结果,默认不使用证书
     */
    function getResult()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        return $this->result;
    }
}
 
/**
 * 统一支付接口类
 */
class UnifiedOrderSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        //检测必填参数
        if(empty($this->parameters["out_trade_no"]))
        {
            throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
        }elseif(empty($this->parameters["body"])){
            throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
        }elseif (empty($this->parameters["total_fee"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
        }elseif (empty($this->parameters["notify_url"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
        }elseif (empty($this->parameters["trade_type"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
        }elseif ($this->parameters["trade_type"] == "JSAPI" &&
            empty($this->parameters["openid"])){
            throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
        }
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["spbill_create_ip"] = $this->di->getRequest()->getClientAddress();//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 获取prepay_id
     */
    function getPrepayId()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        $prepay_id = $this->result["prepay_id"];
        return $prepay_id;
    }
 
}
 
/**
 * 响应型接口基类
 */
class WechatPayServerBase extends WechatBase
{
  public $data;//接收到的数据,类型为关联数组
  var $returnParameters;//返回参数,类型为关联数组
 
  /**
   * 将微信的请求xml转换成关联数组,以方便数据处理
   */
  function saveData($xml)
  {
    $this->data = $this->xmlToArray($xml);
  }
 
  function checkSign()
  {
    $tmpData = $this->data;
    unset($tmpData['sign']);
    $sign = $this->getSign($tmpData);//本地签名
        if (isLocal()) {
            echo $sign;
        }
    if ($this->data['sign'] == $sign) {
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * 获取微信的请求数据
   */
  function getData()
  {
    return $this->data;
  }
 
  /**
   * 设置返回微信的xml数据
   */
  function setReturnParameter($parameter, $parameterValue)
  {
    $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
  }
 
  /**
   * 生成接口参数xml
   */
  function createXml()
  {
    return $this->arrayToXml($this->returnParameters);
  }
 
  /**
   * 将xml数据返回微信
   */
  function returnXml()
  {
    $returnXml = $this->createXml();
    return $returnXml;
  }
}
 
/**
 * 红包发送接口类
 */
class RedPackSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        $requiredParams = [
            'mch_billno',
            'nick_name',
            'send_name',
            're_openid',
            'total_amount',
            'min_value',
            'max_value',
            'total_num',
            'wishing',
            'act_name',
            'remark',
        ];
        //检测必填参数
        foreach ($requiredParams as $val) {
            if (empty($this->parameters[$val])) {
                throw new SDKRuntimeException("缺少红包接口必填参数{$val}!"."<br>");
            }
        }
        $this->parameters["wxappid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["client_ip"] = gethostbyname(gethostname());//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 发送红包
     *
     * curl 返回例子
     *  <xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[发放成功.]]></return_msg>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <err_code><![CDATA[0]]></err_code>
        <err_code_des><![CDATA[发放成功.]]></err_code_des>
        <mch_billno><![CDATA[1228563102201503261427336509]]></mch_billno>
        <mch_id>1228563102</mch_id>
        <wxappid><![CDATA[wxdceccb9098a978b8]]></wxappid>
        <re_openid><![CDATA[o55e0t1NHPxdcRCjbIu_xnjHc2u8]]></re_openid>
        <total_amount>100</total_amount>
        </xml>
     */
    public function send() {
        $this->postXmlSSL();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        return $this->result;
    }
#2WechatSDK->getTokenFromRemote()
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (209)
<?php
 
/**
 * weixin library
 * @author geekzhumail@gmail.com
 * @since 2014-09-24
 * @edit 2015-03-05
 */
class SDKRuntimeException extends Exception {
 
    public function errorMessage() {
        return $this->getMessage();
    }
}
 
class WechatSDK extends WechatBase{
 
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => 'wxdf0a039cf4ea94d1',
        'appsecret' => '7100f0fe837a5287f075b6d2d9814ca0',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/';
    /**
     * @var access_token
     */
    protected $accessToken = '';
 
    /**
     * 是否从远程服务器拿token
     */
    protected $isRemote = false;
 
    public function __construct() {
        parent::__construct();
        $this->isRemote = $this->config->is_remote;
        $this->accessToken = $this->getToken();
    }
 
    /**
     * 初始化数据
     */
    public function init($config) {
        $this->config = $config;
    }
 
    /**
     * 获取Appid
     */
    public function getAppid() {
        return $this->config['appid'];
    }
 
    /**
     * weixin signature
     */
    public function checkSignature($signature, $timestamp, $nonce) {
        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
 
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
 
    /**
     * web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function webOauth2($redirectUri, $state = [], $type = 'snsapi_base') {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = $type;
        if (empty($state['type'])) {
            $state['type'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * pc web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function pcWebOauth2($redirectUri, $state = []) {
        $params = [];
        $params['appid'] = $this->config['open_web_appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = 'snsapi_login';
        if (empty($state['env'])) {
            $state['env'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * get access token by code
     */
    public function getUserTokenByCode($code) {
        $params = array();
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $params['code'] = $code;
        $params['grant_type'] = 'authorization_code';
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * refresh access token
     * @param string $token 填写通过access_token获取到的refresh_token参数
     */
    public function refreshUserToken($token) {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['grant_type'] = 'refresh_token';
        $params['refresh_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 根据网页授权拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function getUserInfoByOauth($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('sns/userinfo', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 验证授权凭证(access_token)是否有效
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function validUserToken($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/auth?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 获取公众号token
     */
    public function getToken() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        if ($this->isRemote) {
            return $this->getTokenFromRemote();
        }
 
        $params = [];
        $params['grant_type'] = 'client_credential';
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $url = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        // 写入缓存
        $expiredAt = $response['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $response['access_token'], $expiredAt);
        return $response['access_token'];
    }
 
    /**
     * 获取access_token from remote
     */
    public function getTokenFromRemote() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/token/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $cache->save('wechat_access_token_expired', $resp['expired_at'], $expiredAt);
            $expiredAt = 120;
            $cache->save($key, $resp['access_token'], $expiredAt);
            return $resp['access_token'];
        } else {
            throw new Exception('wx admin get token error');
        }
    }
 
    /**
     * 获取用户基本信息(包括UnionID机制)
     * @param string $openid 用户的唯一标识
     * @param string $token 调用接口凭证,默认值为Wechat::getToken()
     */
    public function getUserInfo($openid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('cgi-bin/user/info', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 下载已经上传到微信的资源文件
     */
    public function downloadMedia($mediaid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['access_token'] = $token;
        $params['media_id'] = $mediaid;
        $url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $content = curl_exec($ch);
        $info = curl_getinfo($ch);
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            // 正常则分割header和body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($content, 0, $headerSize);
            $body = substr($content, $headerSize);
        }
        curl_close($ch);
        $resp['info'] = $info;
        $resp['header'] = $header;
        $resp['body'] = $body;
        return $resp;
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicket()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        if ($this->isRemote) {
            return $this->getJsapiTicketFromRemote();
        }
        $resp = json_decode($this->get('cgi-bin/ticket/getticket', ['access_token' => $this->accessToken, 'type' => 'jsapi']), true);
 
        // 写入缓存
        $expiredAt = $resp['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $resp['ticket'], $expiredAt);
        return $resp['ticket'];
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicketFromRemote()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/jsticket/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $expiredAt = 120;
            $cache->save($key, $resp['ticket'], $expiredAt);
            return $resp['ticket'];
        } else {
            throw new Exception('wx admin get ticket error');
        }
    }
    /**
     * 获取js api 签名
     */
    public function getSignPackage($url = '')
    {
        $jsticket = $this->getJsapiTicket();
 
        if (! $url) {
            $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
        }
        $timestamp = time();
        $nonceStr = $this->createNoncestr(16);
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket={$jsticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        return [
            "appId"     => $this->config['appid'],
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }
 
 
    /**
     * 新建一个类,由于PSR-4无法获取不以文件名命名的类;
     */
    public function newClass($name)
    {
        return new $name();
    }
 
    // 微信支付
 
    /**
     * 获取微信付款码的prepayid
     * @params array $params 参数
     */
    public function getPrepayId($params) {
        $sdk = new UnifiedOrderSDK;
        $sdk->setParameter("trade_type","JSAPI");//交易类型
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $prepayid = $sdk->getPrepayId();
        return $prepayid;
    }
 
    /**
     * 获取支付js api 签名
     */
    public function getPaySignPackage($prepayid)
    {
        $jsApiObj["appId"] = $this->config['appid'];
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->createNoncestr();
        $jsApiObj["package"] = "prepay_id=$prepayid";
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->getSign($jsApiObj);
        $signPackage = $jsApiObj;
 
        return $signPackage;
    }
 
    /**
     * 发送红包接口
     * @params array $params 参数
     */
    public function sendRedPack($params) {
        $sdk = new RedPackSDK;
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $result = $sdk->send();
        return $result;
    }
}
 
/**
 * 所有接口的基类
 */
class WechatBase
{
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => '',
        'appsecret' => '',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/cgi-bin/';
 
    public function __construct() {
        $this->di = Phalcon\DI::getDefault();
        $this->config = $this->di->getConfig()['wechat'];
    }
 
    protected function get($url, $params = '')
    {
        if (strpos($url, 'https://') === false && strpos($url, 'http://') === false) {
            $url = $this->apiPrefix.$url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl get error');
        }
    }
 
    protected function post($url, $params = '', $data = '')
    {
        if (strpos($url, 'https://') === false) {
            $url = $this->apiPrefix . $url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        if ($data) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl post error');
        }
    }
 
    public function trimString($value)
    {
        $ret = null;
        if (null != $value)
        {
            $ret = $value;
            if (strlen($ret) == 0)
            {
                $ret = null;
            }
        }
        return $ret;
    }
 
    /**
     *   作用:产生随机字符串,不长于32位
     */
    public function createNoncestr( $length = 32, $type = 'all')
    {
        if ($type == 'all') {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        } else if($type == 'number') {
            $chars = "0123456789";
        }
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
 
    /**
     *   作用:格式化参数,签名过程需要使用
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $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['key'];
        //echo "【string2】".$String."</br>";
        //签名步骤三:MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }
 
    /**
     *   作用:array转xml
     */
    public 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;
    }
 
    /**
     *   作用:将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;
    }
 
    /**
     *   作用:以post方式提交xml到对应的接口url
     */
    public function postXmlCurl($xml,$url,$second=30)
    {
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        //返回结果
        if($data)
        {
            return $data;
        }
        else
        {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            return false;
        }
    }
 
    /**
     *   作用:使用证书,以post方式提交xml到对应的接口url
     */
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, $this->config['sslcert_path']);
        //默认格式为PEM,可以注释
        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);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return false;
        }
    }
 
}
 
/**
 * 请求型接口的基类
 */
class WechatPayRequestBase extends WechatBase
{
    var $parameters;//请求参数,类型为关联数组
    public $response;//微信返回的响应
    public $result;//返回参数,类型为关联数组
    var $url;//接口链接
    var $curl_timeout;//curl超时时间
 
    /**
     *   作用:设置请求参数
     */
    function setParameter($parameter, $parameterValue)
    {
        $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
    }
 
    /**
     *   作用:设置标配的请求参数,生成签名,生成接口参数xml
     */
    function createXml()
    {
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     *   作用:post请求xml
     */
    function postXml()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:使用证书post请求xml
     */
    function postXmlSSL()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:获取结果,默认不使用证书
     */
    function getResult()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        return $this->result;
    }
}
 
/**
 * 统一支付接口类
 */
class UnifiedOrderSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        //检测必填参数
        if(empty($this->parameters["out_trade_no"]))
        {
            throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
        }elseif(empty($this->parameters["body"])){
            throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
        }elseif (empty($this->parameters["total_fee"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
        }elseif (empty($this->parameters["notify_url"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
        }elseif (empty($this->parameters["trade_type"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
        }elseif ($this->parameters["trade_type"] == "JSAPI" &&
            empty($this->parameters["openid"])){
            throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
        }
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["spbill_create_ip"] = $this->di->getRequest()->getClientAddress();//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 获取prepay_id
     */
    function getPrepayId()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        $prepay_id = $this->result["prepay_id"];
        return $prepay_id;
    }
 
}
 
/**
 * 响应型接口基类
 */
class WechatPayServerBase extends WechatBase
{
  public $data;//接收到的数据,类型为关联数组
  var $returnParameters;//返回参数,类型为关联数组
 
  /**
   * 将微信的请求xml转换成关联数组,以方便数据处理
   */
  function saveData($xml)
  {
    $this->data = $this->xmlToArray($xml);
  }
 
  function checkSign()
  {
    $tmpData = $this->data;
    unset($tmpData['sign']);
    $sign = $this->getSign($tmpData);//本地签名
        if (isLocal()) {
            echo $sign;
        }
    if ($this->data['sign'] == $sign) {
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * 获取微信的请求数据
   */
  function getData()
  {
    return $this->data;
  }
 
  /**
   * 设置返回微信的xml数据
   */
  function setReturnParameter($parameter, $parameterValue)
  {
    $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
  }
 
  /**
   * 生成接口参数xml
   */
  function createXml()
  {
    return $this->arrayToXml($this->returnParameters);
  }
 
  /**
   * 将xml数据返回微信
   */
  function returnXml()
  {
    $returnXml = $this->createXml();
    return $returnXml;
  }
}
 
/**
 * 红包发送接口类
 */
class RedPackSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        $requiredParams = [
            'mch_billno',
            'nick_name',
            'send_name',
            're_openid',
            'total_amount',
            'min_value',
            'max_value',
            'total_num',
            'wishing',
            'act_name',
            'remark',
        ];
        //检测必填参数
        foreach ($requiredParams as $val) {
            if (empty($this->parameters[$val])) {
                throw new SDKRuntimeException("缺少红包接口必填参数{$val}!"."<br>");
            }
        }
        $this->parameters["wxappid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["client_ip"] = gethostbyname(gethostname());//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 发送红包
     *
     * curl 返回例子
     *  <xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[发放成功.]]></return_msg>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <err_code><![CDATA[0]]></err_code>
        <err_code_des><![CDATA[发放成功.]]></err_code_des>
        <mch_billno><![CDATA[1228563102201503261427336509]]></mch_billno>
        <mch_id>1228563102</mch_id>
        <wxappid><![CDATA[wxdceccb9098a978b8]]></wxappid>
        <re_openid><![CDATA[o55e0t1NHPxdcRCjbIu_xnjHc2u8]]></re_openid>
        <total_amount>100</total_amount>
        </xml>
     */
    public function send() {
        $this->postXmlSSL();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        return $this->result;
    }
#3WechatSDK->getToken()
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (45)
<?php
 
/**
 * weixin library
 * @author geekzhumail@gmail.com
 * @since 2014-09-24
 * @edit 2015-03-05
 */
class SDKRuntimeException extends Exception {
 
    public function errorMessage() {
        return $this->getMessage();
    }
}
 
class WechatSDK extends WechatBase{
 
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => 'wxdf0a039cf4ea94d1',
        'appsecret' => '7100f0fe837a5287f075b6d2d9814ca0',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/';
    /**
     * @var access_token
     */
    protected $accessToken = '';
 
    /**
     * 是否从远程服务器拿token
     */
    protected $isRemote = false;
 
    public function __construct() {
        parent::__construct();
        $this->isRemote = $this->config->is_remote;
        $this->accessToken = $this->getToken();
    }
 
    /**
     * 初始化数据
     */
    public function init($config) {
        $this->config = $config;
    }
 
    /**
     * 获取Appid
     */
    public function getAppid() {
        return $this->config['appid'];
    }
 
    /**
     * weixin signature
     */
    public function checkSignature($signature, $timestamp, $nonce) {
        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
 
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
 
    /**
     * web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function webOauth2($redirectUri, $state = [], $type = 'snsapi_base') {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = $type;
        if (empty($state['type'])) {
            $state['type'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * pc web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function pcWebOauth2($redirectUri, $state = []) {
        $params = [];
        $params['appid'] = $this->config['open_web_appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = 'snsapi_login';
        if (empty($state['env'])) {
            $state['env'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * get access token by code
     */
    public function getUserTokenByCode($code) {
        $params = array();
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $params['code'] = $code;
        $params['grant_type'] = 'authorization_code';
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * refresh access token
     * @param string $token 填写通过access_token获取到的refresh_token参数
     */
    public function refreshUserToken($token) {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['grant_type'] = 'refresh_token';
        $params['refresh_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 根据网页授权拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function getUserInfoByOauth($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('sns/userinfo', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 验证授权凭证(access_token)是否有效
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function validUserToken($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/auth?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 获取公众号token
     */
    public function getToken() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        if ($this->isRemote) {
            return $this->getTokenFromRemote();
        }
 
        $params = [];
        $params['grant_type'] = 'client_credential';
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $url = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        // 写入缓存
        $expiredAt = $response['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $response['access_token'], $expiredAt);
        return $response['access_token'];
    }
 
    /**
     * 获取access_token from remote
     */
    public function getTokenFromRemote() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/token/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $cache->save('wechat_access_token_expired', $resp['expired_at'], $expiredAt);
            $expiredAt = 120;
            $cache->save($key, $resp['access_token'], $expiredAt);
            return $resp['access_token'];
        } else {
            throw new Exception('wx admin get token error');
        }
    }
 
    /**
     * 获取用户基本信息(包括UnionID机制)
     * @param string $openid 用户的唯一标识
     * @param string $token 调用接口凭证,默认值为Wechat::getToken()
     */
    public function getUserInfo($openid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('cgi-bin/user/info', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 下载已经上传到微信的资源文件
     */
    public function downloadMedia($mediaid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['access_token'] = $token;
        $params['media_id'] = $mediaid;
        $url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $content = curl_exec($ch);
        $info = curl_getinfo($ch);
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            // 正常则分割header和body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($content, 0, $headerSize);
            $body = substr($content, $headerSize);
        }
        curl_close($ch);
        $resp['info'] = $info;
        $resp['header'] = $header;
        $resp['body'] = $body;
        return $resp;
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicket()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        if ($this->isRemote) {
            return $this->getJsapiTicketFromRemote();
        }
        $resp = json_decode($this->get('cgi-bin/ticket/getticket', ['access_token' => $this->accessToken, 'type' => 'jsapi']), true);
 
        // 写入缓存
        $expiredAt = $resp['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $resp['ticket'], $expiredAt);
        return $resp['ticket'];
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicketFromRemote()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/jsticket/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $expiredAt = 120;
            $cache->save($key, $resp['ticket'], $expiredAt);
            return $resp['ticket'];
        } else {
            throw new Exception('wx admin get ticket error');
        }
    }
    /**
     * 获取js api 签名
     */
    public function getSignPackage($url = '')
    {
        $jsticket = $this->getJsapiTicket();
 
        if (! $url) {
            $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
        }
        $timestamp = time();
        $nonceStr = $this->createNoncestr(16);
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket={$jsticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        return [
            "appId"     => $this->config['appid'],
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }
 
 
    /**
     * 新建一个类,由于PSR-4无法获取不以文件名命名的类;
     */
    public function newClass($name)
    {
        return new $name();
    }
 
    // 微信支付
 
    /**
     * 获取微信付款码的prepayid
     * @params array $params 参数
     */
    public function getPrepayId($params) {
        $sdk = new UnifiedOrderSDK;
        $sdk->setParameter("trade_type","JSAPI");//交易类型
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $prepayid = $sdk->getPrepayId();
        return $prepayid;
    }
 
    /**
     * 获取支付js api 签名
     */
    public function getPaySignPackage($prepayid)
    {
        $jsApiObj["appId"] = $this->config['appid'];
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->createNoncestr();
        $jsApiObj["package"] = "prepay_id=$prepayid";
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->getSign($jsApiObj);
        $signPackage = $jsApiObj;
 
        return $signPackage;
    }
 
    /**
     * 发送红包接口
     * @params array $params 参数
     */
    public function sendRedPack($params) {
        $sdk = new RedPackSDK;
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $result = $sdk->send();
        return $result;
    }
}
 
/**
 * 所有接口的基类
 */
class WechatBase
{
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => '',
        'appsecret' => '',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/cgi-bin/';
 
    public function __construct() {
        $this->di = Phalcon\DI::getDefault();
        $this->config = $this->di->getConfig()['wechat'];
    }
 
    protected function get($url, $params = '')
    {
        if (strpos($url, 'https://') === false && strpos($url, 'http://') === false) {
            $url = $this->apiPrefix.$url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl get error');
        }
    }
 
    protected function post($url, $params = '', $data = '')
    {
        if (strpos($url, 'https://') === false) {
            $url = $this->apiPrefix . $url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        if ($data) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl post error');
        }
    }
 
    public function trimString($value)
    {
        $ret = null;
        if (null != $value)
        {
            $ret = $value;
            if (strlen($ret) == 0)
            {
                $ret = null;
            }
        }
        return $ret;
    }
 
    /**
     *   作用:产生随机字符串,不长于32位
     */
    public function createNoncestr( $length = 32, $type = 'all')
    {
        if ($type == 'all') {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        } else if($type == 'number') {
            $chars = "0123456789";
        }
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
 
    /**
     *   作用:格式化参数,签名过程需要使用
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $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['key'];
        //echo "【string2】".$String."</br>";
        //签名步骤三:MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }
 
    /**
     *   作用:array转xml
     */
    public 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;
    }
 
    /**
     *   作用:将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;
    }
 
    /**
     *   作用:以post方式提交xml到对应的接口url
     */
    public function postXmlCurl($xml,$url,$second=30)
    {
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        //返回结果
        if($data)
        {
            return $data;
        }
        else
        {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            return false;
        }
    }
 
    /**
     *   作用:使用证书,以post方式提交xml到对应的接口url
     */
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, $this->config['sslcert_path']);
        //默认格式为PEM,可以注释
        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);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return false;
        }
    }
 
}
 
/**
 * 请求型接口的基类
 */
class WechatPayRequestBase extends WechatBase
{
    var $parameters;//请求参数,类型为关联数组
    public $response;//微信返回的响应
    public $result;//返回参数,类型为关联数组
    var $url;//接口链接
    var $curl_timeout;//curl超时时间
 
    /**
     *   作用:设置请求参数
     */
    function setParameter($parameter, $parameterValue)
    {
        $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
    }
 
    /**
     *   作用:设置标配的请求参数,生成签名,生成接口参数xml
     */
    function createXml()
    {
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     *   作用:post请求xml
     */
    function postXml()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:使用证书post请求xml
     */
    function postXmlSSL()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:获取结果,默认不使用证书
     */
    function getResult()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        return $this->result;
    }
}
 
/**
 * 统一支付接口类
 */
class UnifiedOrderSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        //检测必填参数
        if(empty($this->parameters["out_trade_no"]))
        {
            throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
        }elseif(empty($this->parameters["body"])){
            throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
        }elseif (empty($this->parameters["total_fee"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
        }elseif (empty($this->parameters["notify_url"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
        }elseif (empty($this->parameters["trade_type"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
        }elseif ($this->parameters["trade_type"] == "JSAPI" &&
            empty($this->parameters["openid"])){
            throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
        }
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["spbill_create_ip"] = $this->di->getRequest()->getClientAddress();//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 获取prepay_id
     */
    function getPrepayId()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        $prepay_id = $this->result["prepay_id"];
        return $prepay_id;
    }
 
}
 
/**
 * 响应型接口基类
 */
class WechatPayServerBase extends WechatBase
{
  public $data;//接收到的数据,类型为关联数组
  var $returnParameters;//返回参数,类型为关联数组
 
  /**
   * 将微信的请求xml转换成关联数组,以方便数据处理
   */
  function saveData($xml)
  {
    $this->data = $this->xmlToArray($xml);
  }
 
  function checkSign()
  {
    $tmpData = $this->data;
    unset($tmpData['sign']);
    $sign = $this->getSign($tmpData);//本地签名
        if (isLocal()) {
            echo $sign;
        }
    if ($this->data['sign'] == $sign) {
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * 获取微信的请求数据
   */
  function getData()
  {
    return $this->data;
  }
 
  /**
   * 设置返回微信的xml数据
   */
  function setReturnParameter($parameter, $parameterValue)
  {
    $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
  }
 
  /**
   * 生成接口参数xml
   */
  function createXml()
  {
    return $this->arrayToXml($this->returnParameters);
  }
 
  /**
   * 将xml数据返回微信
   */
  function returnXml()
  {
    $returnXml = $this->createXml();
    return $returnXml;
  }
}
 
/**
 * 红包发送接口类
 */
class RedPackSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        $requiredParams = [
            'mch_billno',
            'nick_name',
            'send_name',
            're_openid',
            'total_amount',
            'min_value',
            'max_value',
            'total_num',
            'wishing',
            'act_name',
            'remark',
        ];
        //检测必填参数
        foreach ($requiredParams as $val) {
            if (empty($this->parameters[$val])) {
                throw new SDKRuntimeException("缺少红包接口必填参数{$val}!"."<br>");
            }
        }
        $this->parameters["wxappid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["client_ip"] = gethostbyname(gethostname());//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 发送红包
     *
     * curl 返回例子
     *  <xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[发放成功.]]></return_msg>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <err_code><![CDATA[0]]></err_code>
        <err_code_des><![CDATA[发放成功.]]></err_code_des>
        <mch_billno><![CDATA[1228563102201503261427336509]]></mch_billno>
        <mch_id>1228563102</mch_id>
        <wxappid><![CDATA[wxdceccb9098a978b8]]></wxappid>
        <re_openid><![CDATA[o55e0t1NHPxdcRCjbIu_xnjHc2u8]]></re_openid>
        <total_amount>100</total_amount>
        </xml>
     */
    public function send() {
        $this->postXmlSSL();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        return $this->result;
    }
#4WechatSDK->__construct()
/usr/local/www/liowang-shop-robotwar/shop/app/controllers/PageUserController.php (24)
<?php
 
use HomeSpace\Model;
 
class PageUserController extends PageBaseController {
 
    /**
     * 登录
     */
    public function loginAction()
    {
        $data = [];
        //echo '登录';
        $act = $this->request->getQuery('act');
        if ($act == 'not_active') {
            if (empty($_SESSION['tmp_email'])) {
                $act = '';
            }
            $data['email'] = $_SESSION['tmp_email'];
        }
        $data['act'] = $act;
        $data['errmsg'] = $this->flash->getMessages('error');
        $this->flash->clear();
        $wechat = new WechatSDK;
        //$url = $this->url->get('wechat/oauth2/callback');
        $url = 'http://www.liowang.com/shop/wechat/oauth2/callback';
        $state = ['env' => 'product'];
        if (isLocal()) {
            $state = ['env' => 'local'];
        }
        $data['wechat_login_url'] = $wechat->pcWebOauth2($url, $state);
        echo $this->viewoutput('pc/login', $data);
    }
 
    /**
     * 登录 POST
     */
    public function postLoginAction()
    {
        global $user, $_LANG;
        $errmsg = '';
        $account = $this->request->getPost('account');
        $password = $this->request->getPost('password');
        $remember = ! empty($this->request->getQuery('remember'));
        if (filter_var($account, FILTER_VALIDATE_EMAIL)) {
            // 邮箱登录
            $muser = Model\User::findFirst(['columns' => 'user_name', 'conditions' => "email = '$account'"]);
        } else if(is_numeric($account) && strlen($account) == 11) {
            // 手机登录
            $muser = Model\User::findFirst(['columns' => 'user_name', 'conditions' => "mobile_phone = '$account'"]);
        }
        if (! empty($muser) && ! empty($password)) {
            if ($user->login($muser['user_name'], $password, $remember)) {
                $row = Model\User::findFirstByUserId($_SESSION['user_id']);
                if ($row['is_validated'] == 1) {
                    update_user_info();
                    recalculate_price();
 
                    $this->auth->loginUsingId($_SESSION['user_id']);
                    $_SESSION['pc_login'] = true;
                    return $this->response->redirect('home/index');
                } else {
                    //$_SESSION['login_fail'] ++ ;
                    //$errmsg = '该账号尚未激活请查阅该账号邮箱的激活邮件,或者联系客服';
                    $_SESSION['tmp_email'] = $row['email'];
                    return $this->response->redirect('login?act=not_active');
                }
            } else {
                $_SESSION['login_fail'] ++ ;
                $errmsg = $_LANG['login_failure'];
            }
        }
        $errmsg = $errmsg ?: '账号或者密码错误';
        $this->flash->error($errmsg);
        return $this->response->redirect('login');
    }
 
    /**
     * 退出登录
     */
    public function logoutAction()
    {
        global $user;
        $user->logout();
        return $this->response->redirect($this->request->getHTTPReferer(), true);
    }
 
    /**
     * 邮箱注册验证
     */
    public function emailValidateAction()
    {
        include_once(ROOT_PATH . 'includes/lib_passport.php');
        $data = [];
        $data['status'] = 'user_not_exist';
        $hash = $this->request->getQuery('hash');
        if ($hash) {
            $id = intval(register_hash('decode', $hash));
            $user = Model\User::findFirstByUserId($id);
            if ($user) {
                if ($user['is_validated'] == 0) {
                    Model\User::update(['is_validated' => 1], ['user_id' => $id]);
                    $data['status'] = 'success';
                } else {
                    $data['status'] = 'validated';
                }
                $data['user'] = $user;
            }
        }
        echo $this->viewoutput('pc/email-validate', $data);
    }
 
    /**
     * PC 微信回调 oauth2 函数
     */
    public function wechatOauth2CallbackAction()
    {
        global $ecs, $db, $user;
        include_once(ROOT_PATH . 'includes/lib_passport.php');
        $code = $this->request->getQuery('code');
        if (empty($code)) {
            echo '授权失败';
            exit;
        }
        $state = $this->request->getQuery('state');
        parse_str($state, $params);
        if (! islocal() && ! empty($params['env']) && $params['env'] == 'local') {
            return $this->response->redirect('http://liowang.dev.seafarer.me/shop/wechat/oauth2/callback?' . http_build_query($this->request->getQuery()));
        }
 
        $wechat = new WechatSDK;
        $config = $this->config->wechat;
        $config->appid = $config->open_web_appid;
        $config->appsecret = $config->open_web_secret;
        $wechat->init($config);
        $response = $wechat->getUserTokenByCode($code);
        if (empty($response['errcode'])) {
            // 授权成功
            $ewxpcuser = Model\WxPcUser::findFirstByOpenid($response['openid']);
 
            $data = [];
            $isFirst = false;
            $isNeedCreate = false;
            $uuser = [];
            $wxpcuser = [];
            if ($ewxpcuser) {
                // 用户已经存在
                $euser = Model\User::findFirstByWxPcUserid($ewxpcuser['id']);
            } else {
                // 第一次访问
                $isFirst = true;
                $wxuser = Model\WxUser::findFirstByUnionid($response['unionid']);
                if (empty($wxuser)) {
                    $isNeedCreate = true;
                } else {
                    $euser = Model\User::findFirstByWxUserid($wxuser['id']);
                }
            }
            $wxpcuser['openid'] = $response['openid'];
            $wxpcuser['unionid'] = $response['unionid'];
            $wxpcuser['access_token'] = $response['access_token'];
            $wxpcuser['refresh_token'] = $response['refresh_token'];
            $wxpcuser['expired_at'] = $response['expires_in'] + time() - 200;
            $wxpcuser['scope'] = $response['scope'];
            $wxpcuser['ip'] = $this->request->getClientAddress();
            $info = $wechat->getUserInfoByOauth($response['openid'], $response['access_token']);
            $wxpcuser['nickname'] = $info['nickname'];
            $wxpcuser['gender'] = $info['sex'];
            $wxpcuser['language'] = $info['language'];
            $wxpcuser['city'] = $info['city'];
            $wxpcuser['country'] = $info['country'];
            $wxpcuser['province'] = $info['province'];
            $wxpcuser['headimgurl'] = $info['headimgurl'];
 
            if ($isFirst) {
                // 首次PC扫码登录
                $id = Model\WxPcUser::insert($wxpcuser);
                $_SESSION['wx_pc_user_id'] = $id;
                $uuser['wx_pc_userid'] = $id;
                if (! $isNeedCreate) {
                    Model\User::update($uuser, "user_id = {$euser['user_id']}");
                }
                /**
                if ($isNeedCreate) {
                    // 创建新用户
                    $uuser['avatar'] = 'shop/assets/pc/IMGS/defaultPhoto.png';
                    $name = '会员_' . time() . '_' . $id;
                    $uuser['nickname'] = $wxpcuser['nickname'];
                    $email = 'email_' . time() . '_' . $id . '@liowang.com';
                    if (register($name, '123123', $email) !== false) {;
                        $db->autoExecute($ecs->table('users'), $uuser, 'UPDATE', "user_id = {$_SESSION['user_id']}");
                    } else {
                        SystemException::error('授权失败');
                    }
                } else {
                    // 绑定已在微信授权的用户
                    Model\User::update($uuser, "user_id = {$euser['user_id']}");
                    Model\WxPcUser::update($wxpcuser, "openid = '{$response['openid']}'");
                    $user->set_session($euser['user_name']);
                    $user->set_cookie($euser['user_name']);
                    update_user_info();
                    recalculate_price();
                }**/
            } else {
                Model\WxPcUser::update($wxpcuser, "openid = '{$response['openid']}'");
                $_SESSION['wx_pc_user_id'] = $ewxpcuser['id'];
            }
            if (empty($euser)) {
                return $this->response->redirect('wechat/oauth2/register');
            }
            // 已注册直接登录
            $user->set_session($euser['user_name']);
            $user->set_cookie($euser['user_name']);
            update_user_info();
            recalculate_price();
            $this->auth->loginUsingId($_SESSION['user_id']);
            $_SESSION['pc_login'] = true;
        } else {
            SystemException::error($response['errmsg']);
        }
        $referer = $this->url->get('');
        return $this->response->redirect($referer, true);
    }
 
    /**
     * PC 微信账号登录绑定
     */
    public function wechatOauth2RegisterAction()
    {
        global $ecs, $db, $user;
 
        if (empty($_SESSION['wx_pc_user_id'])) {
            return $this->response->redirect('login');
        }
        $data = [];
        echo $this->viewoutput('pc/wechat-oauth2-register', $data);
    }
 
    /******** 需要登录授权 *********/
    /**
     * 个人信息
     */
    public function infoAction()
    {
        $user = $this->auth->user();
 
        list($user['birYear'], $user['birMonth'], $user['birDay']) = explode('-', $user['birthday']);
        $user['birMonth'] = ltrim($user['birMonth'], 0);
        $user['birDay'] = ltrim($user['birDay'], 0);
        if (strpos($user['avatar'], 'http://') === false && strpos($user['avatar'], 'https://') === false) {
            $user['avatar'] = 'http://' . $this->request->getHttpHost() . '/' . $user['avatar'];
        }
        $data = [];
        $data['user'] = $user;
        $data['navName'] = '';
        $data['userSidebar'] = 'user-info';
        echo $this->viewoutput('pc/user-info', $data);
    }
    /**
     * 购物车
     */
    public function cartAction()
    {
        require(ROOT_PATH . 'includes/lib_order.php');
        $user = $this->auth->user();
        $mcart = new Model\Cart;
        $cartCount = $mcart->getCount();
        $cartGoods = get_cart_goods();
        $discount = compute_discount();
 
        $goodsids = [];
        foreach ($cartGoods['goods_list'] as $goods) {
            $goodsids[] = $goods['goods_id'];
        }
        if ($goodsids) {
            $collectGoodsList = Model\CollectGoods::find(['conditions' => "user_id='$_SESSION[user_id]' AND " . db_create_in($goodsids, 'goods_id')]);
            foreach ($cartGoods['goods_list'] as &$goods) {
                $goods['is_collect'] = false;
                foreach ($collectGoodsList as $row) {
                    if ($row['goods_id'] == $goods['goods_id']) {
                        $goods['is_collect'] = true;
                        break;
                    }
                }
            }
            unset($goods);
        }
 
        $data = [];
        $data['user'] = $user;
        $data['cartGoods'] = $cartGoods;
        $data['navName'] = '';
        echo $this->viewoutput('pc/user-cart', $data);
    }
    /**
     * 购物车结算
     */
    public function cartSettleAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        require(ROOT_PATH . 'includes/lib_order.php');
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
        $user = $this->auth->user();
 
        $recids = $this->request->getQuery('rec_ids') ?: [];
 
        $cartGoods = get_cart_goods($recids);
        if (empty($cartGoods['goods_list'])) {
            // 购物车为空
            return $this->response->redirect('user/cart');
        }
        $discount = compute_discount();
 
        $data = [];
 
        $address_id = intval($this->request->getQuery('address_id'));
        if ($address_id == 0) {
            $address_id = $user['address_id'];
        }
        $address = Model\UserAddress::findFirst(['conditions' => "address_id = $address_id AND user_id = {$user['user_id']}"]);
        $addressList = Model\UserAddress::find(['conditions' => "address_id != $address_id AND user_id = {$user['user_id']}", 'order' => 'address_id DESC']);
        if ($address) {
            array_unshift($addressList, $address);
        }
        $regions = Model\Region::getAll();
        foreach ($addressList as &$row) {
            $country = $regions[$row['country']];
            $province = $country['list'][$row['province']];
            $city = $province['list'][$row['city']];
            $district = $city['list'][$row['district']];
            $row['address_detail'] = $province['region_name'] . $city['region_name'] . $district['region_name'] . $row['address'];
        }
        unset($row);
        $data['addressList'] = $addressList;
        foreach ($cartGoods['goods_list'] as &$goods) {
            //$goods['goods_name'] = mb_substr($goods['goods_name'], 0, 15, 'utf-8') . '......';
        }
        unset($goods);
 
        $regions = Model\Region::getAll();
 
        $data['user'] = $user;
        $data['regions'] = $regions;
        $data['cartGoods'] = $cartGoods;
        $data['navName'] = '';
        echo $this->viewoutput('pc/cart-settle', $data);
    }
 
    /**
     * 已购买的商品
     */
    public function boughtListAction()
    {
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'includes/lib_order.php');
        $user = $this->auth->user();
        $mcart = new Model\Cart;
 
        global $db, $ecs;
 
        $page = intval($this->request->getQuery('page')) ?: 1;
        $limit = 10;
        $offset = ($page - 1) * $limit;
        $userid = $_SESSION['user_id'];
        $morder = new Model\OrderInfo;
 
        $arr    = array();
        $osArr = [];
        $ssArr = [];
        $psArr = [];
        // 需要评论的order_id
        $result = $morder->needComment();
        $needCommentOrderids = [];
        foreach ($result as $row) {
            $needCommentOrderids[$row['order_id']] = $row['order_id'];
        }
 
        $osArr[] = OS_CONFIRMED;
        $osArr[] = OS_SPLITED;
        $ssArr[] = SS_RECEIVED;
        $psArr[] = PS_PAYED;
        $ossql = $sssql = $pssql = '';
        if ($osArr) {
            $ossql = ' AND ' . db_create_in($osArr, 'order_status');
        }
        if ($ssArr) {
            $sssql = ' AND ' . db_create_in($ssArr, 'shipping_status');
        }
        if ($psArr) {
            $pssql = ' AND ' . db_create_in($psArr, 'pay_status');
        }
        $sql = "SELECT order_id, order_sn, order_status, shipping_status, pay_status, add_time, " .
               "(goods_amount + shipping_fee + insure_fee + pay_fee + pack_fee + card_fee + tax - discount) AS total_fee ".
               " FROM " .$GLOBALS['ecs']->table('order_info') .
               ' WHERE 1 = 1';
        $sql .= " AND user_id = '$userid' $ossql $sssql $pssql ORDER BY add_time DESC";
        //$res = $GLOBALS['db']->SelectLimit($sql, 20, ($page - 1) * 20);
        $res = $GLOBALS['db']->query($sql);
 
        $orderids = [];
        while ($row = $GLOBALS['db']->fetchRow($res))
        {
            $orderids[] = $row['order_id'];
            $row['shipping_status'] = ($row['shipping_status'] == SS_SHIPPED_ING) ? SS_PREPARING : $row['shipping_status'];
 
            $arr[$row['order_id']] = array('order_id'       => $row['order_id'],
                           'order_sn'       => $row['order_sn'],
                           'order_time'     => local_date($GLOBALS['_CFG']['time_format'], $row['add_time']),
                           'order_status'   => $row['order_status'],
                           'shipping_status'   => $row['shipping_status'],
                           'pay_status'   => $row['pay_status'],
                           'total_fee'      => price_format($row['total_fee'], false),
                           'needComment' => isset($needCommentOrderids[$row['order_id']]) ? true : false,
                           'goods' => [],
                    );
        }
        $orders = $arr;
        $count = 0;
        if ($orderids) {
            $orderidsStr = implode(',', $orderids);
 
            /* 订单商品 */
            $params = [
                'alias' => 'og',
                'columns' => "og.rec_id, og.order_id, og.goods_id, og.goods_name, og.goods_sn, og.market_price, og.goods_number, " .
                    "og.goods_price, og.goods_attr, og.is_real, og.parent_id, og.is_gift, " .
                    "og.goods_price * og.goods_number AS subtotal, og.extension_code, g.goods_thumb, g.goods_img " .
                    ',c.comment_id ',
                'conditions' =>  "og.order_id IN ($orderidsStr)",
                'offset' => $offset,
                'limit' => $limit,
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = og.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'comment',
                        'alias' => 'c',
                        'on' => 'c.id_value = og.goods_id AND c.order_id = og.order_id '
                    ]
                ],
            ];
            $colls = Model\OrderGoods::find($params);
            unset($params['limit'], $params['offset']);
            $count = Model\OrderGoods::count($params);
            foreach ($colls as $row) {
                if (isset($orders[$row['order_id']])) {
                    $row['market_price'] = price_format($row['market_price'], false);
                    $row['goods_price']  = price_format($row['goods_price'], false);
                    $row['subtotal']     = price_format($row['subtotal'], false);
                    $orders[$row['order_id']]['goods'][] = $row;
                }
            }
        }
 
        $totalPage = $count ? intval(($count - 1) / $limit) + 1 : 1;
 
        $data = [];
        $data['user'] = $user;
        $data['page'] = $page;
        $data['totalPage'] = $totalPage;
        $data['count'] = $count;
        $data['orders'] = $orders;
        $data['navName'] = '';
        $data['userSidebar'] = 'bought-list';
        echo $this->viewoutput('pc/bought-list', $data);
    }
 
    /**
     * 我的收藏
     */
    public function collectListAction()
    {
        global $db, $ecs;
        $user = $this->auth->user();
        $page = intval($this->request->getQuery('page')) ?: 1;
        $ctype = $this->request->getQuery('collect_type') == 'brand' ? 'brand' : 'goods';
        $limit = 10;
        $offset = ($page - 1) * $limit;
        $userid = $user['user_id'];
 
        $data = [];
        if ($ctype == 'goods') {
            $params = [
                'alias' => 'c',
                'columns' => 'g.goods_id, g.goods_name, g.market_price, g.shop_price AS org_price, '.
                    "IFNULL(mp.user_price, g.shop_price * '$_SESSION[discount]') AS shop_price, g.goods_img, ".
                    'g.promote_price, g.promote_start_date,g.promote_end_date, c.rec_id, c.is_attention',
                'conditions' =>  "c.user_id = '$userid' ORDER BY c.rec_id DESC",
                'offset' => $offset,
                'limit' => $limit,
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = c.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'member_price',
                        'alias' => 'mp',
                        'on' => "mp.goods_id = g.goods_id AND mp.user_rank = '$_SESSION[user_rank]' "
                    ],
                ],
            ];
            $goodsList = Model\CollectGoods::find($params);
            foreach ($goodsList as &$row) {
                if ($row['promote_price'] > 0) {
                    $promote_price = bargain_price($row['promote_price'], $row['promote_start_date'], $row['promote_end_date']);
                    $row['promote_price'] = $promote_price > 0 ? price_format($promote_price) : '';
                } else {
                    $row['promote_price'] = '';
                }
                if ($row['promote_price'] > 0) {
                    $row['price'] = $row['promote_price'];
                } else {
                    $row['price'] = $row['shop_price'];
                }
            }
            unset($row);
            unset($params['limit'], $params['offset']);
            $count = Model\CollectGoods::count($params);
 
            $data['list'] = $goodsList;
        } else {
            $params = [
                'alias' => 'cb',
                'columns' => 'b.*',
                'conditions' =>  "cb.user_id = '$userid'",
                'offset' => $offset,
                'limit' => $limit,
                'order' => 'cb.id DESC',
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'brand',
                        'alias' => 'b',
                        'on' => 'b.brand_id = cb.brand_id'
                    ]
                ],
            ];
            $brandList = Model\CollectBrand::find($params);
            foreach ($brandList as &$row) {
 
            }
            unset($row);
            unset($params['limit'], $params['offset']);
            $count = Model\CollectBrand::count($params);
 
            $data['list'] = $brandList;
        }
        $paginator = new \Juice\Paginator(['type' => 'emptyArray', 'config' => [
            'count' => $count,
            'limit' => $limit,
            'page' => $page,
        ]]);
        $paginate = $paginator->getPaginate();
 
        $data['user'] = $user;
        $data['ctype'] = $ctype;
        $data['paginate'] = $paginate;
        $data['navName'] = '';
        $data['userSidebar'] = 'collect-list';
        echo $this->viewoutput('pc/collect-list', $data);
    }
    /**
     * 评论商品
     */
    public function commentAction()
    {
        global $db, $ecs;
        $orderid = intval($this->request->getQuery('order_id'));
        $goodsid = intval($this->request->getQuery('goods_id'));
        if (! $orderid || ! $goodsid) {
            return $this->response->redirect('user/bought/list');
        }
        $user = $this->auth->user();
        $goods = Model\OrderGoods::findFirst([
            'alias' => 'og',
            'columns' => "og.rec_id, og.goods_id, og.goods_name, og.goods_attr, og.goods_sn, og.goods_number, goods_price, g.goods_thumb, o.add_time, o.order_id",
            'conditions' =>  "og.order_id = $orderid AND og.goods_id = $goodsid AND o.user_id = " . $user['user_id'] . ' AND o.shipping_status = 2 AND c.comment_id IS NULL',
            'join' => [
                [
                    'type' => 'INNER',
                    'table' => 'order_info',
                    'alias' => 'o',
                    'on' => 'o.order_id = og.order_id'
                ],
                [
                    'type' => 'INNER',
                    'table' => 'goods',
                    'alias' => 'g',
                    'on' => 'g.goods_id = og.goods_id'
                ],
                [
                    'type' => 'LEFT',
                    'table' => 'comment',
                    'alias' => 'c',
                    'on' => 'c.order_id = og.order_id AND c.id_value = og.goods_id'
                ]
            ],
        ]);
        if (empty($goods)) {
            return $this->response->redirect('user/bought/list');
        }
        $goods['add_time_format'] = local_date('Y-m-d', $goods['add_time']);
 
        $data = [];
        $data['user'] = $user;
        $data['goods'] = $goods;
        $data['navName'] = '';
        $data['userSidebar'] = 'bought-list';
        echo $this->viewoutput('pc/comment', $data);
    }
 
    /**
     * 订单列表
     */
    public function orderListAction()
    {
        global $db, $ecs;
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'includes/lib_order.php');
 
        $data = [];
        $user = $this->auth->user();
        $mcart = new Model\Cart;
 
        $page = intval($this->request->getQuery('page')) ?: 1;
        $limit = 10;
        $offset = ($page - 1) * $limit;
 
        $status = $this->request->getQuery('status') ?: 0;
        $userid = $_SESSION['user_id'];
        $morder = new Model\OrderInfo;
 
        // 需要评论的order_id
        $result = $morder->needComment();
        $needCommentOrderids = [];
        foreach ($result as $row) {
            $needCommentOrderids[$row['order_id']] = $row['order_id'];
        }
 
        if (! empty($status)) {
            $orderAttr = Model\OrderInfo::$orderStatus[$status];
        }
        $ossql = $sssql = $pssql = '';
        if (! empty($orderAttr['os'])) {
            $ossql = ' AND ' . db_create_in($orderAttr['os'], 'o.order_status');
        }
        if (! empty($orderAttr['ss'])) {
            $sssql = ' AND ' . db_create_in($orderAttr['ss'], 'o.shipping_status');
        }
        if (! empty($orderAttr['ps'])) {
            $pssql = ' AND ' . db_create_in($orderAttr['ps'], 'o.pay_status');
        }
 
        $conditions = '1 = 1';
        if ($status == 4) {
            if (empty($needCommentOrderids)) {
                $conditions .= ' AND 1 = 2';
            } else {
                $conditions .= ' AND o.order_id IN (' . implode(',', $needCommentOrderids) . ')';
            }
        }
        $conditions .= " AND o.user_id = '$userid' $ossql $sssql $pssql ORDER BY o.add_time DESC LIMIT $offset, $limit";
        $colls = Model\OrderInfo::find([
            'alias' => 'o',
            'columns' => "o.order_id, o.order_sn, o.order_status, o.shipping_status, o.pay_status, o.add_time, " .
                         "(o.goods_amount + o.shipping_fee + o.insure_fee + o.pay_fee + o.pack_fee + o.card_fee + o.tax - o.discount) AS total_fee, " .
                         "do.delivery_id, do.delivery_sn"
            ,
            'conditions' => $conditions,
            'join' => [
                [
                    'type' => 'LEFT',
                    'table' => 'delivery_order',
                    'alias' => 'do',
                    'on' => 'o.order_id = do.order_id'
                ]
            ],
        ]);
 
        $orderids = [];
        $orders = [];
        foreach ($colls as $row) {
            $orderids[] = $row['order_id'];
            $row['shipping_status'] = ($row['shipping_status'] == SS_SHIPPED_ING) ? SS_PREPARING : $row['shipping_status'];
 
            $row['status'] = Model\OrderInfo::getOrderStatus($row);
            $orders[$row['order_id']] = [
                'order_id'       => $row['order_id'],
                'status' => $row['status'],
                'order_sn'       => $row['order_sn'],
                'order_time'     => local_date($GLOBALS['_CFG']['time_format'], $row['add_time']),
                'order_status'   => $row['order_status'],
                'shipping_status'   => $row['shipping_status'],
                'pay_status'   => $row['pay_status'],
                'total_fee'      => price_format($row['total_fee'], false),
                'delivery_id'       => $row['delivery_id'],
                'delivery_sn'       => $row['delivery_sn'],
                'needComment' => isset($needCommentOrderids[$row['order_id']]) ? true : false,
                'goods' => [],
            ];
        }
 
        $statistics = $morder->getAllCount();
        $count = empty($status) ? $statistics['total'] : $statistics['count'][$status];
        $paginator = new \Juice\Paginator(['type' => 'emptyArray', 'config' => [
            'count' => $count,
            'limit' => $limit,
            'page' => $page,
        ]]);
        $paginate = $paginator->getPaginate();
        $data['paginate'] = $paginate;
 
        if ($orderids) {
            $orderidsStr = implode(',', $orderids);
            $params = [
                'alias' => 'og',
                'columns' => "og.rec_id, og.order_id, og.goods_id, og.goods_name, og.goods_sn, og.market_price, og.goods_number, " .
                    "og.goods_price, og.goods_attr, og.is_real, og.parent_id, og.is_gift, " .
                    "og.goods_price * og.goods_number AS subtotal, og.extension_code, g.goods_thumb, g.goods_img " .
                    ',c.comment_id ',
                'conditions' =>  "og.order_id IN ($orderidsStr)",
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = og.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'comment',
                        'alias' => 'c',
                        'on' => 'c.id_value = og.goods_id AND c.order_id = og.order_id '
                    ],
                ],
            ];
 
            $colls = Model\OrderGoods::find($params);
 
            foreach ($colls as $row) {
                if (isset($orders[$row['order_id']])) {
                    $row['market_price'] = price_format($row['market_price'], false);
                    $row['goods_price']  = price_format($row['goods_price'], false);
                    $row['subtotal']     = price_format($row['subtotal'], false);
                    $orders[$row['order_id']]['goods'][] = $row;
                }
            }
        }
 
        $data['user'] = $user;
        $data['orders'] = $orders;
        $data['status'] = $status;
        $data['statistics'] = $statistics;
        $data['navName'] = '';
        $data['userSidebar'] = 'order-list';
        echo $this->viewoutput('pc/order-list', $data);
    }
 
    /**
     * 地址
     */
    public function addressListAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        $user = $this->auth->user();
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
 
        $addressList = Model\UserAddress::findByUserId($user['user_id']);
        $regionsids = [];
        foreach ($addressList as $region_id => $row) {
            $row['province'] = isset($row['province']) ? intval($row['province']) : 0;
            $row['city']     = isset($row['city'])     ? intval($row['city'])     : 0;
            $row['district']  = isset($row['district'])  ? intval($row['district'])  : 0;
 
            $regionsids[] = $row['district'];
            $regionsids[] = $row['province'];
            $regionsids[] = $row['city'];
        }
        $regions = [];
        if ($regionsids) {
            $idsstr = implode(', ', $regionsids);
            $sql = 'SELECT region_id, region_name FROM ' . $GLOBALS['ecs']->table('region') .
                    " WHERE region_id IN ($idsstr)";
            $res = $GLOBALS['db']->query($sql);
            while ($row = $GLOBALS['db']->fetchRow($res)) {
                $regions[$row['region_id']] = $row;
            }
        }
        foreach ($addressList as $key => &$row) {
            $row['province_name'] = isset($regions[$row['province']]) ? $regions[$row['province']]['region_name'] : '';
            $row['city_name']     = isset($regions[$row['city']])     ? $regions[$row['city']]['region_name']     : '';
            $row['district_name'] = isset($regions[$row['district']])  ? $regions[$row['district']]['region_name']  : '';
        }
        unset($row);
 
        $status = $this->request->getQuery('status');
        $_SESSION['origin_from'] = $status;
        $regions = Model\Region::getAll();
 
        $data = [];
        $data['user'] = $user;
        $data['addressList'] = $addressList;
        $data['regions'] = $regions;
        $data['navName'] = '';
        $data['userSidebar'] = 'address-list';
        echo $this->viewoutput('pc/address-list', $data);
    }
 
    /**
     * 代理信息
     */
    public function agentInfoAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
 
        $user = $this->auth->user();
        $data = [];
        $agent = Model\Agent::findFirstByAgentId($user['agent_id']);
        if (empty($agent)) {
            $data['isEdit'] = false;
        } else {
            $data['isEdit'] = true;
        }
 
        $data['user'] = $user;
        $data['agent'] = $agent;
        $data['navName'] = '';
        $data['userSidebar'] = 'agent';
        echo $this->viewoutput('pc/agent-info', $data);
    }
 
    /**
     * 代理订单信息
     */
    public function agentOrderListAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
 
        $user = $this->auth->user();
 
        $page = intval($this->request->getQuery('page')) ?: 1;
        $limit = 10;
        $offset = ($page - 1) * $limit;
 
        $ossql = $sssql = $pssql = '';
        if (! empty($orderAttr['os'])) {
            $ossql = ' AND ' . db_create_in($orderAttr['os'], 'order_status');
        }
        if (! empty($orderAttr['ss'])) {
            $sssql = ' AND ' . db_create_in($orderAttr['ss'], 'shipping_status');
        }
        if (! empty($orderAttr['ps'])) {
            $pssql = ' AND ' . db_create_in($orderAttr['ps'], 'pay_status');
        }
        $orderids = [];
        $arr = [];
        $count = 0;
        if ($user['agent_id']) {
            $params = [
                'alias' => 'o',
                'columns' => "o.order_id, o.order_sn, o.order_status, o.shipping_status, o.pay_status, o.add_time, " .
                   "(o.goods_amount + o.shipping_fee + o.insure_fee + o.pay_fee + o.pack_fee + o.card_fee + o.tax - o.discount) AS total_fee , u.user_name",
                'conditions' =>  "o.agent_id = '{$user['agent_id']}' $ossql $sssql $pssql",
                'offset' => $offset,
                'limit' => $limit,
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'users',
                        'alias' => 'u',
                        'on' => 'u.user_id = o.user_id'
                    ]
                ],
                'order' => 'add_time DESC',
            ];
            $colls = Model\OrderInfo::find($params);
 
            foreach ($colls as $row)
            {
                $orderids[] = $row['order_id'];
                $row['shipping_status'] = ($row['shipping_status'] == SS_SHIPPED_ING) ? SS_PREPARING : $row['shipping_status'];
 
                $row['status'] = Model\OrderInfo::getOrderStatus($row);
                $orderTime = local_date($GLOBALS['_CFG']['time_format'], $row['add_time']);
                $dateArr = explode(' ', $orderTime);
                $arr[$row['order_id']] = [
                    'order_id'       => $row['order_id'],
                    'user_name' => $row['user_name'],
                    'status' => $row['status'],
                    'order_sn'       => $row['order_sn'],
                    'order_time'     => $orderTime,
                    'order_time_date'     => $orderTime,
                    'order_time_arr'     => $dateArr,
                    'order_status'   => $row['order_status'],
                    'shipping_status'   => $row['shipping_status'],
                    'pay_status'   => $row['pay_status'],
                    'total_fee'      => price_format($row['total_fee'], false),
                    'goods' => [],
                ];
            }
            unset($params['limit'], $params['offset']);
            $count = Model\OrderInfo::count($params);
        }
 
        $paginator = new \Juice\Paginator(['type' => 'emptyArray', 'config' => [
            'count' => $count,
            'limit' => $limit,
            'page' => $page,
        ]]);
        $paginate = $paginator->getPaginate();
 
        $orders = $arr;
        if ($orderids) {
            $orderidsStr = implode(',', $orderids);
            $params = [
                'alias' => 'og',
                'columns' => "og.rec_id, og.order_id, og.goods_id, og.goods_name, og.goods_sn, og.market_price, og.goods_number, " .
                    "og.goods_price, og.goods_attr, og.is_real, og.parent_id, og.is_gift, " .
                    "og.goods_price * og.goods_number AS subtotal, og.extension_code, g.goods_thumb, g.goods_img " .
                    ',c.comment_id ',
                'conditions' =>  "og.order_id IN ($orderidsStr)",
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = og.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'comment',
                        'alias' => 'c',
                        'on' => 'c.id_value = og.goods_id AND c.order_id = og.order_id '
                    ]
                ],
            ];
            $colls = Model\OrderGoods::find($params);
 
            foreach ($colls as $row) {
                if (isset($orders[$row['order_id']])) {
                    $row['market_price'] = price_format($row['market_price'], false);
                    $row['goods_price']  = price_format($row['goods_price'], false);
                    $row['subtotal']     = price_format($row['subtotal'], false);
                    $orders[$row['order_id']]['goods'][] = $row;
                }
            }
        }
 
        $data = [];
        $data['user'] = $user;
        $data['orders'] = $orders;
        $data['paginate'] = $paginate;
        $data['navName'] = '';
        $data['userSidebar'] = 'agent-order-list';
        echo $this->viewoutput('pc/agent-order-list', $data);
    }
 
    /**
     * 绑定微信号
     */
    public function wechatBindAction()
    {
        $user = $this->auth->user();
 
        $wechat = new WechatSDK;
        $url = 'http://www.liowang.com/shop/wechat/oauth2/callback/bind';
        $state = ['env' => 'product'];
        if (isLocal()) {
            $state = ['env' => 'local'];
        }
        return $this->response->redirect($wechat->pcWebOauth2($url, $state), true);
    }
    /**
     * PC 微信回调 oauth2 绑定已登录用户
     */
    public function wechatOauth2CallbackBindAction()
    {
        global $ecs, $db, $user;
        include_once(ROOT_PATH . 'includes/lib_passport.php');
        $code = $this->request->getQuery('code');
        if (empty($code)) {
            echo '授权失败';
            exit;
        }
        $state = $this->request->getQuery('state');
        parse_str($state, $params);
        if (! islocal() && ! empty($params['env']) && $params['env'] == 'local') {
            return $this->response->redirect('http://liowang.dev.seafarer.me/shop/wechat/oauth2/callback/bind?' . http_build_query($this->request->getQuery()));
        }
 
        $wechat = new WechatSDK;
        $config = $this->config->wechat;
        $config->appid = $config->open_web_appid;
        $config->appsecret = $config->open_web_secret;
        $wechat->init($config);
        $response = $wechat->getUserTokenByCode($code);
        if (empty($response['errcode'])) {
            // 授权成功
            $ewxpcuser = Model\WxPcUser::findFirstByOpenid($response['openid']);
 
            $data = [];
            $isFirst = false;
            $isNeedCreate = false;
            $uuser = [];
            $wxpcuser = [];
            if ($ewxpcuser) {
                // 用户已经存在
                $euser = Model\User::findFirstByWxPcUserid($ewxpcuser['id']);
            } else {
                // 第一次访问
                $isFirst = true;
                $wxuser = Model\WxUser::findFirstByUnionid($response['unionid']);
                if (empty($wxuser)) {
                    $isNeedCreate = true;
                } else {
                    $euser = Model\User::findFirstByWxUserid($wxuser['id']);
                }
            }
            $wxpcuser['openid'] = $response['openid'];
            $wxpcuser['unionid'] = $response['unionid'];
            $wxpcuser['access_token'] = $response['access_token'];
            $wxpcuser['refresh_token'] = $response['refresh_token'];
            $wxpcuser['expired_at'] = $response['expires_in'] + time() - 200;
            $wxpcuser['scope'] = $response['scope'];
            $wxpcuser['ip'] = $this->request->getClientAddress();
            $info = $wechat->getUserInfoByOauth($response['openid'], $response['access_token']);
            $wxpcuser['nickname'] = $info['nickname'];
            $wxpcuser['gender'] = $info['sex'];
            $wxpcuser['language'] = $info['language'];
            $wxpcuser['city'] = $info['city'];
            $wxpcuser['country'] = $info['country'];
            $wxpcuser['province'] = $info['province'];
            $wxpcuser['headimgurl'] = $info['headimgurl'];
 
            if (! empty($euser) && $euser['user_id'] != $_SESSION['user_id']) {
                exit('该微信已被绑定');
            }
 
            if ($isFirst) {
                // 首次PC扫码登录
                $id = Model\WxPcUser::insert($wxpcuser);
                $uuser['wx_pc_userid'] = $id;
                Model\User::update($uuser, "user_id = {$_SESSION['user_id']}");
 
            } else {
                $uuser['wx_pc_userid'] = $ewxpcuser['id'];
                Model\User::update($uuser, "user_id = {$_SESSION['user_id']}");
                Model\WxPcUser::update($wxpcuser, "openid = '{$response['openid']}'");
            }
        } else {
            SystemException::error($response['errmsg']);
        }
        $referer = $this->url->get('user/info');
        return $this->response->redirect($referer, true);
    }
 
}
#5PageUserController->loginAction()
#6Phalcon\Dispatcher->dispatch()
#7Phalcon\Mvc\Application->handle()
/usr/local/www/liowang-shop-robotwar/shop/public/index.php (36)
<?php
 
error_reporting(E_ALL);
 
try {
 
    define('BASE_PATH', realpath('..') . '/');
    define('APP_PATH', BASE_PATH . 'app/');
    define('STORAGE_PATH', BASE_PATH . 'storage/');
 
    // 系统环境变量,参考laravel模式
    $systemEnvs = [
        'local' => [
            'juice-pc'
        ],
        'production' => [
            '10-10-92-111'
        ],
        'testing' => [
            '10-10-13-185'
        ],
    ];
 
    /**
     * 项目引导文件
     */
    $config = include __DIR__ . "/../app/start/start.php";
 
    /**
     * Handle the request
     */
    $application = new \Phalcon\Mvc\Application($di);
 
    $application->useImplicitView(false); // 禁用自动渲染
 
    echo $application->handle()->getContent();
 
} catch (\Exception $e) {
    //echo $e;
    throw $e;
Phalcon Framework 2.0.13

Warning: Cannot modify header information - headers already sent by (output started at /usr/local/www/liowang-shop-robotwar/shop/app/libraries/PrettyExceptions/Library/Phalcon/Utils/PrettyExceptions.php:339) in /usr/local/www/liowang-shop-robotwar/shop/app/libraries/PrettyExceptions/Library/Phalcon/Utils/PrettyExceptions.php on line 440
Exception - Undefined index: access_token
Undefined index: access_token
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (260)
#1{closure}(8, Undefined index: access_token, /usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php, 260, Array(9))
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (260)
<?php
 
/**
 * weixin library
 * @author geekzhumail@gmail.com
 * @since 2014-09-24
 * @edit 2015-03-05
 */
class SDKRuntimeException extends Exception {
 
    public function errorMessage() {
        return $this->getMessage();
    }
}
 
class WechatSDK extends WechatBase{
 
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => 'wxdf0a039cf4ea94d1',
        'appsecret' => '7100f0fe837a5287f075b6d2d9814ca0',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/';
    /**
     * @var access_token
     */
    protected $accessToken = '';
 
    /**
     * 是否从远程服务器拿token
     */
    protected $isRemote = false;
 
    public function __construct() {
        parent::__construct();
        $this->isRemote = $this->config->is_remote;
        $this->accessToken = $this->getToken();
    }
 
    /**
     * 初始化数据
     */
    public function init($config) {
        $this->config = $config;
    }
 
    /**
     * 获取Appid
     */
    public function getAppid() {
        return $this->config['appid'];
    }
 
    /**
     * weixin signature
     */
    public function checkSignature($signature, $timestamp, $nonce) {
        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
 
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
 
    /**
     * web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function webOauth2($redirectUri, $state = [], $type = 'snsapi_base') {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = $type;
        if (empty($state['type'])) {
            $state['type'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * pc web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function pcWebOauth2($redirectUri, $state = []) {
        $params = [];
        $params['appid'] = $this->config['open_web_appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = 'snsapi_login';
        if (empty($state['env'])) {
            $state['env'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * get access token by code
     */
    public function getUserTokenByCode($code) {
        $params = array();
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $params['code'] = $code;
        $params['grant_type'] = 'authorization_code';
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * refresh access token
     * @param string $token 填写通过access_token获取到的refresh_token参数
     */
    public function refreshUserToken($token) {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['grant_type'] = 'refresh_token';
        $params['refresh_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 根据网页授权拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function getUserInfoByOauth($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('sns/userinfo', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 验证授权凭证(access_token)是否有效
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function validUserToken($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/auth?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 获取公众号token
     */
    public function getToken() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        if ($this->isRemote) {
            return $this->getTokenFromRemote();
        }
 
        $params = [];
        $params['grant_type'] = 'client_credential';
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $url = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        // 写入缓存
        $expiredAt = $response['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $response['access_token'], $expiredAt);
        return $response['access_token'];
    }
 
    /**
     * 获取access_token from remote
     */
    public function getTokenFromRemote() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/token/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $cache->save('wechat_access_token_expired', $resp['expired_at'], $expiredAt);
            $expiredAt = 120;
            $cache->save($key, $resp['access_token'], $expiredAt);
            return $resp['access_token'];
        } else {
            throw new Exception('wx admin get token error');
        }
    }
 
    /**
     * 获取用户基本信息(包括UnionID机制)
     * @param string $openid 用户的唯一标识
     * @param string $token 调用接口凭证,默认值为Wechat::getToken()
     */
    public function getUserInfo($openid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('cgi-bin/user/info', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 下载已经上传到微信的资源文件
     */
    public function downloadMedia($mediaid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['access_token'] = $token;
        $params['media_id'] = $mediaid;
        $url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $content = curl_exec($ch);
        $info = curl_getinfo($ch);
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            // 正常则分割header和body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($content, 0, $headerSize);
            $body = substr($content, $headerSize);
        }
        curl_close($ch);
        $resp['info'] = $info;
        $resp['header'] = $header;
        $resp['body'] = $body;
        return $resp;
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicket()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        if ($this->isRemote) {
            return $this->getJsapiTicketFromRemote();
        }
        $resp = json_decode($this->get('cgi-bin/ticket/getticket', ['access_token' => $this->accessToken, 'type' => 'jsapi']), true);
 
        // 写入缓存
        $expiredAt = $resp['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $resp['ticket'], $expiredAt);
        return $resp['ticket'];
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicketFromRemote()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/jsticket/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $expiredAt = 120;
            $cache->save($key, $resp['ticket'], $expiredAt);
            return $resp['ticket'];
        } else {
            throw new Exception('wx admin get ticket error');
        }
    }
    /**
     * 获取js api 签名
     */
    public function getSignPackage($url = '')
    {
        $jsticket = $this->getJsapiTicket();
 
        if (! $url) {
            $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
        }
        $timestamp = time();
        $nonceStr = $this->createNoncestr(16);
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket={$jsticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        return [
            "appId"     => $this->config['appid'],
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }
 
 
    /**
     * 新建一个类,由于PSR-4无法获取不以文件名命名的类;
     */
    public function newClass($name)
    {
        return new $name();
    }
 
    // 微信支付
 
    /**
     * 获取微信付款码的prepayid
     * @params array $params 参数
     */
    public function getPrepayId($params) {
        $sdk = new UnifiedOrderSDK;
        $sdk->setParameter("trade_type","JSAPI");//交易类型
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $prepayid = $sdk->getPrepayId();
        return $prepayid;
    }
 
    /**
     * 获取支付js api 签名
     */
    public function getPaySignPackage($prepayid)
    {
        $jsApiObj["appId"] = $this->config['appid'];
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->createNoncestr();
        $jsApiObj["package"] = "prepay_id=$prepayid";
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->getSign($jsApiObj);
        $signPackage = $jsApiObj;
 
        return $signPackage;
    }
 
    /**
     * 发送红包接口
     * @params array $params 参数
     */
    public function sendRedPack($params) {
        $sdk = new RedPackSDK;
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $result = $sdk->send();
        return $result;
    }
}
 
/**
 * 所有接口的基类
 */
class WechatBase
{
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => '',
        'appsecret' => '',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/cgi-bin/';
 
    public function __construct() {
        $this->di = Phalcon\DI::getDefault();
        $this->config = $this->di->getConfig()['wechat'];
    }
 
    protected function get($url, $params = '')
    {
        if (strpos($url, 'https://') === false && strpos($url, 'http://') === false) {
            $url = $this->apiPrefix.$url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl get error');
        }
    }
 
    protected function post($url, $params = '', $data = '')
    {
        if (strpos($url, 'https://') === false) {
            $url = $this->apiPrefix . $url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        if ($data) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl post error');
        }
    }
 
    public function trimString($value)
    {
        $ret = null;
        if (null != $value)
        {
            $ret = $value;
            if (strlen($ret) == 0)
            {
                $ret = null;
            }
        }
        return $ret;
    }
 
    /**
     *   作用:产生随机字符串,不长于32位
     */
    public function createNoncestr( $length = 32, $type = 'all')
    {
        if ($type == 'all') {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        } else if($type == 'number') {
            $chars = "0123456789";
        }
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
 
    /**
     *   作用:格式化参数,签名过程需要使用
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $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['key'];
        //echo "【string2】".$String."</br>";
        //签名步骤三:MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }
 
    /**
     *   作用:array转xml
     */
    public 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;
    }
 
    /**
     *   作用:将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;
    }
 
    /**
     *   作用:以post方式提交xml到对应的接口url
     */
    public function postXmlCurl($xml,$url,$second=30)
    {
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        //返回结果
        if($data)
        {
            return $data;
        }
        else
        {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            return false;
        }
    }
 
    /**
     *   作用:使用证书,以post方式提交xml到对应的接口url
     */
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, $this->config['sslcert_path']);
        //默认格式为PEM,可以注释
        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);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return false;
        }
    }
 
}
 
/**
 * 请求型接口的基类
 */
class WechatPayRequestBase extends WechatBase
{
    var $parameters;//请求参数,类型为关联数组
    public $response;//微信返回的响应
    public $result;//返回参数,类型为关联数组
    var $url;//接口链接
    var $curl_timeout;//curl超时时间
 
    /**
     *   作用:设置请求参数
     */
    function setParameter($parameter, $parameterValue)
    {
        $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
    }
 
    /**
     *   作用:设置标配的请求参数,生成签名,生成接口参数xml
     */
    function createXml()
    {
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     *   作用:post请求xml
     */
    function postXml()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:使用证书post请求xml
     */
    function postXmlSSL()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:获取结果,默认不使用证书
     */
    function getResult()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        return $this->result;
    }
}
 
/**
 * 统一支付接口类
 */
class UnifiedOrderSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        //检测必填参数
        if(empty($this->parameters["out_trade_no"]))
        {
            throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
        }elseif(empty($this->parameters["body"])){
            throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
        }elseif (empty($this->parameters["total_fee"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
        }elseif (empty($this->parameters["notify_url"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
        }elseif (empty($this->parameters["trade_type"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
        }elseif ($this->parameters["trade_type"] == "JSAPI" &&
            empty($this->parameters["openid"])){
            throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
        }
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["spbill_create_ip"] = $this->di->getRequest()->getClientAddress();//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 获取prepay_id
     */
    function getPrepayId()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        $prepay_id = $this->result["prepay_id"];
        return $prepay_id;
    }
 
}
 
/**
 * 响应型接口基类
 */
class WechatPayServerBase extends WechatBase
{
  public $data;//接收到的数据,类型为关联数组
  var $returnParameters;//返回参数,类型为关联数组
 
  /**
   * 将微信的请求xml转换成关联数组,以方便数据处理
   */
  function saveData($xml)
  {
    $this->data = $this->xmlToArray($xml);
  }
 
  function checkSign()
  {
    $tmpData = $this->data;
    unset($tmpData['sign']);
    $sign = $this->getSign($tmpData);//本地签名
        if (isLocal()) {
            echo $sign;
        }
    if ($this->data['sign'] == $sign) {
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * 获取微信的请求数据
   */
  function getData()
  {
    return $this->data;
  }
 
  /**
   * 设置返回微信的xml数据
   */
  function setReturnParameter($parameter, $parameterValue)
  {
    $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
  }
 
  /**
   * 生成接口参数xml
   */
  function createXml()
  {
    return $this->arrayToXml($this->returnParameters);
  }
 
  /**
   * 将xml数据返回微信
   */
  function returnXml()
  {
    $returnXml = $this->createXml();
    return $returnXml;
  }
}
 
/**
 * 红包发送接口类
 */
class RedPackSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        $requiredParams = [
            'mch_billno',
            'nick_name',
            'send_name',
            're_openid',
            'total_amount',
            'min_value',
            'max_value',
            'total_num',
            'wishing',
            'act_name',
            'remark',
        ];
        //检测必填参数
        foreach ($requiredParams as $val) {
            if (empty($this->parameters[$val])) {
                throw new SDKRuntimeException("缺少红包接口必填参数{$val}!"."<br>");
            }
        }
        $this->parameters["wxappid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["client_ip"] = gethostbyname(gethostname());//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 发送红包
     *
     * curl 返回例子
     *  <xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[发放成功.]]></return_msg>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <err_code><![CDATA[0]]></err_code>
        <err_code_des><![CDATA[发放成功.]]></err_code_des>
        <mch_billno><![CDATA[1228563102201503261427336509]]></mch_billno>
        <mch_id>1228563102</mch_id>
        <wxappid><![CDATA[wxdceccb9098a978b8]]></wxappid>
        <re_openid><![CDATA[o55e0t1NHPxdcRCjbIu_xnjHc2u8]]></re_openid>
        <total_amount>100</total_amount>
        </xml>
     */
    public function send() {
        $this->postXmlSSL();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        return $this->result;
    }
#2WechatSDK->getTokenFromRemote()
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (209)
<?php
 
/**
 * weixin library
 * @author geekzhumail@gmail.com
 * @since 2014-09-24
 * @edit 2015-03-05
 */
class SDKRuntimeException extends Exception {
 
    public function errorMessage() {
        return $this->getMessage();
    }
}
 
class WechatSDK extends WechatBase{
 
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => 'wxdf0a039cf4ea94d1',
        'appsecret' => '7100f0fe837a5287f075b6d2d9814ca0',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/';
    /**
     * @var access_token
     */
    protected $accessToken = '';
 
    /**
     * 是否从远程服务器拿token
     */
    protected $isRemote = false;
 
    public function __construct() {
        parent::__construct();
        $this->isRemote = $this->config->is_remote;
        $this->accessToken = $this->getToken();
    }
 
    /**
     * 初始化数据
     */
    public function init($config) {
        $this->config = $config;
    }
 
    /**
     * 获取Appid
     */
    public function getAppid() {
        return $this->config['appid'];
    }
 
    /**
     * weixin signature
     */
    public function checkSignature($signature, $timestamp, $nonce) {
        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
 
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
 
    /**
     * web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function webOauth2($redirectUri, $state = [], $type = 'snsapi_base') {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = $type;
        if (empty($state['type'])) {
            $state['type'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * pc web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function pcWebOauth2($redirectUri, $state = []) {
        $params = [];
        $params['appid'] = $this->config['open_web_appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = 'snsapi_login';
        if (empty($state['env'])) {
            $state['env'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * get access token by code
     */
    public function getUserTokenByCode($code) {
        $params = array();
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $params['code'] = $code;
        $params['grant_type'] = 'authorization_code';
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * refresh access token
     * @param string $token 填写通过access_token获取到的refresh_token参数
     */
    public function refreshUserToken($token) {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['grant_type'] = 'refresh_token';
        $params['refresh_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 根据网页授权拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function getUserInfoByOauth($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('sns/userinfo', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 验证授权凭证(access_token)是否有效
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function validUserToken($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/auth?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 获取公众号token
     */
    public function getToken() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        if ($this->isRemote) {
            return $this->getTokenFromRemote();
        }
 
        $params = [];
        $params['grant_type'] = 'client_credential';
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $url = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        // 写入缓存
        $expiredAt = $response['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $response['access_token'], $expiredAt);
        return $response['access_token'];
    }
 
    /**
     * 获取access_token from remote
     */
    public function getTokenFromRemote() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/token/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $cache->save('wechat_access_token_expired', $resp['expired_at'], $expiredAt);
            $expiredAt = 120;
            $cache->save($key, $resp['access_token'], $expiredAt);
            return $resp['access_token'];
        } else {
            throw new Exception('wx admin get token error');
        }
    }
 
    /**
     * 获取用户基本信息(包括UnionID机制)
     * @param string $openid 用户的唯一标识
     * @param string $token 调用接口凭证,默认值为Wechat::getToken()
     */
    public function getUserInfo($openid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('cgi-bin/user/info', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 下载已经上传到微信的资源文件
     */
    public function downloadMedia($mediaid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['access_token'] = $token;
        $params['media_id'] = $mediaid;
        $url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $content = curl_exec($ch);
        $info = curl_getinfo($ch);
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            // 正常则分割header和body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($content, 0, $headerSize);
            $body = substr($content, $headerSize);
        }
        curl_close($ch);
        $resp['info'] = $info;
        $resp['header'] = $header;
        $resp['body'] = $body;
        return $resp;
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicket()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        if ($this->isRemote) {
            return $this->getJsapiTicketFromRemote();
        }
        $resp = json_decode($this->get('cgi-bin/ticket/getticket', ['access_token' => $this->accessToken, 'type' => 'jsapi']), true);
 
        // 写入缓存
        $expiredAt = $resp['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $resp['ticket'], $expiredAt);
        return $resp['ticket'];
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicketFromRemote()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/jsticket/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $expiredAt = 120;
            $cache->save($key, $resp['ticket'], $expiredAt);
            return $resp['ticket'];
        } else {
            throw new Exception('wx admin get ticket error');
        }
    }
    /**
     * 获取js api 签名
     */
    public function getSignPackage($url = '')
    {
        $jsticket = $this->getJsapiTicket();
 
        if (! $url) {
            $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
        }
        $timestamp = time();
        $nonceStr = $this->createNoncestr(16);
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket={$jsticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        return [
            "appId"     => $this->config['appid'],
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }
 
 
    /**
     * 新建一个类,由于PSR-4无法获取不以文件名命名的类;
     */
    public function newClass($name)
    {
        return new $name();
    }
 
    // 微信支付
 
    /**
     * 获取微信付款码的prepayid
     * @params array $params 参数
     */
    public function getPrepayId($params) {
        $sdk = new UnifiedOrderSDK;
        $sdk->setParameter("trade_type","JSAPI");//交易类型
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $prepayid = $sdk->getPrepayId();
        return $prepayid;
    }
 
    /**
     * 获取支付js api 签名
     */
    public function getPaySignPackage($prepayid)
    {
        $jsApiObj["appId"] = $this->config['appid'];
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->createNoncestr();
        $jsApiObj["package"] = "prepay_id=$prepayid";
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->getSign($jsApiObj);
        $signPackage = $jsApiObj;
 
        return $signPackage;
    }
 
    /**
     * 发送红包接口
     * @params array $params 参数
     */
    public function sendRedPack($params) {
        $sdk = new RedPackSDK;
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $result = $sdk->send();
        return $result;
    }
}
 
/**
 * 所有接口的基类
 */
class WechatBase
{
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => '',
        'appsecret' => '',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/cgi-bin/';
 
    public function __construct() {
        $this->di = Phalcon\DI::getDefault();
        $this->config = $this->di->getConfig()['wechat'];
    }
 
    protected function get($url, $params = '')
    {
        if (strpos($url, 'https://') === false && strpos($url, 'http://') === false) {
            $url = $this->apiPrefix.$url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl get error');
        }
    }
 
    protected function post($url, $params = '', $data = '')
    {
        if (strpos($url, 'https://') === false) {
            $url = $this->apiPrefix . $url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        if ($data) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl post error');
        }
    }
 
    public function trimString($value)
    {
        $ret = null;
        if (null != $value)
        {
            $ret = $value;
            if (strlen($ret) == 0)
            {
                $ret = null;
            }
        }
        return $ret;
    }
 
    /**
     *   作用:产生随机字符串,不长于32位
     */
    public function createNoncestr( $length = 32, $type = 'all')
    {
        if ($type == 'all') {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        } else if($type == 'number') {
            $chars = "0123456789";
        }
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
 
    /**
     *   作用:格式化参数,签名过程需要使用
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $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['key'];
        //echo "【string2】".$String."</br>";
        //签名步骤三:MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }
 
    /**
     *   作用:array转xml
     */
    public 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;
    }
 
    /**
     *   作用:将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;
    }
 
    /**
     *   作用:以post方式提交xml到对应的接口url
     */
    public function postXmlCurl($xml,$url,$second=30)
    {
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        //返回结果
        if($data)
        {
            return $data;
        }
        else
        {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            return false;
        }
    }
 
    /**
     *   作用:使用证书,以post方式提交xml到对应的接口url
     */
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, $this->config['sslcert_path']);
        //默认格式为PEM,可以注释
        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);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return false;
        }
    }
 
}
 
/**
 * 请求型接口的基类
 */
class WechatPayRequestBase extends WechatBase
{
    var $parameters;//请求参数,类型为关联数组
    public $response;//微信返回的响应
    public $result;//返回参数,类型为关联数组
    var $url;//接口链接
    var $curl_timeout;//curl超时时间
 
    /**
     *   作用:设置请求参数
     */
    function setParameter($parameter, $parameterValue)
    {
        $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
    }
 
    /**
     *   作用:设置标配的请求参数,生成签名,生成接口参数xml
     */
    function createXml()
    {
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     *   作用:post请求xml
     */
    function postXml()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:使用证书post请求xml
     */
    function postXmlSSL()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:获取结果,默认不使用证书
     */
    function getResult()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        return $this->result;
    }
}
 
/**
 * 统一支付接口类
 */
class UnifiedOrderSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        //检测必填参数
        if(empty($this->parameters["out_trade_no"]))
        {
            throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
        }elseif(empty($this->parameters["body"])){
            throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
        }elseif (empty($this->parameters["total_fee"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
        }elseif (empty($this->parameters["notify_url"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
        }elseif (empty($this->parameters["trade_type"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
        }elseif ($this->parameters["trade_type"] == "JSAPI" &&
            empty($this->parameters["openid"])){
            throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
        }
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["spbill_create_ip"] = $this->di->getRequest()->getClientAddress();//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 获取prepay_id
     */
    function getPrepayId()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        $prepay_id = $this->result["prepay_id"];
        return $prepay_id;
    }
 
}
 
/**
 * 响应型接口基类
 */
class WechatPayServerBase extends WechatBase
{
  public $data;//接收到的数据,类型为关联数组
  var $returnParameters;//返回参数,类型为关联数组
 
  /**
   * 将微信的请求xml转换成关联数组,以方便数据处理
   */
  function saveData($xml)
  {
    $this->data = $this->xmlToArray($xml);
  }
 
  function checkSign()
  {
    $tmpData = $this->data;
    unset($tmpData['sign']);
    $sign = $this->getSign($tmpData);//本地签名
        if (isLocal()) {
            echo $sign;
        }
    if ($this->data['sign'] == $sign) {
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * 获取微信的请求数据
   */
  function getData()
  {
    return $this->data;
  }
 
  /**
   * 设置返回微信的xml数据
   */
  function setReturnParameter($parameter, $parameterValue)
  {
    $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
  }
 
  /**
   * 生成接口参数xml
   */
  function createXml()
  {
    return $this->arrayToXml($this->returnParameters);
  }
 
  /**
   * 将xml数据返回微信
   */
  function returnXml()
  {
    $returnXml = $this->createXml();
    return $returnXml;
  }
}
 
/**
 * 红包发送接口类
 */
class RedPackSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        $requiredParams = [
            'mch_billno',
            'nick_name',
            'send_name',
            're_openid',
            'total_amount',
            'min_value',
            'max_value',
            'total_num',
            'wishing',
            'act_name',
            'remark',
        ];
        //检测必填参数
        foreach ($requiredParams as $val) {
            if (empty($this->parameters[$val])) {
                throw new SDKRuntimeException("缺少红包接口必填参数{$val}!"."<br>");
            }
        }
        $this->parameters["wxappid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["client_ip"] = gethostbyname(gethostname());//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 发送红包
     *
     * curl 返回例子
     *  <xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[发放成功.]]></return_msg>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <err_code><![CDATA[0]]></err_code>
        <err_code_des><![CDATA[发放成功.]]></err_code_des>
        <mch_billno><![CDATA[1228563102201503261427336509]]></mch_billno>
        <mch_id>1228563102</mch_id>
        <wxappid><![CDATA[wxdceccb9098a978b8]]></wxappid>
        <re_openid><![CDATA[o55e0t1NHPxdcRCjbIu_xnjHc2u8]]></re_openid>
        <total_amount>100</total_amount>
        </xml>
     */
    public function send() {
        $this->postXmlSSL();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        return $this->result;
    }
#3WechatSDK->getToken()
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (45)
<?php
 
/**
 * weixin library
 * @author geekzhumail@gmail.com
 * @since 2014-09-24
 * @edit 2015-03-05
 */
class SDKRuntimeException extends Exception {
 
    public function errorMessage() {
        return $this->getMessage();
    }
}
 
class WechatSDK extends WechatBase{
 
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => 'wxdf0a039cf4ea94d1',
        'appsecret' => '7100f0fe837a5287f075b6d2d9814ca0',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/';
    /**
     * @var access_token
     */
    protected $accessToken = '';
 
    /**
     * 是否从远程服务器拿token
     */
    protected $isRemote = false;
 
    public function __construct() {
        parent::__construct();
        $this->isRemote = $this->config->is_remote;
        $this->accessToken = $this->getToken();
    }
 
    /**
     * 初始化数据
     */
    public function init($config) {
        $this->config = $config;
    }
 
    /**
     * 获取Appid
     */
    public function getAppid() {
        return $this->config['appid'];
    }
 
    /**
     * weixin signature
     */
    public function checkSignature($signature, $timestamp, $nonce) {
        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
 
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
 
    /**
     * web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function webOauth2($redirectUri, $state = [], $type = 'snsapi_base') {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = $type;
        if (empty($state['type'])) {
            $state['type'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * pc web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function pcWebOauth2($redirectUri, $state = []) {
        $params = [];
        $params['appid'] = $this->config['open_web_appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = 'snsapi_login';
        if (empty($state['env'])) {
            $state['env'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * get access token by code
     */
    public function getUserTokenByCode($code) {
        $params = array();
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $params['code'] = $code;
        $params['grant_type'] = 'authorization_code';
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * refresh access token
     * @param string $token 填写通过access_token获取到的refresh_token参数
     */
    public function refreshUserToken($token) {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['grant_type'] = 'refresh_token';
        $params['refresh_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 根据网页授权拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function getUserInfoByOauth($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('sns/userinfo', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 验证授权凭证(access_token)是否有效
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function validUserToken($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/auth?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 获取公众号token
     */
    public function getToken() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        if ($this->isRemote) {
            return $this->getTokenFromRemote();
        }
 
        $params = [];
        $params['grant_type'] = 'client_credential';
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $url = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        // 写入缓存
        $expiredAt = $response['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $response['access_token'], $expiredAt);
        return $response['access_token'];
    }
 
    /**
     * 获取access_token from remote
     */
    public function getTokenFromRemote() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/token/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $cache->save('wechat_access_token_expired', $resp['expired_at'], $expiredAt);
            $expiredAt = 120;
            $cache->save($key, $resp['access_token'], $expiredAt);
            return $resp['access_token'];
        } else {
            throw new Exception('wx admin get token error');
        }
    }
 
    /**
     * 获取用户基本信息(包括UnionID机制)
     * @param string $openid 用户的唯一标识
     * @param string $token 调用接口凭证,默认值为Wechat::getToken()
     */
    public function getUserInfo($openid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('cgi-bin/user/info', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 下载已经上传到微信的资源文件
     */
    public function downloadMedia($mediaid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['access_token'] = $token;
        $params['media_id'] = $mediaid;
        $url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $content = curl_exec($ch);
        $info = curl_getinfo($ch);
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            // 正常则分割header和body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($content, 0, $headerSize);
            $body = substr($content, $headerSize);
        }
        curl_close($ch);
        $resp['info'] = $info;
        $resp['header'] = $header;
        $resp['body'] = $body;
        return $resp;
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicket()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        if ($this->isRemote) {
            return $this->getJsapiTicketFromRemote();
        }
        $resp = json_decode($this->get('cgi-bin/ticket/getticket', ['access_token' => $this->accessToken, 'type' => 'jsapi']), true);
 
        // 写入缓存
        $expiredAt = $resp['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $resp['ticket'], $expiredAt);
        return $resp['ticket'];
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicketFromRemote()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/jsticket/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $expiredAt = 120;
            $cache->save($key, $resp['ticket'], $expiredAt);
            return $resp['ticket'];
        } else {
            throw new Exception('wx admin get ticket error');
        }
    }
    /**
     * 获取js api 签名
     */
    public function getSignPackage($url = '')
    {
        $jsticket = $this->getJsapiTicket();
 
        if (! $url) {
            $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
        }
        $timestamp = time();
        $nonceStr = $this->createNoncestr(16);
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket={$jsticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        return [
            "appId"     => $this->config['appid'],
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }
 
 
    /**
     * 新建一个类,由于PSR-4无法获取不以文件名命名的类;
     */
    public function newClass($name)
    {
        return new $name();
    }
 
    // 微信支付
 
    /**
     * 获取微信付款码的prepayid
     * @params array $params 参数
     */
    public function getPrepayId($params) {
        $sdk = new UnifiedOrderSDK;
        $sdk->setParameter("trade_type","JSAPI");//交易类型
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $prepayid = $sdk->getPrepayId();
        return $prepayid;
    }
 
    /**
     * 获取支付js api 签名
     */
    public function getPaySignPackage($prepayid)
    {
        $jsApiObj["appId"] = $this->config['appid'];
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->createNoncestr();
        $jsApiObj["package"] = "prepay_id=$prepayid";
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->getSign($jsApiObj);
        $signPackage = $jsApiObj;
 
        return $signPackage;
    }
 
    /**
     * 发送红包接口
     * @params array $params 参数
     */
    public function sendRedPack($params) {
        $sdk = new RedPackSDK;
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $result = $sdk->send();
        return $result;
    }
}
 
/**
 * 所有接口的基类
 */
class WechatBase
{
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => '',
        'appsecret' => '',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/cgi-bin/';
 
    public function __construct() {
        $this->di = Phalcon\DI::getDefault();
        $this->config = $this->di->getConfig()['wechat'];
    }
 
    protected function get($url, $params = '')
    {
        if (strpos($url, 'https://') === false && strpos($url, 'http://') === false) {
            $url = $this->apiPrefix.$url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl get error');
        }
    }
 
    protected function post($url, $params = '', $data = '')
    {
        if (strpos($url, 'https://') === false) {
            $url = $this->apiPrefix . $url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        if ($data) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl post error');
        }
    }
 
    public function trimString($value)
    {
        $ret = null;
        if (null != $value)
        {
            $ret = $value;
            if (strlen($ret) == 0)
            {
                $ret = null;
            }
        }
        return $ret;
    }
 
    /**
     *   作用:产生随机字符串,不长于32位
     */
    public function createNoncestr( $length = 32, $type = 'all')
    {
        if ($type == 'all') {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        } else if($type == 'number') {
            $chars = "0123456789";
        }
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
 
    /**
     *   作用:格式化参数,签名过程需要使用
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $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['key'];
        //echo "【string2】".$String."</br>";
        //签名步骤三:MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }
 
    /**
     *   作用:array转xml
     */
    public 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;
    }
 
    /**
     *   作用:将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;
    }
 
    /**
     *   作用:以post方式提交xml到对应的接口url
     */
    public function postXmlCurl($xml,$url,$second=30)
    {
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        //返回结果
        if($data)
        {
            return $data;
        }
        else
        {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            return false;
        }
    }
 
    /**
     *   作用:使用证书,以post方式提交xml到对应的接口url
     */
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, $this->config['sslcert_path']);
        //默认格式为PEM,可以注释
        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);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return false;
        }
    }
 
}
 
/**
 * 请求型接口的基类
 */
class WechatPayRequestBase extends WechatBase
{
    var $parameters;//请求参数,类型为关联数组
    public $response;//微信返回的响应
    public $result;//返回参数,类型为关联数组
    var $url;//接口链接
    var $curl_timeout;//curl超时时间
 
    /**
     *   作用:设置请求参数
     */
    function setParameter($parameter, $parameterValue)
    {
        $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
    }
 
    /**
     *   作用:设置标配的请求参数,生成签名,生成接口参数xml
     */
    function createXml()
    {
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     *   作用:post请求xml
     */
    function postXml()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:使用证书post请求xml
     */
    function postXmlSSL()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:获取结果,默认不使用证书
     */
    function getResult()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        return $this->result;
    }
}
 
/**
 * 统一支付接口类
 */
class UnifiedOrderSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        //检测必填参数
        if(empty($this->parameters["out_trade_no"]))
        {
            throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
        }elseif(empty($this->parameters["body"])){
            throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
        }elseif (empty($this->parameters["total_fee"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
        }elseif (empty($this->parameters["notify_url"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
        }elseif (empty($this->parameters["trade_type"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
        }elseif ($this->parameters["trade_type"] == "JSAPI" &&
            empty($this->parameters["openid"])){
            throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
        }
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["spbill_create_ip"] = $this->di->getRequest()->getClientAddress();//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 获取prepay_id
     */
    function getPrepayId()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        $prepay_id = $this->result["prepay_id"];
        return $prepay_id;
    }
 
}
 
/**
 * 响应型接口基类
 */
class WechatPayServerBase extends WechatBase
{
  public $data;//接收到的数据,类型为关联数组
  var $returnParameters;//返回参数,类型为关联数组
 
  /**
   * 将微信的请求xml转换成关联数组,以方便数据处理
   */
  function saveData($xml)
  {
    $this->data = $this->xmlToArray($xml);
  }
 
  function checkSign()
  {
    $tmpData = $this->data;
    unset($tmpData['sign']);
    $sign = $this->getSign($tmpData);//本地签名
        if (isLocal()) {
            echo $sign;
        }
    if ($this->data['sign'] == $sign) {
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * 获取微信的请求数据
   */
  function getData()
  {
    return $this->data;
  }
 
  /**
   * 设置返回微信的xml数据
   */
  function setReturnParameter($parameter, $parameterValue)
  {
    $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
  }
 
  /**
   * 生成接口参数xml
   */
  function createXml()
  {
    return $this->arrayToXml($this->returnParameters);
  }
 
  /**
   * 将xml数据返回微信
   */
  function returnXml()
  {
    $returnXml = $this->createXml();
    return $returnXml;
  }
}
 
/**
 * 红包发送接口类
 */
class RedPackSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        $requiredParams = [
            'mch_billno',
            'nick_name',
            'send_name',
            're_openid',
            'total_amount',
            'min_value',
            'max_value',
            'total_num',
            'wishing',
            'act_name',
            'remark',
        ];
        //检测必填参数
        foreach ($requiredParams as $val) {
            if (empty($this->parameters[$val])) {
                throw new SDKRuntimeException("缺少红包接口必填参数{$val}!"."<br>");
            }
        }
        $this->parameters["wxappid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["client_ip"] = gethostbyname(gethostname());//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 发送红包
     *
     * curl 返回例子
     *  <xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[发放成功.]]></return_msg>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <err_code><![CDATA[0]]></err_code>
        <err_code_des><![CDATA[发放成功.]]></err_code_des>
        <mch_billno><![CDATA[1228563102201503261427336509]]></mch_billno>
        <mch_id>1228563102</mch_id>
        <wxappid><![CDATA[wxdceccb9098a978b8]]></wxappid>
        <re_openid><![CDATA[o55e0t1NHPxdcRCjbIu_xnjHc2u8]]></re_openid>
        <total_amount>100</total_amount>
        </xml>
     */
    public function send() {
        $this->postXmlSSL();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        return $this->result;
    }
#4WechatSDK->__construct()
/usr/local/www/liowang-shop-robotwar/shop/app/controllers/PageUserController.php (24)
<?php
 
use HomeSpace\Model;
 
class PageUserController extends PageBaseController {
 
    /**
     * 登录
     */
    public function loginAction()
    {
        $data = [];
        //echo '登录';
        $act = $this->request->getQuery('act');
        if ($act == 'not_active') {
            if (empty($_SESSION['tmp_email'])) {
                $act = '';
            }
            $data['email'] = $_SESSION['tmp_email'];
        }
        $data['act'] = $act;
        $data['errmsg'] = $this->flash->getMessages('error');
        $this->flash->clear();
        $wechat = new WechatSDK;
        //$url = $this->url->get('wechat/oauth2/callback');
        $url = 'http://www.liowang.com/shop/wechat/oauth2/callback';
        $state = ['env' => 'product'];
        if (isLocal()) {
            $state = ['env' => 'local'];
        }
        $data['wechat_login_url'] = $wechat->pcWebOauth2($url, $state);
        echo $this->viewoutput('pc/login', $data);
    }
 
    /**
     * 登录 POST
     */
    public function postLoginAction()
    {
        global $user, $_LANG;
        $errmsg = '';
        $account = $this->request->getPost('account');
        $password = $this->request->getPost('password');
        $remember = ! empty($this->request->getQuery('remember'));
        if (filter_var($account, FILTER_VALIDATE_EMAIL)) {
            // 邮箱登录
            $muser = Model\User::findFirst(['columns' => 'user_name', 'conditions' => "email = '$account'"]);
        } else if(is_numeric($account) && strlen($account) == 11) {
            // 手机登录
            $muser = Model\User::findFirst(['columns' => 'user_name', 'conditions' => "mobile_phone = '$account'"]);
        }
        if (! empty($muser) && ! empty($password)) {
            if ($user->login($muser['user_name'], $password, $remember)) {
                $row = Model\User::findFirstByUserId($_SESSION['user_id']);
                if ($row['is_validated'] == 1) {
                    update_user_info();
                    recalculate_price();
 
                    $this->auth->loginUsingId($_SESSION['user_id']);
                    $_SESSION['pc_login'] = true;
                    return $this->response->redirect('home/index');
                } else {
                    //$_SESSION['login_fail'] ++ ;
                    //$errmsg = '该账号尚未激活请查阅该账号邮箱的激活邮件,或者联系客服';
                    $_SESSION['tmp_email'] = $row['email'];
                    return $this->response->redirect('login?act=not_active');
                }
            } else {
                $_SESSION['login_fail'] ++ ;
                $errmsg = $_LANG['login_failure'];
            }
        }
        $errmsg = $errmsg ?: '账号或者密码错误';
        $this->flash->error($errmsg);
        return $this->response->redirect('login');
    }
 
    /**
     * 退出登录
     */
    public function logoutAction()
    {
        global $user;
        $user->logout();
        return $this->response->redirect($this->request->getHTTPReferer(), true);
    }
 
    /**
     * 邮箱注册验证
     */
    public function emailValidateAction()
    {
        include_once(ROOT_PATH . 'includes/lib_passport.php');
        $data = [];
        $data['status'] = 'user_not_exist';
        $hash = $this->request->getQuery('hash');
        if ($hash) {
            $id = intval(register_hash('decode', $hash));
            $user = Model\User::findFirstByUserId($id);
            if ($user) {
                if ($user['is_validated'] == 0) {
                    Model\User::update(['is_validated' => 1], ['user_id' => $id]);
                    $data['status'] = 'success';
                } else {
                    $data['status'] = 'validated';
                }
                $data['user'] = $user;
            }
        }
        echo $this->viewoutput('pc/email-validate', $data);
    }
 
    /**
     * PC 微信回调 oauth2 函数
     */
    public function wechatOauth2CallbackAction()
    {
        global $ecs, $db, $user;
        include_once(ROOT_PATH . 'includes/lib_passport.php');
        $code = $this->request->getQuery('code');
        if (empty($code)) {
            echo '授权失败';
            exit;
        }
        $state = $this->request->getQuery('state');
        parse_str($state, $params);
        if (! islocal() && ! empty($params['env']) && $params['env'] == 'local') {
            return $this->response->redirect('http://liowang.dev.seafarer.me/shop/wechat/oauth2/callback?' . http_build_query($this->request->getQuery()));
        }
 
        $wechat = new WechatSDK;
        $config = $this->config->wechat;
        $config->appid = $config->open_web_appid;
        $config->appsecret = $config->open_web_secret;
        $wechat->init($config);
        $response = $wechat->getUserTokenByCode($code);
        if (empty($response['errcode'])) {
            // 授权成功
            $ewxpcuser = Model\WxPcUser::findFirstByOpenid($response['openid']);
 
            $data = [];
            $isFirst = false;
            $isNeedCreate = false;
            $uuser = [];
            $wxpcuser = [];
            if ($ewxpcuser) {
                // 用户已经存在
                $euser = Model\User::findFirstByWxPcUserid($ewxpcuser['id']);
            } else {
                // 第一次访问
                $isFirst = true;
                $wxuser = Model\WxUser::findFirstByUnionid($response['unionid']);
                if (empty($wxuser)) {
                    $isNeedCreate = true;
                } else {
                    $euser = Model\User::findFirstByWxUserid($wxuser['id']);
                }
            }
            $wxpcuser['openid'] = $response['openid'];
            $wxpcuser['unionid'] = $response['unionid'];
            $wxpcuser['access_token'] = $response['access_token'];
            $wxpcuser['refresh_token'] = $response['refresh_token'];
            $wxpcuser['expired_at'] = $response['expires_in'] + time() - 200;
            $wxpcuser['scope'] = $response['scope'];
            $wxpcuser['ip'] = $this->request->getClientAddress();
            $info = $wechat->getUserInfoByOauth($response['openid'], $response['access_token']);
            $wxpcuser['nickname'] = $info['nickname'];
            $wxpcuser['gender'] = $info['sex'];
            $wxpcuser['language'] = $info['language'];
            $wxpcuser['city'] = $info['city'];
            $wxpcuser['country'] = $info['country'];
            $wxpcuser['province'] = $info['province'];
            $wxpcuser['headimgurl'] = $info['headimgurl'];
 
            if ($isFirst) {
                // 首次PC扫码登录
                $id = Model\WxPcUser::insert($wxpcuser);
                $_SESSION['wx_pc_user_id'] = $id;
                $uuser['wx_pc_userid'] = $id;
                if (! $isNeedCreate) {
                    Model\User::update($uuser, "user_id = {$euser['user_id']}");
                }
                /**
                if ($isNeedCreate) {
                    // 创建新用户
                    $uuser['avatar'] = 'shop/assets/pc/IMGS/defaultPhoto.png';
                    $name = '会员_' . time() . '_' . $id;
                    $uuser['nickname'] = $wxpcuser['nickname'];
                    $email = 'email_' . time() . '_' . $id . '@liowang.com';
                    if (register($name, '123123', $email) !== false) {;
                        $db->autoExecute($ecs->table('users'), $uuser, 'UPDATE', "user_id = {$_SESSION['user_id']}");
                    } else {
                        SystemException::error('授权失败');
                    }
                } else {
                    // 绑定已在微信授权的用户
                    Model\User::update($uuser, "user_id = {$euser['user_id']}");
                    Model\WxPcUser::update($wxpcuser, "openid = '{$response['openid']}'");
                    $user->set_session($euser['user_name']);
                    $user->set_cookie($euser['user_name']);
                    update_user_info();
                    recalculate_price();
                }**/
            } else {
                Model\WxPcUser::update($wxpcuser, "openid = '{$response['openid']}'");
                $_SESSION['wx_pc_user_id'] = $ewxpcuser['id'];
            }
            if (empty($euser)) {
                return $this->response->redirect('wechat/oauth2/register');
            }
            // 已注册直接登录
            $user->set_session($euser['user_name']);
            $user->set_cookie($euser['user_name']);
            update_user_info();
            recalculate_price();
            $this->auth->loginUsingId($_SESSION['user_id']);
            $_SESSION['pc_login'] = true;
        } else {
            SystemException::error($response['errmsg']);
        }
        $referer = $this->url->get('');
        return $this->response->redirect($referer, true);
    }
 
    /**
     * PC 微信账号登录绑定
     */
    public function wechatOauth2RegisterAction()
    {
        global $ecs, $db, $user;
 
        if (empty($_SESSION['wx_pc_user_id'])) {
            return $this->response->redirect('login');
        }
        $data = [];
        echo $this->viewoutput('pc/wechat-oauth2-register', $data);
    }
 
    /******** 需要登录授权 *********/
    /**
     * 个人信息
     */
    public function infoAction()
    {
        $user = $this->auth->user();
 
        list($user['birYear'], $user['birMonth'], $user['birDay']) = explode('-', $user['birthday']);
        $user['birMonth'] = ltrim($user['birMonth'], 0);
        $user['birDay'] = ltrim($user['birDay'], 0);
        if (strpos($user['avatar'], 'http://') === false && strpos($user['avatar'], 'https://') === false) {
            $user['avatar'] = 'http://' . $this->request->getHttpHost() . '/' . $user['avatar'];
        }
        $data = [];
        $data['user'] = $user;
        $data['navName'] = '';
        $data['userSidebar'] = 'user-info';
        echo $this->viewoutput('pc/user-info', $data);
    }
    /**
     * 购物车
     */
    public function cartAction()
    {
        require(ROOT_PATH . 'includes/lib_order.php');
        $user = $this->auth->user();
        $mcart = new Model\Cart;
        $cartCount = $mcart->getCount();
        $cartGoods = get_cart_goods();
        $discount = compute_discount();
 
        $goodsids = [];
        foreach ($cartGoods['goods_list'] as $goods) {
            $goodsids[] = $goods['goods_id'];
        }
        if ($goodsids) {
            $collectGoodsList = Model\CollectGoods::find(['conditions' => "user_id='$_SESSION[user_id]' AND " . db_create_in($goodsids, 'goods_id')]);
            foreach ($cartGoods['goods_list'] as &$goods) {
                $goods['is_collect'] = false;
                foreach ($collectGoodsList as $row) {
                    if ($row['goods_id'] == $goods['goods_id']) {
                        $goods['is_collect'] = true;
                        break;
                    }
                }
            }
            unset($goods);
        }
 
        $data = [];
        $data['user'] = $user;
        $data['cartGoods'] = $cartGoods;
        $data['navName'] = '';
        echo $this->viewoutput('pc/user-cart', $data);
    }
    /**
     * 购物车结算
     */
    public function cartSettleAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        require(ROOT_PATH . 'includes/lib_order.php');
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
        $user = $this->auth->user();
 
        $recids = $this->request->getQuery('rec_ids') ?: [];
 
        $cartGoods = get_cart_goods($recids);
        if (empty($cartGoods['goods_list'])) {
            // 购物车为空
            return $this->response->redirect('user/cart');
        }
        $discount = compute_discount();
 
        $data = [];
 
        $address_id = intval($this->request->getQuery('address_id'));
        if ($address_id == 0) {
            $address_id = $user['address_id'];
        }
        $address = Model\UserAddress::findFirst(['conditions' => "address_id = $address_id AND user_id = {$user['user_id']}"]);
        $addressList = Model\UserAddress::find(['conditions' => "address_id != $address_id AND user_id = {$user['user_id']}", 'order' => 'address_id DESC']);
        if ($address) {
            array_unshift($addressList, $address);
        }
        $regions = Model\Region::getAll();
        foreach ($addressList as &$row) {
            $country = $regions[$row['country']];
            $province = $country['list'][$row['province']];
            $city = $province['list'][$row['city']];
            $district = $city['list'][$row['district']];
            $row['address_detail'] = $province['region_name'] . $city['region_name'] . $district['region_name'] . $row['address'];
        }
        unset($row);
        $data['addressList'] = $addressList;
        foreach ($cartGoods['goods_list'] as &$goods) {
            //$goods['goods_name'] = mb_substr($goods['goods_name'], 0, 15, 'utf-8') . '......';
        }
        unset($goods);
 
        $regions = Model\Region::getAll();
 
        $data['user'] = $user;
        $data['regions'] = $regions;
        $data['cartGoods'] = $cartGoods;
        $data['navName'] = '';
        echo $this->viewoutput('pc/cart-settle', $data);
    }
 
    /**
     * 已购买的商品
     */
    public function boughtListAction()
    {
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'includes/lib_order.php');
        $user = $this->auth->user();
        $mcart = new Model\Cart;
 
        global $db, $ecs;
 
        $page = intval($this->request->getQuery('page')) ?: 1;
        $limit = 10;
        $offset = ($page - 1) * $limit;
        $userid = $_SESSION['user_id'];
        $morder = new Model\OrderInfo;
 
        $arr    = array();
        $osArr = [];
        $ssArr = [];
        $psArr = [];
        // 需要评论的order_id
        $result = $morder->needComment();
        $needCommentOrderids = [];
        foreach ($result as $row) {
            $needCommentOrderids[$row['order_id']] = $row['order_id'];
        }
 
        $osArr[] = OS_CONFIRMED;
        $osArr[] = OS_SPLITED;
        $ssArr[] = SS_RECEIVED;
        $psArr[] = PS_PAYED;
        $ossql = $sssql = $pssql = '';
        if ($osArr) {
            $ossql = ' AND ' . db_create_in($osArr, 'order_status');
        }
        if ($ssArr) {
            $sssql = ' AND ' . db_create_in($ssArr, 'shipping_status');
        }
        if ($psArr) {
            $pssql = ' AND ' . db_create_in($psArr, 'pay_status');
        }
        $sql = "SELECT order_id, order_sn, order_status, shipping_status, pay_status, add_time, " .
               "(goods_amount + shipping_fee + insure_fee + pay_fee + pack_fee + card_fee + tax - discount) AS total_fee ".
               " FROM " .$GLOBALS['ecs']->table('order_info') .
               ' WHERE 1 = 1';
        $sql .= " AND user_id = '$userid' $ossql $sssql $pssql ORDER BY add_time DESC";
        //$res = $GLOBALS['db']->SelectLimit($sql, 20, ($page - 1) * 20);
        $res = $GLOBALS['db']->query($sql);
 
        $orderids = [];
        while ($row = $GLOBALS['db']->fetchRow($res))
        {
            $orderids[] = $row['order_id'];
            $row['shipping_status'] = ($row['shipping_status'] == SS_SHIPPED_ING) ? SS_PREPARING : $row['shipping_status'];
 
            $arr[$row['order_id']] = array('order_id'       => $row['order_id'],
                           'order_sn'       => $row['order_sn'],
                           'order_time'     => local_date($GLOBALS['_CFG']['time_format'], $row['add_time']),
                           'order_status'   => $row['order_status'],
                           'shipping_status'   => $row['shipping_status'],
                           'pay_status'   => $row['pay_status'],
                           'total_fee'      => price_format($row['total_fee'], false),
                           'needComment' => isset($needCommentOrderids[$row['order_id']]) ? true : false,
                           'goods' => [],
                    );
        }
        $orders = $arr;
        $count = 0;
        if ($orderids) {
            $orderidsStr = implode(',', $orderids);
 
            /* 订单商品 */
            $params = [
                'alias' => 'og',
                'columns' => "og.rec_id, og.order_id, og.goods_id, og.goods_name, og.goods_sn, og.market_price, og.goods_number, " .
                    "og.goods_price, og.goods_attr, og.is_real, og.parent_id, og.is_gift, " .
                    "og.goods_price * og.goods_number AS subtotal, og.extension_code, g.goods_thumb, g.goods_img " .
                    ',c.comment_id ',
                'conditions' =>  "og.order_id IN ($orderidsStr)",
                'offset' => $offset,
                'limit' => $limit,
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = og.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'comment',
                        'alias' => 'c',
                        'on' => 'c.id_value = og.goods_id AND c.order_id = og.order_id '
                    ]
                ],
            ];
            $colls = Model\OrderGoods::find($params);
            unset($params['limit'], $params['offset']);
            $count = Model\OrderGoods::count($params);
            foreach ($colls as $row) {
                if (isset($orders[$row['order_id']])) {
                    $row['market_price'] = price_format($row['market_price'], false);
                    $row['goods_price']  = price_format($row['goods_price'], false);
                    $row['subtotal']     = price_format($row['subtotal'], false);
                    $orders[$row['order_id']]['goods'][] = $row;
                }
            }
        }
 
        $totalPage = $count ? intval(($count - 1) / $limit) + 1 : 1;
 
        $data = [];
        $data['user'] = $user;
        $data['page'] = $page;
        $data['totalPage'] = $totalPage;
        $data['count'] = $count;
        $data['orders'] = $orders;
        $data['navName'] = '';
        $data['userSidebar'] = 'bought-list';
        echo $this->viewoutput('pc/bought-list', $data);
    }
 
    /**
     * 我的收藏
     */
    public function collectListAction()
    {
        global $db, $ecs;
        $user = $this->auth->user();
        $page = intval($this->request->getQuery('page')) ?: 1;
        $ctype = $this->request->getQuery('collect_type') == 'brand' ? 'brand' : 'goods';
        $limit = 10;
        $offset = ($page - 1) * $limit;
        $userid = $user['user_id'];
 
        $data = [];
        if ($ctype == 'goods') {
            $params = [
                'alias' => 'c',
                'columns' => 'g.goods_id, g.goods_name, g.market_price, g.shop_price AS org_price, '.
                    "IFNULL(mp.user_price, g.shop_price * '$_SESSION[discount]') AS shop_price, g.goods_img, ".
                    'g.promote_price, g.promote_start_date,g.promote_end_date, c.rec_id, c.is_attention',
                'conditions' =>  "c.user_id = '$userid' ORDER BY c.rec_id DESC",
                'offset' => $offset,
                'limit' => $limit,
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = c.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'member_price',
                        'alias' => 'mp',
                        'on' => "mp.goods_id = g.goods_id AND mp.user_rank = '$_SESSION[user_rank]' "
                    ],
                ],
            ];
            $goodsList = Model\CollectGoods::find($params);
            foreach ($goodsList as &$row) {
                if ($row['promote_price'] > 0) {
                    $promote_price = bargain_price($row['promote_price'], $row['promote_start_date'], $row['promote_end_date']);
                    $row['promote_price'] = $promote_price > 0 ? price_format($promote_price) : '';
                } else {
                    $row['promote_price'] = '';
                }
                if ($row['promote_price'] > 0) {
                    $row['price'] = $row['promote_price'];
                } else {
                    $row['price'] = $row['shop_price'];
                }
            }
            unset($row);
            unset($params['limit'], $params['offset']);
            $count = Model\CollectGoods::count($params);
 
            $data['list'] = $goodsList;
        } else {
            $params = [
                'alias' => 'cb',
                'columns' => 'b.*',
                'conditions' =>  "cb.user_id = '$userid'",
                'offset' => $offset,
                'limit' => $limit,
                'order' => 'cb.id DESC',
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'brand',
                        'alias' => 'b',
                        'on' => 'b.brand_id = cb.brand_id'
                    ]
                ],
            ];
            $brandList = Model\CollectBrand::find($params);
            foreach ($brandList as &$row) {
 
            }
            unset($row);
            unset($params['limit'], $params['offset']);
            $count = Model\CollectBrand::count($params);
 
            $data['list'] = $brandList;
        }
        $paginator = new \Juice\Paginator(['type' => 'emptyArray', 'config' => [
            'count' => $count,
            'limit' => $limit,
            'page' => $page,
        ]]);
        $paginate = $paginator->getPaginate();
 
        $data['user'] = $user;
        $data['ctype'] = $ctype;
        $data['paginate'] = $paginate;
        $data['navName'] = '';
        $data['userSidebar'] = 'collect-list';
        echo $this->viewoutput('pc/collect-list', $data);
    }
    /**
     * 评论商品
     */
    public function commentAction()
    {
        global $db, $ecs;
        $orderid = intval($this->request->getQuery('order_id'));
        $goodsid = intval($this->request->getQuery('goods_id'));
        if (! $orderid || ! $goodsid) {
            return $this->response->redirect('user/bought/list');
        }
        $user = $this->auth->user();
        $goods = Model\OrderGoods::findFirst([
            'alias' => 'og',
            'columns' => "og.rec_id, og.goods_id, og.goods_name, og.goods_attr, og.goods_sn, og.goods_number, goods_price, g.goods_thumb, o.add_time, o.order_id",
            'conditions' =>  "og.order_id = $orderid AND og.goods_id = $goodsid AND o.user_id = " . $user['user_id'] . ' AND o.shipping_status = 2 AND c.comment_id IS NULL',
            'join' => [
                [
                    'type' => 'INNER',
                    'table' => 'order_info',
                    'alias' => 'o',
                    'on' => 'o.order_id = og.order_id'
                ],
                [
                    'type' => 'INNER',
                    'table' => 'goods',
                    'alias' => 'g',
                    'on' => 'g.goods_id = og.goods_id'
                ],
                [
                    'type' => 'LEFT',
                    'table' => 'comment',
                    'alias' => 'c',
                    'on' => 'c.order_id = og.order_id AND c.id_value = og.goods_id'
                ]
            ],
        ]);
        if (empty($goods)) {
            return $this->response->redirect('user/bought/list');
        }
        $goods['add_time_format'] = local_date('Y-m-d', $goods['add_time']);
 
        $data = [];
        $data['user'] = $user;
        $data['goods'] = $goods;
        $data['navName'] = '';
        $data['userSidebar'] = 'bought-list';
        echo $this->viewoutput('pc/comment', $data);
    }
 
    /**
     * 订单列表
     */
    public function orderListAction()
    {
        global $db, $ecs;
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'includes/lib_order.php');
 
        $data = [];
        $user = $this->auth->user();
        $mcart = new Model\Cart;
 
        $page = intval($this->request->getQuery('page')) ?: 1;
        $limit = 10;
        $offset = ($page - 1) * $limit;
 
        $status = $this->request->getQuery('status') ?: 0;
        $userid = $_SESSION['user_id'];
        $morder = new Model\OrderInfo;
 
        // 需要评论的order_id
        $result = $morder->needComment();
        $needCommentOrderids = [];
        foreach ($result as $row) {
            $needCommentOrderids[$row['order_id']] = $row['order_id'];
        }
 
        if (! empty($status)) {
            $orderAttr = Model\OrderInfo::$orderStatus[$status];
        }
        $ossql = $sssql = $pssql = '';
        if (! empty($orderAttr['os'])) {
            $ossql = ' AND ' . db_create_in($orderAttr['os'], 'o.order_status');
        }
        if (! empty($orderAttr['ss'])) {
            $sssql = ' AND ' . db_create_in($orderAttr['ss'], 'o.shipping_status');
        }
        if (! empty($orderAttr['ps'])) {
            $pssql = ' AND ' . db_create_in($orderAttr['ps'], 'o.pay_status');
        }
 
        $conditions = '1 = 1';
        if ($status == 4) {
            if (empty($needCommentOrderids)) {
                $conditions .= ' AND 1 = 2';
            } else {
                $conditions .= ' AND o.order_id IN (' . implode(',', $needCommentOrderids) . ')';
            }
        }
        $conditions .= " AND o.user_id = '$userid' $ossql $sssql $pssql ORDER BY o.add_time DESC LIMIT $offset, $limit";
        $colls = Model\OrderInfo::find([
            'alias' => 'o',
            'columns' => "o.order_id, o.order_sn, o.order_status, o.shipping_status, o.pay_status, o.add_time, " .
                         "(o.goods_amount + o.shipping_fee + o.insure_fee + o.pay_fee + o.pack_fee + o.card_fee + o.tax - o.discount) AS total_fee, " .
                         "do.delivery_id, do.delivery_sn"
            ,
            'conditions' => $conditions,
            'join' => [
                [
                    'type' => 'LEFT',
                    'table' => 'delivery_order',
                    'alias' => 'do',
                    'on' => 'o.order_id = do.order_id'
                ]
            ],
        ]);
 
        $orderids = [];
        $orders = [];
        foreach ($colls as $row) {
            $orderids[] = $row['order_id'];
            $row['shipping_status'] = ($row['shipping_status'] == SS_SHIPPED_ING) ? SS_PREPARING : $row['shipping_status'];
 
            $row['status'] = Model\OrderInfo::getOrderStatus($row);
            $orders[$row['order_id']] = [
                'order_id'       => $row['order_id'],
                'status' => $row['status'],
                'order_sn'       => $row['order_sn'],
                'order_time'     => local_date($GLOBALS['_CFG']['time_format'], $row['add_time']),
                'order_status'   => $row['order_status'],
                'shipping_status'   => $row['shipping_status'],
                'pay_status'   => $row['pay_status'],
                'total_fee'      => price_format($row['total_fee'], false),
                'delivery_id'       => $row['delivery_id'],
                'delivery_sn'       => $row['delivery_sn'],
                'needComment' => isset($needCommentOrderids[$row['order_id']]) ? true : false,
                'goods' => [],
            ];
        }
 
        $statistics = $morder->getAllCount();
        $count = empty($status) ? $statistics['total'] : $statistics['count'][$status];
        $paginator = new \Juice\Paginator(['type' => 'emptyArray', 'config' => [
            'count' => $count,
            'limit' => $limit,
            'page' => $page,
        ]]);
        $paginate = $paginator->getPaginate();
        $data['paginate'] = $paginate;
 
        if ($orderids) {
            $orderidsStr = implode(',', $orderids);
            $params = [
                'alias' => 'og',
                'columns' => "og.rec_id, og.order_id, og.goods_id, og.goods_name, og.goods_sn, og.market_price, og.goods_number, " .
                    "og.goods_price, og.goods_attr, og.is_real, og.parent_id, og.is_gift, " .
                    "og.goods_price * og.goods_number AS subtotal, og.extension_code, g.goods_thumb, g.goods_img " .
                    ',c.comment_id ',
                'conditions' =>  "og.order_id IN ($orderidsStr)",
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = og.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'comment',
                        'alias' => 'c',
                        'on' => 'c.id_value = og.goods_id AND c.order_id = og.order_id '
                    ],
                ],
            ];
 
            $colls = Model\OrderGoods::find($params);
 
            foreach ($colls as $row) {
                if (isset($orders[$row['order_id']])) {
                    $row['market_price'] = price_format($row['market_price'], false);
                    $row['goods_price']  = price_format($row['goods_price'], false);
                    $row['subtotal']     = price_format($row['subtotal'], false);
                    $orders[$row['order_id']]['goods'][] = $row;
                }
            }
        }
 
        $data['user'] = $user;
        $data['orders'] = $orders;
        $data['status'] = $status;
        $data['statistics'] = $statistics;
        $data['navName'] = '';
        $data['userSidebar'] = 'order-list';
        echo $this->viewoutput('pc/order-list', $data);
    }
 
    /**
     * 地址
     */
    public function addressListAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        $user = $this->auth->user();
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
 
        $addressList = Model\UserAddress::findByUserId($user['user_id']);
        $regionsids = [];
        foreach ($addressList as $region_id => $row) {
            $row['province'] = isset($row['province']) ? intval($row['province']) : 0;
            $row['city']     = isset($row['city'])     ? intval($row['city'])     : 0;
            $row['district']  = isset($row['district'])  ? intval($row['district'])  : 0;
 
            $regionsids[] = $row['district'];
            $regionsids[] = $row['province'];
            $regionsids[] = $row['city'];
        }
        $regions = [];
        if ($regionsids) {
            $idsstr = implode(', ', $regionsids);
            $sql = 'SELECT region_id, region_name FROM ' . $GLOBALS['ecs']->table('region') .
                    " WHERE region_id IN ($idsstr)";
            $res = $GLOBALS['db']->query($sql);
            while ($row = $GLOBALS['db']->fetchRow($res)) {
                $regions[$row['region_id']] = $row;
            }
        }
        foreach ($addressList as $key => &$row) {
            $row['province_name'] = isset($regions[$row['province']]) ? $regions[$row['province']]['region_name'] : '';
            $row['city_name']     = isset($regions[$row['city']])     ? $regions[$row['city']]['region_name']     : '';
            $row['district_name'] = isset($regions[$row['district']])  ? $regions[$row['district']]['region_name']  : '';
        }
        unset($row);
 
        $status = $this->request->getQuery('status');
        $_SESSION['origin_from'] = $status;
        $regions = Model\Region::getAll();
 
        $data = [];
        $data['user'] = $user;
        $data['addressList'] = $addressList;
        $data['regions'] = $regions;
        $data['navName'] = '';
        $data['userSidebar'] = 'address-list';
        echo $this->viewoutput('pc/address-list', $data);
    }
 
    /**
     * 代理信息
     */
    public function agentInfoAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
 
        $user = $this->auth->user();
        $data = [];
        $agent = Model\Agent::findFirstByAgentId($user['agent_id']);
        if (empty($agent)) {
            $data['isEdit'] = false;
        } else {
            $data['isEdit'] = true;
        }
 
        $data['user'] = $user;
        $data['agent'] = $agent;
        $data['navName'] = '';
        $data['userSidebar'] = 'agent';
        echo $this->viewoutput('pc/agent-info', $data);
    }
 
    /**
     * 代理订单信息
     */
    public function agentOrderListAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
 
        $user = $this->auth->user();
 
        $page = intval($this->request->getQuery('page')) ?: 1;
        $limit = 10;
        $offset = ($page - 1) * $limit;
 
        $ossql = $sssql = $pssql = '';
        if (! empty($orderAttr['os'])) {
            $ossql = ' AND ' . db_create_in($orderAttr['os'], 'order_status');
        }
        if (! empty($orderAttr['ss'])) {
            $sssql = ' AND ' . db_create_in($orderAttr['ss'], 'shipping_status');
        }
        if (! empty($orderAttr['ps'])) {
            $pssql = ' AND ' . db_create_in($orderAttr['ps'], 'pay_status');
        }
        $orderids = [];
        $arr = [];
        $count = 0;
        if ($user['agent_id']) {
            $params = [
                'alias' => 'o',
                'columns' => "o.order_id, o.order_sn, o.order_status, o.shipping_status, o.pay_status, o.add_time, " .
                   "(o.goods_amount + o.shipping_fee + o.insure_fee + o.pay_fee + o.pack_fee + o.card_fee + o.tax - o.discount) AS total_fee , u.user_name",
                'conditions' =>  "o.agent_id = '{$user['agent_id']}' $ossql $sssql $pssql",
                'offset' => $offset,
                'limit' => $limit,
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'users',
                        'alias' => 'u',
                        'on' => 'u.user_id = o.user_id'
                    ]
                ],
                'order' => 'add_time DESC',
            ];
            $colls = Model\OrderInfo::find($params);
 
            foreach ($colls as $row)
            {
                $orderids[] = $row['order_id'];
                $row['shipping_status'] = ($row['shipping_status'] == SS_SHIPPED_ING) ? SS_PREPARING : $row['shipping_status'];
 
                $row['status'] = Model\OrderInfo::getOrderStatus($row);
                $orderTime = local_date($GLOBALS['_CFG']['time_format'], $row['add_time']);
                $dateArr = explode(' ', $orderTime);
                $arr[$row['order_id']] = [
                    'order_id'       => $row['order_id'],
                    'user_name' => $row['user_name'],
                    'status' => $row['status'],
                    'order_sn'       => $row['order_sn'],
                    'order_time'     => $orderTime,
                    'order_time_date'     => $orderTime,
                    'order_time_arr'     => $dateArr,
                    'order_status'   => $row['order_status'],
                    'shipping_status'   => $row['shipping_status'],
                    'pay_status'   => $row['pay_status'],
                    'total_fee'      => price_format($row['total_fee'], false),
                    'goods' => [],
                ];
            }
            unset($params['limit'], $params['offset']);
            $count = Model\OrderInfo::count($params);
        }
 
        $paginator = new \Juice\Paginator(['type' => 'emptyArray', 'config' => [
            'count' => $count,
            'limit' => $limit,
            'page' => $page,
        ]]);
        $paginate = $paginator->getPaginate();
 
        $orders = $arr;
        if ($orderids) {
            $orderidsStr = implode(',', $orderids);
            $params = [
                'alias' => 'og',
                'columns' => "og.rec_id, og.order_id, og.goods_id, og.goods_name, og.goods_sn, og.market_price, og.goods_number, " .
                    "og.goods_price, og.goods_attr, og.is_real, og.parent_id, og.is_gift, " .
                    "og.goods_price * og.goods_number AS subtotal, og.extension_code, g.goods_thumb, g.goods_img " .
                    ',c.comment_id ',
                'conditions' =>  "og.order_id IN ($orderidsStr)",
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = og.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'comment',
                        'alias' => 'c',
                        'on' => 'c.id_value = og.goods_id AND c.order_id = og.order_id '
                    ]
                ],
            ];
            $colls = Model\OrderGoods::find($params);
 
            foreach ($colls as $row) {
                if (isset($orders[$row['order_id']])) {
                    $row['market_price'] = price_format($row['market_price'], false);
                    $row['goods_price']  = price_format($row['goods_price'], false);
                    $row['subtotal']     = price_format($row['subtotal'], false);
                    $orders[$row['order_id']]['goods'][] = $row;
                }
            }
        }
 
        $data = [];
        $data['user'] = $user;
        $data['orders'] = $orders;
        $data['paginate'] = $paginate;
        $data['navName'] = '';
        $data['userSidebar'] = 'agent-order-list';
        echo $this->viewoutput('pc/agent-order-list', $data);
    }
 
    /**
     * 绑定微信号
     */
    public function wechatBindAction()
    {
        $user = $this->auth->user();
 
        $wechat = new WechatSDK;
        $url = 'http://www.liowang.com/shop/wechat/oauth2/callback/bind';
        $state = ['env' => 'product'];
        if (isLocal()) {
            $state = ['env' => 'local'];
        }
        return $this->response->redirect($wechat->pcWebOauth2($url, $state), true);
    }
    /**
     * PC 微信回调 oauth2 绑定已登录用户
     */
    public function wechatOauth2CallbackBindAction()
    {
        global $ecs, $db, $user;
        include_once(ROOT_PATH . 'includes/lib_passport.php');
        $code = $this->request->getQuery('code');
        if (empty($code)) {
            echo '授权失败';
            exit;
        }
        $state = $this->request->getQuery('state');
        parse_str($state, $params);
        if (! islocal() && ! empty($params['env']) && $params['env'] == 'local') {
            return $this->response->redirect('http://liowang.dev.seafarer.me/shop/wechat/oauth2/callback/bind?' . http_build_query($this->request->getQuery()));
        }
 
        $wechat = new WechatSDK;
        $config = $this->config->wechat;
        $config->appid = $config->open_web_appid;
        $config->appsecret = $config->open_web_secret;
        $wechat->init($config);
        $response = $wechat->getUserTokenByCode($code);
        if (empty($response['errcode'])) {
            // 授权成功
            $ewxpcuser = Model\WxPcUser::findFirstByOpenid($response['openid']);
 
            $data = [];
            $isFirst = false;
            $isNeedCreate = false;
            $uuser = [];
            $wxpcuser = [];
            if ($ewxpcuser) {
                // 用户已经存在
                $euser = Model\User::findFirstByWxPcUserid($ewxpcuser['id']);
            } else {
                // 第一次访问
                $isFirst = true;
                $wxuser = Model\WxUser::findFirstByUnionid($response['unionid']);
                if (empty($wxuser)) {
                    $isNeedCreate = true;
                } else {
                    $euser = Model\User::findFirstByWxUserid($wxuser['id']);
                }
            }
            $wxpcuser['openid'] = $response['openid'];
            $wxpcuser['unionid'] = $response['unionid'];
            $wxpcuser['access_token'] = $response['access_token'];
            $wxpcuser['refresh_token'] = $response['refresh_token'];
            $wxpcuser['expired_at'] = $response['expires_in'] + time() - 200;
            $wxpcuser['scope'] = $response['scope'];
            $wxpcuser['ip'] = $this->request->getClientAddress();
            $info = $wechat->getUserInfoByOauth($response['openid'], $response['access_token']);
            $wxpcuser['nickname'] = $info['nickname'];
            $wxpcuser['gender'] = $info['sex'];
            $wxpcuser['language'] = $info['language'];
            $wxpcuser['city'] = $info['city'];
            $wxpcuser['country'] = $info['country'];
            $wxpcuser['province'] = $info['province'];
            $wxpcuser['headimgurl'] = $info['headimgurl'];
 
            if (! empty($euser) && $euser['user_id'] != $_SESSION['user_id']) {
                exit('该微信已被绑定');
            }
 
            if ($isFirst) {
                // 首次PC扫码登录
                $id = Model\WxPcUser::insert($wxpcuser);
                $uuser['wx_pc_userid'] = $id;
                Model\User::update($uuser, "user_id = {$_SESSION['user_id']}");
 
            } else {
                $uuser['wx_pc_userid'] = $ewxpcuser['id'];
                Model\User::update($uuser, "user_id = {$_SESSION['user_id']}");
                Model\WxPcUser::update($wxpcuser, "openid = '{$response['openid']}'");
            }
        } else {
            SystemException::error($response['errmsg']);
        }
        $referer = $this->url->get('user/info');
        return $this->response->redirect($referer, true);
    }
 
}
#5PageUserController->loginAction()
#6Phalcon\Dispatcher->dispatch()
#7Phalcon\Mvc\Application->handle()
/usr/local/www/liowang-shop-robotwar/shop/public/index.php (36)
<?php
 
error_reporting(E_ALL);
 
try {
 
    define('BASE_PATH', realpath('..') . '/');
    define('APP_PATH', BASE_PATH . 'app/');
    define('STORAGE_PATH', BASE_PATH . 'storage/');
 
    // 系统环境变量,参考laravel模式
    $systemEnvs = [
        'local' => [
            'juice-pc'
        ],
        'production' => [
            '10-10-92-111'
        ],
        'testing' => [
            '10-10-13-185'
        ],
    ];
 
    /**
     * 项目引导文件
     */
    $config = include __DIR__ . "/../app/start/start.php";
 
    /**
     * Handle the request
     */
    $application = new \Phalcon\Mvc\Application($di);
 
    $application->useImplicitView(false); // 禁用自动渲染
 
    echo $application->handle()->getContent();
 
} catch (\Exception $e) {
    //echo $e;
    throw $e;
Phalcon Framework 2.0.13

Warning: Cannot modify header information - headers already sent by (output started at /usr/local/www/liowang-shop-robotwar/shop/app/libraries/PrettyExceptions/Library/Phalcon/Utils/PrettyExceptions.php:339) in /usr/local/www/liowang-shop-robotwar/shop/app/libraries/PrettyExceptions/Library/Phalcon/Utils/PrettyExceptions.php on line 440
Exception - Undefined index: access_token
Undefined index: access_token
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (261)
#1{closure}(8, Undefined index: access_token, /usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php, 261, Array(9))
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (261)
<?php
 
/**
 * weixin library
 * @author geekzhumail@gmail.com
 * @since 2014-09-24
 * @edit 2015-03-05
 */
class SDKRuntimeException extends Exception {
 
    public function errorMessage() {
        return $this->getMessage();
    }
}
 
class WechatSDK extends WechatBase{
 
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => 'wxdf0a039cf4ea94d1',
        'appsecret' => '7100f0fe837a5287f075b6d2d9814ca0',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/';
    /**
     * @var access_token
     */
    protected $accessToken = '';
 
    /**
     * 是否从远程服务器拿token
     */
    protected $isRemote = false;
 
    public function __construct() {
        parent::__construct();
        $this->isRemote = $this->config->is_remote;
        $this->accessToken = $this->getToken();
    }
 
    /**
     * 初始化数据
     */
    public function init($config) {
        $this->config = $config;
    }
 
    /**
     * 获取Appid
     */
    public function getAppid() {
        return $this->config['appid'];
    }
 
    /**
     * weixin signature
     */
    public function checkSignature($signature, $timestamp, $nonce) {
        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
 
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
 
    /**
     * web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function webOauth2($redirectUri, $state = [], $type = 'snsapi_base') {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = $type;
        if (empty($state['type'])) {
            $state['type'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * pc web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function pcWebOauth2($redirectUri, $state = []) {
        $params = [];
        $params['appid'] = $this->config['open_web_appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = 'snsapi_login';
        if (empty($state['env'])) {
            $state['env'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * get access token by code
     */
    public function getUserTokenByCode($code) {
        $params = array();
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $params['code'] = $code;
        $params['grant_type'] = 'authorization_code';
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * refresh access token
     * @param string $token 填写通过access_token获取到的refresh_token参数
     */
    public function refreshUserToken($token) {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['grant_type'] = 'refresh_token';
        $params['refresh_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 根据网页授权拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function getUserInfoByOauth($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('sns/userinfo', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 验证授权凭证(access_token)是否有效
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function validUserToken($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/auth?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 获取公众号token
     */
    public function getToken() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        if ($this->isRemote) {
            return $this->getTokenFromRemote();
        }
 
        $params = [];
        $params['grant_type'] = 'client_credential';
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $url = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        // 写入缓存
        $expiredAt = $response['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $response['access_token'], $expiredAt);
        return $response['access_token'];
    }
 
    /**
     * 获取access_token from remote
     */
    public function getTokenFromRemote() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/token/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $cache->save('wechat_access_token_expired', $resp['expired_at'], $expiredAt);
            $expiredAt = 120;
            $cache->save($key, $resp['access_token'], $expiredAt);
            return $resp['access_token'];
        } else {
            throw new Exception('wx admin get token error');
        }
    }
 
    /**
     * 获取用户基本信息(包括UnionID机制)
     * @param string $openid 用户的唯一标识
     * @param string $token 调用接口凭证,默认值为Wechat::getToken()
     */
    public function getUserInfo($openid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('cgi-bin/user/info', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 下载已经上传到微信的资源文件
     */
    public function downloadMedia($mediaid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['access_token'] = $token;
        $params['media_id'] = $mediaid;
        $url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $content = curl_exec($ch);
        $info = curl_getinfo($ch);
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            // 正常则分割header和body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($content, 0, $headerSize);
            $body = substr($content, $headerSize);
        }
        curl_close($ch);
        $resp['info'] = $info;
        $resp['header'] = $header;
        $resp['body'] = $body;
        return $resp;
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicket()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        if ($this->isRemote) {
            return $this->getJsapiTicketFromRemote();
        }
        $resp = json_decode($this->get('cgi-bin/ticket/getticket', ['access_token' => $this->accessToken, 'type' => 'jsapi']), true);
 
        // 写入缓存
        $expiredAt = $resp['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $resp['ticket'], $expiredAt);
        return $resp['ticket'];
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicketFromRemote()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/jsticket/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $expiredAt = 120;
            $cache->save($key, $resp['ticket'], $expiredAt);
            return $resp['ticket'];
        } else {
            throw new Exception('wx admin get ticket error');
        }
    }
    /**
     * 获取js api 签名
     */
    public function getSignPackage($url = '')
    {
        $jsticket = $this->getJsapiTicket();
 
        if (! $url) {
            $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
        }
        $timestamp = time();
        $nonceStr = $this->createNoncestr(16);
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket={$jsticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        return [
            "appId"     => $this->config['appid'],
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }
 
 
    /**
     * 新建一个类,由于PSR-4无法获取不以文件名命名的类;
     */
    public function newClass($name)
    {
        return new $name();
    }
 
    // 微信支付
 
    /**
     * 获取微信付款码的prepayid
     * @params array $params 参数
     */
    public function getPrepayId($params) {
        $sdk = new UnifiedOrderSDK;
        $sdk->setParameter("trade_type","JSAPI");//交易类型
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $prepayid = $sdk->getPrepayId();
        return $prepayid;
    }
 
    /**
     * 获取支付js api 签名
     */
    public function getPaySignPackage($prepayid)
    {
        $jsApiObj["appId"] = $this->config['appid'];
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->createNoncestr();
        $jsApiObj["package"] = "prepay_id=$prepayid";
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->getSign($jsApiObj);
        $signPackage = $jsApiObj;
 
        return $signPackage;
    }
 
    /**
     * 发送红包接口
     * @params array $params 参数
     */
    public function sendRedPack($params) {
        $sdk = new RedPackSDK;
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $result = $sdk->send();
        return $result;
    }
}
 
/**
 * 所有接口的基类
 */
class WechatBase
{
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => '',
        'appsecret' => '',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/cgi-bin/';
 
    public function __construct() {
        $this->di = Phalcon\DI::getDefault();
        $this->config = $this->di->getConfig()['wechat'];
    }
 
    protected function get($url, $params = '')
    {
        if (strpos($url, 'https://') === false && strpos($url, 'http://') === false) {
            $url = $this->apiPrefix.$url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl get error');
        }
    }
 
    protected function post($url, $params = '', $data = '')
    {
        if (strpos($url, 'https://') === false) {
            $url = $this->apiPrefix . $url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        if ($data) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl post error');
        }
    }
 
    public function trimString($value)
    {
        $ret = null;
        if (null != $value)
        {
            $ret = $value;
            if (strlen($ret) == 0)
            {
                $ret = null;
            }
        }
        return $ret;
    }
 
    /**
     *   作用:产生随机字符串,不长于32位
     */
    public function createNoncestr( $length = 32, $type = 'all')
    {
        if ($type == 'all') {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        } else if($type == 'number') {
            $chars = "0123456789";
        }
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
 
    /**
     *   作用:格式化参数,签名过程需要使用
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $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['key'];
        //echo "【string2】".$String."</br>";
        //签名步骤三:MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }
 
    /**
     *   作用:array转xml
     */
    public 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;
    }
 
    /**
     *   作用:将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;
    }
 
    /**
     *   作用:以post方式提交xml到对应的接口url
     */
    public function postXmlCurl($xml,$url,$second=30)
    {
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        //返回结果
        if($data)
        {
            return $data;
        }
        else
        {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            return false;
        }
    }
 
    /**
     *   作用:使用证书,以post方式提交xml到对应的接口url
     */
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, $this->config['sslcert_path']);
        //默认格式为PEM,可以注释
        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);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return false;
        }
    }
 
}
 
/**
 * 请求型接口的基类
 */
class WechatPayRequestBase extends WechatBase
{
    var $parameters;//请求参数,类型为关联数组
    public $response;//微信返回的响应
    public $result;//返回参数,类型为关联数组
    var $url;//接口链接
    var $curl_timeout;//curl超时时间
 
    /**
     *   作用:设置请求参数
     */
    function setParameter($parameter, $parameterValue)
    {
        $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
    }
 
    /**
     *   作用:设置标配的请求参数,生成签名,生成接口参数xml
     */
    function createXml()
    {
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     *   作用:post请求xml
     */
    function postXml()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:使用证书post请求xml
     */
    function postXmlSSL()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:获取结果,默认不使用证书
     */
    function getResult()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        return $this->result;
    }
}
 
/**
 * 统一支付接口类
 */
class UnifiedOrderSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        //检测必填参数
        if(empty($this->parameters["out_trade_no"]))
        {
            throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
        }elseif(empty($this->parameters["body"])){
            throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
        }elseif (empty($this->parameters["total_fee"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
        }elseif (empty($this->parameters["notify_url"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
        }elseif (empty($this->parameters["trade_type"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
        }elseif ($this->parameters["trade_type"] == "JSAPI" &&
            empty($this->parameters["openid"])){
            throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
        }
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["spbill_create_ip"] = $this->di->getRequest()->getClientAddress();//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 获取prepay_id
     */
    function getPrepayId()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        $prepay_id = $this->result["prepay_id"];
        return $prepay_id;
    }
 
}
 
/**
 * 响应型接口基类
 */
class WechatPayServerBase extends WechatBase
{
  public $data;//接收到的数据,类型为关联数组
  var $returnParameters;//返回参数,类型为关联数组
 
  /**
   * 将微信的请求xml转换成关联数组,以方便数据处理
   */
  function saveData($xml)
  {
    $this->data = $this->xmlToArray($xml);
  }
 
  function checkSign()
  {
    $tmpData = $this->data;
    unset($tmpData['sign']);
    $sign = $this->getSign($tmpData);//本地签名
        if (isLocal()) {
            echo $sign;
        }
    if ($this->data['sign'] == $sign) {
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * 获取微信的请求数据
   */
  function getData()
  {
    return $this->data;
  }
 
  /**
   * 设置返回微信的xml数据
   */
  function setReturnParameter($parameter, $parameterValue)
  {
    $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
  }
 
  /**
   * 生成接口参数xml
   */
  function createXml()
  {
    return $this->arrayToXml($this->returnParameters);
  }
 
  /**
   * 将xml数据返回微信
   */
  function returnXml()
  {
    $returnXml = $this->createXml();
    return $returnXml;
  }
}
 
/**
 * 红包发送接口类
 */
class RedPackSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        $requiredParams = [
            'mch_billno',
            'nick_name',
            'send_name',
            're_openid',
            'total_amount',
            'min_value',
            'max_value',
            'total_num',
            'wishing',
            'act_name',
            'remark',
        ];
        //检测必填参数
        foreach ($requiredParams as $val) {
            if (empty($this->parameters[$val])) {
                throw new SDKRuntimeException("缺少红包接口必填参数{$val}!"."<br>");
            }
        }
        $this->parameters["wxappid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["client_ip"] = gethostbyname(gethostname());//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 发送红包
     *
     * curl 返回例子
     *  <xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[发放成功.]]></return_msg>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <err_code><![CDATA[0]]></err_code>
        <err_code_des><![CDATA[发放成功.]]></err_code_des>
        <mch_billno><![CDATA[1228563102201503261427336509]]></mch_billno>
        <mch_id>1228563102</mch_id>
        <wxappid><![CDATA[wxdceccb9098a978b8]]></wxappid>
        <re_openid><![CDATA[o55e0t1NHPxdcRCjbIu_xnjHc2u8]]></re_openid>
        <total_amount>100</total_amount>
        </xml>
     */
    public function send() {
        $this->postXmlSSL();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        return $this->result;
    }
#2WechatSDK->getTokenFromRemote()
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (209)
<?php
 
/**
 * weixin library
 * @author geekzhumail@gmail.com
 * @since 2014-09-24
 * @edit 2015-03-05
 */
class SDKRuntimeException extends Exception {
 
    public function errorMessage() {
        return $this->getMessage();
    }
}
 
class WechatSDK extends WechatBase{
 
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => 'wxdf0a039cf4ea94d1',
        'appsecret' => '7100f0fe837a5287f075b6d2d9814ca0',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/';
    /**
     * @var access_token
     */
    protected $accessToken = '';
 
    /**
     * 是否从远程服务器拿token
     */
    protected $isRemote = false;
 
    public function __construct() {
        parent::__construct();
        $this->isRemote = $this->config->is_remote;
        $this->accessToken = $this->getToken();
    }
 
    /**
     * 初始化数据
     */
    public function init($config) {
        $this->config = $config;
    }
 
    /**
     * 获取Appid
     */
    public function getAppid() {
        return $this->config['appid'];
    }
 
    /**
     * weixin signature
     */
    public function checkSignature($signature, $timestamp, $nonce) {
        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
 
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
 
    /**
     * web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function webOauth2($redirectUri, $state = [], $type = 'snsapi_base') {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = $type;
        if (empty($state['type'])) {
            $state['type'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * pc web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function pcWebOauth2($redirectUri, $state = []) {
        $params = [];
        $params['appid'] = $this->config['open_web_appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = 'snsapi_login';
        if (empty($state['env'])) {
            $state['env'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * get access token by code
     */
    public function getUserTokenByCode($code) {
        $params = array();
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $params['code'] = $code;
        $params['grant_type'] = 'authorization_code';
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * refresh access token
     * @param string $token 填写通过access_token获取到的refresh_token参数
     */
    public function refreshUserToken($token) {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['grant_type'] = 'refresh_token';
        $params['refresh_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 根据网页授权拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function getUserInfoByOauth($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('sns/userinfo', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 验证授权凭证(access_token)是否有效
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function validUserToken($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/auth?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 获取公众号token
     */
    public function getToken() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        if ($this->isRemote) {
            return $this->getTokenFromRemote();
        }
 
        $params = [];
        $params['grant_type'] = 'client_credential';
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $url = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        // 写入缓存
        $expiredAt = $response['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $response['access_token'], $expiredAt);
        return $response['access_token'];
    }
 
    /**
     * 获取access_token from remote
     */
    public function getTokenFromRemote() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/token/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $cache->save('wechat_access_token_expired', $resp['expired_at'], $expiredAt);
            $expiredAt = 120;
            $cache->save($key, $resp['access_token'], $expiredAt);
            return $resp['access_token'];
        } else {
            throw new Exception('wx admin get token error');
        }
    }
 
    /**
     * 获取用户基本信息(包括UnionID机制)
     * @param string $openid 用户的唯一标识
     * @param string $token 调用接口凭证,默认值为Wechat::getToken()
     */
    public function getUserInfo($openid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('cgi-bin/user/info', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 下载已经上传到微信的资源文件
     */
    public function downloadMedia($mediaid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['access_token'] = $token;
        $params['media_id'] = $mediaid;
        $url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $content = curl_exec($ch);
        $info = curl_getinfo($ch);
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            // 正常则分割header和body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($content, 0, $headerSize);
            $body = substr($content, $headerSize);
        }
        curl_close($ch);
        $resp['info'] = $info;
        $resp['header'] = $header;
        $resp['body'] = $body;
        return $resp;
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicket()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        if ($this->isRemote) {
            return $this->getJsapiTicketFromRemote();
        }
        $resp = json_decode($this->get('cgi-bin/ticket/getticket', ['access_token' => $this->accessToken, 'type' => 'jsapi']), true);
 
        // 写入缓存
        $expiredAt = $resp['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $resp['ticket'], $expiredAt);
        return $resp['ticket'];
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicketFromRemote()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/jsticket/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $expiredAt = 120;
            $cache->save($key, $resp['ticket'], $expiredAt);
            return $resp['ticket'];
        } else {
            throw new Exception('wx admin get ticket error');
        }
    }
    /**
     * 获取js api 签名
     */
    public function getSignPackage($url = '')
    {
        $jsticket = $this->getJsapiTicket();
 
        if (! $url) {
            $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
        }
        $timestamp = time();
        $nonceStr = $this->createNoncestr(16);
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket={$jsticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        return [
            "appId"     => $this->config['appid'],
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }
 
 
    /**
     * 新建一个类,由于PSR-4无法获取不以文件名命名的类;
     */
    public function newClass($name)
    {
        return new $name();
    }
 
    // 微信支付
 
    /**
     * 获取微信付款码的prepayid
     * @params array $params 参数
     */
    public function getPrepayId($params) {
        $sdk = new UnifiedOrderSDK;
        $sdk->setParameter("trade_type","JSAPI");//交易类型
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $prepayid = $sdk->getPrepayId();
        return $prepayid;
    }
 
    /**
     * 获取支付js api 签名
     */
    public function getPaySignPackage($prepayid)
    {
        $jsApiObj["appId"] = $this->config['appid'];
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->createNoncestr();
        $jsApiObj["package"] = "prepay_id=$prepayid";
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->getSign($jsApiObj);
        $signPackage = $jsApiObj;
 
        return $signPackage;
    }
 
    /**
     * 发送红包接口
     * @params array $params 参数
     */
    public function sendRedPack($params) {
        $sdk = new RedPackSDK;
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $result = $sdk->send();
        return $result;
    }
}
 
/**
 * 所有接口的基类
 */
class WechatBase
{
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => '',
        'appsecret' => '',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/cgi-bin/';
 
    public function __construct() {
        $this->di = Phalcon\DI::getDefault();
        $this->config = $this->di->getConfig()['wechat'];
    }
 
    protected function get($url, $params = '')
    {
        if (strpos($url, 'https://') === false && strpos($url, 'http://') === false) {
            $url = $this->apiPrefix.$url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl get error');
        }
    }
 
    protected function post($url, $params = '', $data = '')
    {
        if (strpos($url, 'https://') === false) {
            $url = $this->apiPrefix . $url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        if ($data) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl post error');
        }
    }
 
    public function trimString($value)
    {
        $ret = null;
        if (null != $value)
        {
            $ret = $value;
            if (strlen($ret) == 0)
            {
                $ret = null;
            }
        }
        return $ret;
    }
 
    /**
     *   作用:产生随机字符串,不长于32位
     */
    public function createNoncestr( $length = 32, $type = 'all')
    {
        if ($type == 'all') {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        } else if($type == 'number') {
            $chars = "0123456789";
        }
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
 
    /**
     *   作用:格式化参数,签名过程需要使用
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $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['key'];
        //echo "【string2】".$String."</br>";
        //签名步骤三:MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }
 
    /**
     *   作用:array转xml
     */
    public 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;
    }
 
    /**
     *   作用:将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;
    }
 
    /**
     *   作用:以post方式提交xml到对应的接口url
     */
    public function postXmlCurl($xml,$url,$second=30)
    {
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        //返回结果
        if($data)
        {
            return $data;
        }
        else
        {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            return false;
        }
    }
 
    /**
     *   作用:使用证书,以post方式提交xml到对应的接口url
     */
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, $this->config['sslcert_path']);
        //默认格式为PEM,可以注释
        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);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return false;
        }
    }
 
}
 
/**
 * 请求型接口的基类
 */
class WechatPayRequestBase extends WechatBase
{
    var $parameters;//请求参数,类型为关联数组
    public $response;//微信返回的响应
    public $result;//返回参数,类型为关联数组
    var $url;//接口链接
    var $curl_timeout;//curl超时时间
 
    /**
     *   作用:设置请求参数
     */
    function setParameter($parameter, $parameterValue)
    {
        $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
    }
 
    /**
     *   作用:设置标配的请求参数,生成签名,生成接口参数xml
     */
    function createXml()
    {
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     *   作用:post请求xml
     */
    function postXml()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:使用证书post请求xml
     */
    function postXmlSSL()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:获取结果,默认不使用证书
     */
    function getResult()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        return $this->result;
    }
}
 
/**
 * 统一支付接口类
 */
class UnifiedOrderSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        //检测必填参数
        if(empty($this->parameters["out_trade_no"]))
        {
            throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
        }elseif(empty($this->parameters["body"])){
            throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
        }elseif (empty($this->parameters["total_fee"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
        }elseif (empty($this->parameters["notify_url"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
        }elseif (empty($this->parameters["trade_type"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
        }elseif ($this->parameters["trade_type"] == "JSAPI" &&
            empty($this->parameters["openid"])){
            throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
        }
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["spbill_create_ip"] = $this->di->getRequest()->getClientAddress();//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 获取prepay_id
     */
    function getPrepayId()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        $prepay_id = $this->result["prepay_id"];
        return $prepay_id;
    }
 
}
 
/**
 * 响应型接口基类
 */
class WechatPayServerBase extends WechatBase
{
  public $data;//接收到的数据,类型为关联数组
  var $returnParameters;//返回参数,类型为关联数组
 
  /**
   * 将微信的请求xml转换成关联数组,以方便数据处理
   */
  function saveData($xml)
  {
    $this->data = $this->xmlToArray($xml);
  }
 
  function checkSign()
  {
    $tmpData = $this->data;
    unset($tmpData['sign']);
    $sign = $this->getSign($tmpData);//本地签名
        if (isLocal()) {
            echo $sign;
        }
    if ($this->data['sign'] == $sign) {
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * 获取微信的请求数据
   */
  function getData()
  {
    return $this->data;
  }
 
  /**
   * 设置返回微信的xml数据
   */
  function setReturnParameter($parameter, $parameterValue)
  {
    $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
  }
 
  /**
   * 生成接口参数xml
   */
  function createXml()
  {
    return $this->arrayToXml($this->returnParameters);
  }
 
  /**
   * 将xml数据返回微信
   */
  function returnXml()
  {
    $returnXml = $this->createXml();
    return $returnXml;
  }
}
 
/**
 * 红包发送接口类
 */
class RedPackSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        $requiredParams = [
            'mch_billno',
            'nick_name',
            'send_name',
            're_openid',
            'total_amount',
            'min_value',
            'max_value',
            'total_num',
            'wishing',
            'act_name',
            'remark',
        ];
        //检测必填参数
        foreach ($requiredParams as $val) {
            if (empty($this->parameters[$val])) {
                throw new SDKRuntimeException("缺少红包接口必填参数{$val}!"."<br>");
            }
        }
        $this->parameters["wxappid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["client_ip"] = gethostbyname(gethostname());//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 发送红包
     *
     * curl 返回例子
     *  <xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[发放成功.]]></return_msg>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <err_code><![CDATA[0]]></err_code>
        <err_code_des><![CDATA[发放成功.]]></err_code_des>
        <mch_billno><![CDATA[1228563102201503261427336509]]></mch_billno>
        <mch_id>1228563102</mch_id>
        <wxappid><![CDATA[wxdceccb9098a978b8]]></wxappid>
        <re_openid><![CDATA[o55e0t1NHPxdcRCjbIu_xnjHc2u8]]></re_openid>
        <total_amount>100</total_amount>
        </xml>
     */
    public function send() {
        $this->postXmlSSL();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        return $this->result;
    }
#3WechatSDK->getToken()
/usr/local/www/liowang-shop-robotwar/shop/app/libraries/WechatSDK.php (45)
<?php
 
/**
 * weixin library
 * @author geekzhumail@gmail.com
 * @since 2014-09-24
 * @edit 2015-03-05
 */
class SDKRuntimeException extends Exception {
 
    public function errorMessage() {
        return $this->getMessage();
    }
}
 
class WechatSDK extends WechatBase{
 
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => 'wxdf0a039cf4ea94d1',
        'appsecret' => '7100f0fe837a5287f075b6d2d9814ca0',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/';
    /**
     * @var access_token
     */
    protected $accessToken = '';
 
    /**
     * 是否从远程服务器拿token
     */
    protected $isRemote = false;
 
    public function __construct() {
        parent::__construct();
        $this->isRemote = $this->config->is_remote;
        $this->accessToken = $this->getToken();
    }
 
    /**
     * 初始化数据
     */
    public function init($config) {
        $this->config = $config;
    }
 
    /**
     * 获取Appid
     */
    public function getAppid() {
        return $this->config['appid'];
    }
 
    /**
     * weixin signature
     */
    public function checkSignature($signature, $timestamp, $nonce) {
        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
 
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
 
    /**
     * web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function webOauth2($redirectUri, $state = [], $type = 'snsapi_base') {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = $type;
        if (empty($state['type'])) {
            $state['type'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * pc web oauth2
     * @params string $redirectUri 回调地址
     * @params array $state 额外的参数
     * @params string $type 授权模式
     * @return string 授权地址
     */
    public function pcWebOauth2($redirectUri, $state = []) {
        $params = [];
        $params['appid'] = $this->config['open_web_appid'];
        $params['redirect_uri'] = $redirectUri;
        $params['response_type'] = 'code';
        $params['scope'] = 'snsapi_login';
        if (empty($state['env'])) {
            $state['env'] = 'product';
        }
        $params['state'] = urldecode(http_build_query($state));
        $url = 'http://open.weixin.qq.com/connect/qrconnect?' . http_build_query($params) . '#wechat_redirect';
        return $url;
    }
 
    /**
     * get access token by code
     */
    public function getUserTokenByCode($code) {
        $params = array();
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $params['code'] = $code;
        $params['grant_type'] = 'authorization_code';
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * refresh access token
     * @param string $token 填写通过access_token获取到的refresh_token参数
     */
    public function refreshUserToken($token) {
        $params = [];
        $params['appid'] = $this->config['appid'];
        $params['grant_type'] = 'refresh_token';
        $params['refresh_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 根据网页授权拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function getUserInfoByOauth($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('sns/userinfo', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 验证授权凭证(access_token)是否有效
     * @param string $openid 用户的唯一标识
     * @param string $token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     */
    public function validUserToken($openid, $token) {
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $url = 'https://api.weixin.qq.com/sns/auth?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 获取公众号token
     */
    public function getToken() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        if ($this->isRemote) {
            return $this->getTokenFromRemote();
        }
 
        $params = [];
        $params['grant_type'] = 'client_credential';
        $params['appid'] = $this->config['appid'];
        $params['secret'] = $this->config['appsecret'];
        $url = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $response = json_decode($response, true);
        // 写入缓存
        $expiredAt = $response['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $response['access_token'], $expiredAt);
        return $response['access_token'];
    }
 
    /**
     * 获取access_token from remote
     */
    public function getTokenFromRemote() {
        $cache = $this->di->getCache();
        $key = 'wechat_access_token_' . $this->config['appid'];
        if ($cache->exists($key) && $token = $cache->get($key)) {
            return $token;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/token/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $cache->save('wechat_access_token_expired', $resp['expired_at'], $expiredAt);
            $expiredAt = 120;
            $cache->save($key, $resp['access_token'], $expiredAt);
            return $resp['access_token'];
        } else {
            throw new Exception('wx admin get token error');
        }
    }
 
    /**
     * 获取用户基本信息(包括UnionID机制)
     * @param string $openid 用户的唯一标识
     * @param string $token 调用接口凭证,默认值为Wechat::getToken()
     */
    public function getUserInfo($openid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['openid'] = $openid;
        $params['access_token'] = $token;
        $params['lang'] = 'zh_CN';
        $response = $this->get('cgi-bin/user/info', $params);
        $response = json_decode($response, true);
        return $response;
    }
 
    /**
     * 下载已经上传到微信的资源文件
     */
    public function downloadMedia($mediaid, $token = '') {
        if (empty($token)) {
            $token = $this->getToken();
        }
        $params = [];
        $params['access_token'] = $token;
        $params['media_id'] = $mediaid;
        $url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?' . http_build_query($params);
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $content = curl_exec($ch);
        $info = curl_getinfo($ch);
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            // 正常则分割header和body
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($content, 0, $headerSize);
            $body = substr($content, $headerSize);
        }
        curl_close($ch);
        $resp['info'] = $info;
        $resp['header'] = $header;
        $resp['body'] = $body;
        return $resp;
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicket()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        if ($this->isRemote) {
            return $this->getJsapiTicketFromRemote();
        }
        $resp = json_decode($this->get('cgi-bin/ticket/getticket', ['access_token' => $this->accessToken, 'type' => 'jsapi']), true);
 
        // 写入缓存
        $expiredAt = $resp['expires_in'] - 5 * 60;// 设置缓存时间(分钟)
        $cache->save($key, $resp['ticket'], $expiredAt);
        return $resp['ticket'];
    }
 
    /**
     * 获取 js api ticket
     */
    public function getJsapiTicketFromRemote()
    {
        $cache = $this->di->getCache();
        $key = 'wechat_js_ticket' . $this->config['appid'];
        if ($cache->exists($key) && $ticket = $cache->get($key)) {
            return $ticket;
        }
        $params = [];
        $params['appid'] = $this->config['appid'];
        $url = 'http://wx.seafarer.me/openapi/v1/jsticket/get';
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        $options[CURLOPT_POSTFIELDS] = $params;
        curl_setopt_array($ch, $options);
        $resp = curl_exec($ch);
        $resp = json_decode($resp, true);
        $status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($status["http_code"]) == 200) {
            // 写入缓存
            $expiredAt = $resp['expired_at'] - time();// 设置缓存时间(秒)
            $expiredAt = 120;
            $cache->save($key, $resp['ticket'], $expiredAt);
            return $resp['ticket'];
        } else {
            throw new Exception('wx admin get ticket error');
        }
    }
    /**
     * 获取js api 签名
     */
    public function getSignPackage($url = '')
    {
        $jsticket = $this->getJsapiTicket();
 
        if (! $url) {
            $url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
        }
        $timestamp = time();
        $nonceStr = $this->createNoncestr(16);
        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket={$jsticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        return [
            "appId"     => $this->config['appid'],
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => sha1($string),
            "rawString" => $string
        ];
    }
 
 
    /**
     * 新建一个类,由于PSR-4无法获取不以文件名命名的类;
     */
    public function newClass($name)
    {
        return new $name();
    }
 
    // 微信支付
 
    /**
     * 获取微信付款码的prepayid
     * @params array $params 参数
     */
    public function getPrepayId($params) {
        $sdk = new UnifiedOrderSDK;
        $sdk->setParameter("trade_type","JSAPI");//交易类型
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $prepayid = $sdk->getPrepayId();
        return $prepayid;
    }
 
    /**
     * 获取支付js api 签名
     */
    public function getPaySignPackage($prepayid)
    {
        $jsApiObj["appId"] = $this->config['appid'];
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->createNoncestr();
        $jsApiObj["package"] = "prepay_id=$prepayid";
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->getSign($jsApiObj);
        $signPackage = $jsApiObj;
 
        return $signPackage;
    }
 
    /**
     * 发送红包接口
     * @params array $params 参数
     */
    public function sendRedPack($params) {
        $sdk = new RedPackSDK;
        foreach ($params as $key => $val) {
            $sdk->setParameter($key, $val);
        }
        $result = $sdk->send();
        return $result;
    }
}
 
/**
 * 所有接口的基类
 */
class WechatBase
{
    /**
     * 配置参数
     * @var
     */
    protected $config = [
        'appid' => '',
        'appsecret' => '',
    ];
 
    /**
     * api地址
     * @var
     */
    protected $apiPrefix = 'https://api.weixin.qq.com/cgi-bin/';
 
    public function __construct() {
        $this->di = Phalcon\DI::getDefault();
        $this->config = $this->di->getConfig()['wechat'];
    }
 
    protected function get($url, $params = '')
    {
        if (strpos($url, 'https://') === false && strpos($url, 'http://') === false) {
            $url = $this->apiPrefix.$url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true
        ];
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl get error');
        }
    }
 
    protected function post($url, $params = '', $data = '')
    {
        if (strpos($url, 'https://') === false) {
            $url = $this->apiPrefix . $url;
        }
        if ($params) {
            $url .= '?'.http_build_query($params);
        }
 
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
        ];
        if ($data) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $options);
        $response = curl_exec($ch);
        $resp_status = curl_getinfo($ch);
        curl_close($ch);
        if (intval($resp_status["http_code"]) == 200) {
            return $response;
        } else {
            throw new Exception('WechatSDK curl post error');
        }
    }
 
    public function trimString($value)
    {
        $ret = null;
        if (null != $value)
        {
            $ret = $value;
            if (strlen($ret) == 0)
            {
                $ret = null;
            }
        }
        return $ret;
    }
 
    /**
     *   作用:产生随机字符串,不长于32位
     */
    public function createNoncestr( $length = 32, $type = 'all')
    {
        if ($type == 'all') {
            $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        } else if($type == 'number') {
            $chars = "0123456789";
        }
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {
            $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }
        return $str;
    }
 
    /**
     *   作用:格式化参数,签名过程需要使用
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v)
        {
            if($urlencode)
            {
               $v = urlencode($v);
            }
            //$buff .= strtolower($k) . "=" . $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['key'];
        //echo "【string2】".$String."</br>";
        //签名步骤三:MD5加密
        $String = md5($String);
        //echo "【string3】 ".$String."</br>";
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        //echo "【result】 ".$result_."</br>";
        return $result_;
    }
 
    /**
     *   作用:array转xml
     */
    public 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;
    }
 
    /**
     *   作用:将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;
    }
 
    /**
     *   作用:以post方式提交xml到对应的接口url
     */
    public function postXmlCurl($xml,$url,$second=30)
    {
        //初始化curl
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        curl_close($ch);
        //返回结果
        if($data)
        {
            return $data;
        }
        else
        {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            return false;
        }
    }
 
    /**
     *   作用:使用证书,以post方式提交xml到对应的接口url
     */
    public function postXmlSSLCurl($xml,$url,$second=30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch,CURLOPT_TIMEOUT,$second);
        //这里设置代理,如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch,CURLOPT_URL, $url);
        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);
        //设置证书
        //使用证书:cert 与 key 分别属于两个.pem文件
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, $this->config['sslcert_path']);
        //默认格式为PEM,可以注释
        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);
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }
        else {
            $error = curl_errno($ch);
            echo "curl出错,错误码:$error"."<br>";
            echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return false;
        }
    }
 
}
 
/**
 * 请求型接口的基类
 */
class WechatPayRequestBase extends WechatBase
{
    var $parameters;//请求参数,类型为关联数组
    public $response;//微信返回的响应
    public $result;//返回参数,类型为关联数组
    var $url;//接口链接
    var $curl_timeout;//curl超时时间
 
    /**
     *   作用:设置请求参数
     */
    function setParameter($parameter, $parameterValue)
    {
        $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
    }
 
    /**
     *   作用:设置标配的请求参数,生成签名,生成接口参数xml
     */
    function createXml()
    {
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     *   作用:post请求xml
     */
    function postXml()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:使用证书post请求xml
     */
    function postXmlSSL()
    {
        $xml = $this->createXml();
        $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout);
        return $this->response;
    }
 
    /**
     *   作用:获取结果,默认不使用证书
     */
    function getResult()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        return $this->result;
    }
}
 
/**
 * 统一支付接口类
 */
class UnifiedOrderSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        //检测必填参数
        if(empty($this->parameters["out_trade_no"]))
        {
            throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
        }elseif(empty($this->parameters["body"])){
            throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
        }elseif (empty($this->parameters["total_fee"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
        }elseif (empty($this->parameters["notify_url"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
        }elseif (empty($this->parameters["trade_type"])) {
            throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
        }elseif ($this->parameters["trade_type"] == "JSAPI" &&
            empty($this->parameters["openid"])){
            throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
        }
        $this->parameters["appid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["spbill_create_ip"] = $this->di->getRequest()->getClientAddress();//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 获取prepay_id
     */
    function getPrepayId()
    {
        $this->postXml();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        $prepay_id = $this->result["prepay_id"];
        return $prepay_id;
    }
 
}
 
/**
 * 响应型接口基类
 */
class WechatPayServerBase extends WechatBase
{
  public $data;//接收到的数据,类型为关联数组
  var $returnParameters;//返回参数,类型为关联数组
 
  /**
   * 将微信的请求xml转换成关联数组,以方便数据处理
   */
  function saveData($xml)
  {
    $this->data = $this->xmlToArray($xml);
  }
 
  function checkSign()
  {
    $tmpData = $this->data;
    unset($tmpData['sign']);
    $sign = $this->getSign($tmpData);//本地签名
        if (isLocal()) {
            echo $sign;
        }
    if ($this->data['sign'] == $sign) {
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * 获取微信的请求数据
   */
  function getData()
  {
    return $this->data;
  }
 
  /**
   * 设置返回微信的xml数据
   */
  function setReturnParameter($parameter, $parameterValue)
  {
    $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue);
  }
 
  /**
   * 生成接口参数xml
   */
  function createXml()
  {
    return $this->arrayToXml($this->returnParameters);
  }
 
  /**
   * 将xml数据返回微信
   */
  function returnXml()
  {
    $returnXml = $this->createXml();
    return $returnXml;
  }
}
 
/**
 * 红包发送接口类
 */
class RedPackSDK extends WechatPayRequestBase
{
    function __construct()
    {
        parent::__construct();
        //设置接口链接
        $this->url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
        //设置curl超时时间
        $this->curl_timeout = $this->config['curl_timeout'];
    }
 
    /**
     * 生成接口参数xml
     */
    function createXml()
    {
        $requiredParams = [
            'mch_billno',
            'nick_name',
            'send_name',
            're_openid',
            'total_amount',
            'min_value',
            'max_value',
            'total_num',
            'wishing',
            'act_name',
            'remark',
        ];
        //检测必填参数
        foreach ($requiredParams as $val) {
            if (empty($this->parameters[$val])) {
                throw new SDKRuntimeException("缺少红包接口必填参数{$val}!"."<br>");
            }
        }
        $this->parameters["wxappid"] = $this->config['appid'];//公众账号ID
        $this->parameters["mch_id"] = $this->config['mchid'];//商户号
        $this->parameters["client_ip"] = gethostbyname(gethostname());//终端ip
        $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
        $this->parameters["sign"] = $this->getSign($this->parameters);//签名
        return  $this->arrayToXml($this->parameters);
    }
 
    /**
     * 发送红包
     *
     * curl 返回例子
     *  <xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[发放成功.]]></return_msg>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <err_code><![CDATA[0]]></err_code>
        <err_code_des><![CDATA[发放成功.]]></err_code_des>
        <mch_billno><![CDATA[1228563102201503261427336509]]></mch_billno>
        <mch_id>1228563102</mch_id>
        <wxappid><![CDATA[wxdceccb9098a978b8]]></wxappid>
        <re_openid><![CDATA[o55e0t1NHPxdcRCjbIu_xnjHc2u8]]></re_openid>
        <total_amount>100</total_amount>
        </xml>
     */
    public function send() {
        $this->postXmlSSL();
        $this->result = $this->xmlToArray($this->response);
        if ($this->result['return_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['return_msg']);
        }
        if ($this->result['result_code'] == 'FAIL') {
            throw new SDKRuntimeException($this->result['err_code_des']);
        }
        return $this->result;
    }
#4WechatSDK->__construct()
/usr/local/www/liowang-shop-robotwar/shop/app/controllers/PageUserController.php (24)
<?php
 
use HomeSpace\Model;
 
class PageUserController extends PageBaseController {
 
    /**
     * 登录
     */
    public function loginAction()
    {
        $data = [];
        //echo '登录';
        $act = $this->request->getQuery('act');
        if ($act == 'not_active') {
            if (empty($_SESSION['tmp_email'])) {
                $act = '';
            }
            $data['email'] = $_SESSION['tmp_email'];
        }
        $data['act'] = $act;
        $data['errmsg'] = $this->flash->getMessages('error');
        $this->flash->clear();
        $wechat = new WechatSDK;
        //$url = $this->url->get('wechat/oauth2/callback');
        $url = 'http://www.liowang.com/shop/wechat/oauth2/callback';
        $state = ['env' => 'product'];
        if (isLocal()) {
            $state = ['env' => 'local'];
        }
        $data['wechat_login_url'] = $wechat->pcWebOauth2($url, $state);
        echo $this->viewoutput('pc/login', $data);
    }
 
    /**
     * 登录 POST
     */
    public function postLoginAction()
    {
        global $user, $_LANG;
        $errmsg = '';
        $account = $this->request->getPost('account');
        $password = $this->request->getPost('password');
        $remember = ! empty($this->request->getQuery('remember'));
        if (filter_var($account, FILTER_VALIDATE_EMAIL)) {
            // 邮箱登录
            $muser = Model\User::findFirst(['columns' => 'user_name', 'conditions' => "email = '$account'"]);
        } else if(is_numeric($account) && strlen($account) == 11) {
            // 手机登录
            $muser = Model\User::findFirst(['columns' => 'user_name', 'conditions' => "mobile_phone = '$account'"]);
        }
        if (! empty($muser) && ! empty($password)) {
            if ($user->login($muser['user_name'], $password, $remember)) {
                $row = Model\User::findFirstByUserId($_SESSION['user_id']);
                if ($row['is_validated'] == 1) {
                    update_user_info();
                    recalculate_price();
 
                    $this->auth->loginUsingId($_SESSION['user_id']);
                    $_SESSION['pc_login'] = true;
                    return $this->response->redirect('home/index');
                } else {
                    //$_SESSION['login_fail'] ++ ;
                    //$errmsg = '该账号尚未激活请查阅该账号邮箱的激活邮件,或者联系客服';
                    $_SESSION['tmp_email'] = $row['email'];
                    return $this->response->redirect('login?act=not_active');
                }
            } else {
                $_SESSION['login_fail'] ++ ;
                $errmsg = $_LANG['login_failure'];
            }
        }
        $errmsg = $errmsg ?: '账号或者密码错误';
        $this->flash->error($errmsg);
        return $this->response->redirect('login');
    }
 
    /**
     * 退出登录
     */
    public function logoutAction()
    {
        global $user;
        $user->logout();
        return $this->response->redirect($this->request->getHTTPReferer(), true);
    }
 
    /**
     * 邮箱注册验证
     */
    public function emailValidateAction()
    {
        include_once(ROOT_PATH . 'includes/lib_passport.php');
        $data = [];
        $data['status'] = 'user_not_exist';
        $hash = $this->request->getQuery('hash');
        if ($hash) {
            $id = intval(register_hash('decode', $hash));
            $user = Model\User::findFirstByUserId($id);
            if ($user) {
                if ($user['is_validated'] == 0) {
                    Model\User::update(['is_validated' => 1], ['user_id' => $id]);
                    $data['status'] = 'success';
                } else {
                    $data['status'] = 'validated';
                }
                $data['user'] = $user;
            }
        }
        echo $this->viewoutput('pc/email-validate', $data);
    }
 
    /**
     * PC 微信回调 oauth2 函数
     */
    public function wechatOauth2CallbackAction()
    {
        global $ecs, $db, $user;
        include_once(ROOT_PATH . 'includes/lib_passport.php');
        $code = $this->request->getQuery('code');
        if (empty($code)) {
            echo '授权失败';
            exit;
        }
        $state = $this->request->getQuery('state');
        parse_str($state, $params);
        if (! islocal() && ! empty($params['env']) && $params['env'] == 'local') {
            return $this->response->redirect('http://liowang.dev.seafarer.me/shop/wechat/oauth2/callback?' . http_build_query($this->request->getQuery()));
        }
 
        $wechat = new WechatSDK;
        $config = $this->config->wechat;
        $config->appid = $config->open_web_appid;
        $config->appsecret = $config->open_web_secret;
        $wechat->init($config);
        $response = $wechat->getUserTokenByCode($code);
        if (empty($response['errcode'])) {
            // 授权成功
            $ewxpcuser = Model\WxPcUser::findFirstByOpenid($response['openid']);
 
            $data = [];
            $isFirst = false;
            $isNeedCreate = false;
            $uuser = [];
            $wxpcuser = [];
            if ($ewxpcuser) {
                // 用户已经存在
                $euser = Model\User::findFirstByWxPcUserid($ewxpcuser['id']);
            } else {
                // 第一次访问
                $isFirst = true;
                $wxuser = Model\WxUser::findFirstByUnionid($response['unionid']);
                if (empty($wxuser)) {
                    $isNeedCreate = true;
                } else {
                    $euser = Model\User::findFirstByWxUserid($wxuser['id']);
                }
            }
            $wxpcuser['openid'] = $response['openid'];
            $wxpcuser['unionid'] = $response['unionid'];
            $wxpcuser['access_token'] = $response['access_token'];
            $wxpcuser['refresh_token'] = $response['refresh_token'];
            $wxpcuser['expired_at'] = $response['expires_in'] + time() - 200;
            $wxpcuser['scope'] = $response['scope'];
            $wxpcuser['ip'] = $this->request->getClientAddress();
            $info = $wechat->getUserInfoByOauth($response['openid'], $response['access_token']);
            $wxpcuser['nickname'] = $info['nickname'];
            $wxpcuser['gender'] = $info['sex'];
            $wxpcuser['language'] = $info['language'];
            $wxpcuser['city'] = $info['city'];
            $wxpcuser['country'] = $info['country'];
            $wxpcuser['province'] = $info['province'];
            $wxpcuser['headimgurl'] = $info['headimgurl'];
 
            if ($isFirst) {
                // 首次PC扫码登录
                $id = Model\WxPcUser::insert($wxpcuser);
                $_SESSION['wx_pc_user_id'] = $id;
                $uuser['wx_pc_userid'] = $id;
                if (! $isNeedCreate) {
                    Model\User::update($uuser, "user_id = {$euser['user_id']}");
                }
                /**
                if ($isNeedCreate) {
                    // 创建新用户
                    $uuser['avatar'] = 'shop/assets/pc/IMGS/defaultPhoto.png';
                    $name = '会员_' . time() . '_' . $id;
                    $uuser['nickname'] = $wxpcuser['nickname'];
                    $email = 'email_' . time() . '_' . $id . '@liowang.com';
                    if (register($name, '123123', $email) !== false) {;
                        $db->autoExecute($ecs->table('users'), $uuser, 'UPDATE', "user_id = {$_SESSION['user_id']}");
                    } else {
                        SystemException::error('授权失败');
                    }
                } else {
                    // 绑定已在微信授权的用户
                    Model\User::update($uuser, "user_id = {$euser['user_id']}");
                    Model\WxPcUser::update($wxpcuser, "openid = '{$response['openid']}'");
                    $user->set_session($euser['user_name']);
                    $user->set_cookie($euser['user_name']);
                    update_user_info();
                    recalculate_price();
                }**/
            } else {
                Model\WxPcUser::update($wxpcuser, "openid = '{$response['openid']}'");
                $_SESSION['wx_pc_user_id'] = $ewxpcuser['id'];
            }
            if (empty($euser)) {
                return $this->response->redirect('wechat/oauth2/register');
            }
            // 已注册直接登录
            $user->set_session($euser['user_name']);
            $user->set_cookie($euser['user_name']);
            update_user_info();
            recalculate_price();
            $this->auth->loginUsingId($_SESSION['user_id']);
            $_SESSION['pc_login'] = true;
        } else {
            SystemException::error($response['errmsg']);
        }
        $referer = $this->url->get('');
        return $this->response->redirect($referer, true);
    }
 
    /**
     * PC 微信账号登录绑定
     */
    public function wechatOauth2RegisterAction()
    {
        global $ecs, $db, $user;
 
        if (empty($_SESSION['wx_pc_user_id'])) {
            return $this->response->redirect('login');
        }
        $data = [];
        echo $this->viewoutput('pc/wechat-oauth2-register', $data);
    }
 
    /******** 需要登录授权 *********/
    /**
     * 个人信息
     */
    public function infoAction()
    {
        $user = $this->auth->user();
 
        list($user['birYear'], $user['birMonth'], $user['birDay']) = explode('-', $user['birthday']);
        $user['birMonth'] = ltrim($user['birMonth'], 0);
        $user['birDay'] = ltrim($user['birDay'], 0);
        if (strpos($user['avatar'], 'http://') === false && strpos($user['avatar'], 'https://') === false) {
            $user['avatar'] = 'http://' . $this->request->getHttpHost() . '/' . $user['avatar'];
        }
        $data = [];
        $data['user'] = $user;
        $data['navName'] = '';
        $data['userSidebar'] = 'user-info';
        echo $this->viewoutput('pc/user-info', $data);
    }
    /**
     * 购物车
     */
    public function cartAction()
    {
        require(ROOT_PATH . 'includes/lib_order.php');
        $user = $this->auth->user();
        $mcart = new Model\Cart;
        $cartCount = $mcart->getCount();
        $cartGoods = get_cart_goods();
        $discount = compute_discount();
 
        $goodsids = [];
        foreach ($cartGoods['goods_list'] as $goods) {
            $goodsids[] = $goods['goods_id'];
        }
        if ($goodsids) {
            $collectGoodsList = Model\CollectGoods::find(['conditions' => "user_id='$_SESSION[user_id]' AND " . db_create_in($goodsids, 'goods_id')]);
            foreach ($cartGoods['goods_list'] as &$goods) {
                $goods['is_collect'] = false;
                foreach ($collectGoodsList as $row) {
                    if ($row['goods_id'] == $goods['goods_id']) {
                        $goods['is_collect'] = true;
                        break;
                    }
                }
            }
            unset($goods);
        }
 
        $data = [];
        $data['user'] = $user;
        $data['cartGoods'] = $cartGoods;
        $data['navName'] = '';
        echo $this->viewoutput('pc/user-cart', $data);
    }
    /**
     * 购物车结算
     */
    public function cartSettleAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        require(ROOT_PATH . 'includes/lib_order.php');
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
        $user = $this->auth->user();
 
        $recids = $this->request->getQuery('rec_ids') ?: [];
 
        $cartGoods = get_cart_goods($recids);
        if (empty($cartGoods['goods_list'])) {
            // 购物车为空
            return $this->response->redirect('user/cart');
        }
        $discount = compute_discount();
 
        $data = [];
 
        $address_id = intval($this->request->getQuery('address_id'));
        if ($address_id == 0) {
            $address_id = $user['address_id'];
        }
        $address = Model\UserAddress::findFirst(['conditions' => "address_id = $address_id AND user_id = {$user['user_id']}"]);
        $addressList = Model\UserAddress::find(['conditions' => "address_id != $address_id AND user_id = {$user['user_id']}", 'order' => 'address_id DESC']);
        if ($address) {
            array_unshift($addressList, $address);
        }
        $regions = Model\Region::getAll();
        foreach ($addressList as &$row) {
            $country = $regions[$row['country']];
            $province = $country['list'][$row['province']];
            $city = $province['list'][$row['city']];
            $district = $city['list'][$row['district']];
            $row['address_detail'] = $province['region_name'] . $city['region_name'] . $district['region_name'] . $row['address'];
        }
        unset($row);
        $data['addressList'] = $addressList;
        foreach ($cartGoods['goods_list'] as &$goods) {
            //$goods['goods_name'] = mb_substr($goods['goods_name'], 0, 15, 'utf-8') . '......';
        }
        unset($goods);
 
        $regions = Model\Region::getAll();
 
        $data['user'] = $user;
        $data['regions'] = $regions;
        $data['cartGoods'] = $cartGoods;
        $data['navName'] = '';
        echo $this->viewoutput('pc/cart-settle', $data);
    }
 
    /**
     * 已购买的商品
     */
    public function boughtListAction()
    {
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'includes/lib_order.php');
        $user = $this->auth->user();
        $mcart = new Model\Cart;
 
        global $db, $ecs;
 
        $page = intval($this->request->getQuery('page')) ?: 1;
        $limit = 10;
        $offset = ($page - 1) * $limit;
        $userid = $_SESSION['user_id'];
        $morder = new Model\OrderInfo;
 
        $arr    = array();
        $osArr = [];
        $ssArr = [];
        $psArr = [];
        // 需要评论的order_id
        $result = $morder->needComment();
        $needCommentOrderids = [];
        foreach ($result as $row) {
            $needCommentOrderids[$row['order_id']] = $row['order_id'];
        }
 
        $osArr[] = OS_CONFIRMED;
        $osArr[] = OS_SPLITED;
        $ssArr[] = SS_RECEIVED;
        $psArr[] = PS_PAYED;
        $ossql = $sssql = $pssql = '';
        if ($osArr) {
            $ossql = ' AND ' . db_create_in($osArr, 'order_status');
        }
        if ($ssArr) {
            $sssql = ' AND ' . db_create_in($ssArr, 'shipping_status');
        }
        if ($psArr) {
            $pssql = ' AND ' . db_create_in($psArr, 'pay_status');
        }
        $sql = "SELECT order_id, order_sn, order_status, shipping_status, pay_status, add_time, " .
               "(goods_amount + shipping_fee + insure_fee + pay_fee + pack_fee + card_fee + tax - discount) AS total_fee ".
               " FROM " .$GLOBALS['ecs']->table('order_info') .
               ' WHERE 1 = 1';
        $sql .= " AND user_id = '$userid' $ossql $sssql $pssql ORDER BY add_time DESC";
        //$res = $GLOBALS['db']->SelectLimit($sql, 20, ($page - 1) * 20);
        $res = $GLOBALS['db']->query($sql);
 
        $orderids = [];
        while ($row = $GLOBALS['db']->fetchRow($res))
        {
            $orderids[] = $row['order_id'];
            $row['shipping_status'] = ($row['shipping_status'] == SS_SHIPPED_ING) ? SS_PREPARING : $row['shipping_status'];
 
            $arr[$row['order_id']] = array('order_id'       => $row['order_id'],
                           'order_sn'       => $row['order_sn'],
                           'order_time'     => local_date($GLOBALS['_CFG']['time_format'], $row['add_time']),
                           'order_status'   => $row['order_status'],
                           'shipping_status'   => $row['shipping_status'],
                           'pay_status'   => $row['pay_status'],
                           'total_fee'      => price_format($row['total_fee'], false),
                           'needComment' => isset($needCommentOrderids[$row['order_id']]) ? true : false,
                           'goods' => [],
                    );
        }
        $orders = $arr;
        $count = 0;
        if ($orderids) {
            $orderidsStr = implode(',', $orderids);
 
            /* 订单商品 */
            $params = [
                'alias' => 'og',
                'columns' => "og.rec_id, og.order_id, og.goods_id, og.goods_name, og.goods_sn, og.market_price, og.goods_number, " .
                    "og.goods_price, og.goods_attr, og.is_real, og.parent_id, og.is_gift, " .
                    "og.goods_price * og.goods_number AS subtotal, og.extension_code, g.goods_thumb, g.goods_img " .
                    ',c.comment_id ',
                'conditions' =>  "og.order_id IN ($orderidsStr)",
                'offset' => $offset,
                'limit' => $limit,
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = og.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'comment',
                        'alias' => 'c',
                        'on' => 'c.id_value = og.goods_id AND c.order_id = og.order_id '
                    ]
                ],
            ];
            $colls = Model\OrderGoods::find($params);
            unset($params['limit'], $params['offset']);
            $count = Model\OrderGoods::count($params);
            foreach ($colls as $row) {
                if (isset($orders[$row['order_id']])) {
                    $row['market_price'] = price_format($row['market_price'], false);
                    $row['goods_price']  = price_format($row['goods_price'], false);
                    $row['subtotal']     = price_format($row['subtotal'], false);
                    $orders[$row['order_id']]['goods'][] = $row;
                }
            }
        }
 
        $totalPage = $count ? intval(($count - 1) / $limit) + 1 : 1;
 
        $data = [];
        $data['user'] = $user;
        $data['page'] = $page;
        $data['totalPage'] = $totalPage;
        $data['count'] = $count;
        $data['orders'] = $orders;
        $data['navName'] = '';
        $data['userSidebar'] = 'bought-list';
        echo $this->viewoutput('pc/bought-list', $data);
    }
 
    /**
     * 我的收藏
     */
    public function collectListAction()
    {
        global $db, $ecs;
        $user = $this->auth->user();
        $page = intval($this->request->getQuery('page')) ?: 1;
        $ctype = $this->request->getQuery('collect_type') == 'brand' ? 'brand' : 'goods';
        $limit = 10;
        $offset = ($page - 1) * $limit;
        $userid = $user['user_id'];
 
        $data = [];
        if ($ctype == 'goods') {
            $params = [
                'alias' => 'c',
                'columns' => 'g.goods_id, g.goods_name, g.market_price, g.shop_price AS org_price, '.
                    "IFNULL(mp.user_price, g.shop_price * '$_SESSION[discount]') AS shop_price, g.goods_img, ".
                    'g.promote_price, g.promote_start_date,g.promote_end_date, c.rec_id, c.is_attention',
                'conditions' =>  "c.user_id = '$userid' ORDER BY c.rec_id DESC",
                'offset' => $offset,
                'limit' => $limit,
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = c.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'member_price',
                        'alias' => 'mp',
                        'on' => "mp.goods_id = g.goods_id AND mp.user_rank = '$_SESSION[user_rank]' "
                    ],
                ],
            ];
            $goodsList = Model\CollectGoods::find($params);
            foreach ($goodsList as &$row) {
                if ($row['promote_price'] > 0) {
                    $promote_price = bargain_price($row['promote_price'], $row['promote_start_date'], $row['promote_end_date']);
                    $row['promote_price'] = $promote_price > 0 ? price_format($promote_price) : '';
                } else {
                    $row['promote_price'] = '';
                }
                if ($row['promote_price'] > 0) {
                    $row['price'] = $row['promote_price'];
                } else {
                    $row['price'] = $row['shop_price'];
                }
            }
            unset($row);
            unset($params['limit'], $params['offset']);
            $count = Model\CollectGoods::count($params);
 
            $data['list'] = $goodsList;
        } else {
            $params = [
                'alias' => 'cb',
                'columns' => 'b.*',
                'conditions' =>  "cb.user_id = '$userid'",
                'offset' => $offset,
                'limit' => $limit,
                'order' => 'cb.id DESC',
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'brand',
                        'alias' => 'b',
                        'on' => 'b.brand_id = cb.brand_id'
                    ]
                ],
            ];
            $brandList = Model\CollectBrand::find($params);
            foreach ($brandList as &$row) {
 
            }
            unset($row);
            unset($params['limit'], $params['offset']);
            $count = Model\CollectBrand::count($params);
 
            $data['list'] = $brandList;
        }
        $paginator = new \Juice\Paginator(['type' => 'emptyArray', 'config' => [
            'count' => $count,
            'limit' => $limit,
            'page' => $page,
        ]]);
        $paginate = $paginator->getPaginate();
 
        $data['user'] = $user;
        $data['ctype'] = $ctype;
        $data['paginate'] = $paginate;
        $data['navName'] = '';
        $data['userSidebar'] = 'collect-list';
        echo $this->viewoutput('pc/collect-list', $data);
    }
    /**
     * 评论商品
     */
    public function commentAction()
    {
        global $db, $ecs;
        $orderid = intval($this->request->getQuery('order_id'));
        $goodsid = intval($this->request->getQuery('goods_id'));
        if (! $orderid || ! $goodsid) {
            return $this->response->redirect('user/bought/list');
        }
        $user = $this->auth->user();
        $goods = Model\OrderGoods::findFirst([
            'alias' => 'og',
            'columns' => "og.rec_id, og.goods_id, og.goods_name, og.goods_attr, og.goods_sn, og.goods_number, goods_price, g.goods_thumb, o.add_time, o.order_id",
            'conditions' =>  "og.order_id = $orderid AND og.goods_id = $goodsid AND o.user_id = " . $user['user_id'] . ' AND o.shipping_status = 2 AND c.comment_id IS NULL',
            'join' => [
                [
                    'type' => 'INNER',
                    'table' => 'order_info',
                    'alias' => 'o',
                    'on' => 'o.order_id = og.order_id'
                ],
                [
                    'type' => 'INNER',
                    'table' => 'goods',
                    'alias' => 'g',
                    'on' => 'g.goods_id = og.goods_id'
                ],
                [
                    'type' => 'LEFT',
                    'table' => 'comment',
                    'alias' => 'c',
                    'on' => 'c.order_id = og.order_id AND c.id_value = og.goods_id'
                ]
            ],
        ]);
        if (empty($goods)) {
            return $this->response->redirect('user/bought/list');
        }
        $goods['add_time_format'] = local_date('Y-m-d', $goods['add_time']);
 
        $data = [];
        $data['user'] = $user;
        $data['goods'] = $goods;
        $data['navName'] = '';
        $data['userSidebar'] = 'bought-list';
        echo $this->viewoutput('pc/comment', $data);
    }
 
    /**
     * 订单列表
     */
    public function orderListAction()
    {
        global $db, $ecs;
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'includes/lib_order.php');
 
        $data = [];
        $user = $this->auth->user();
        $mcart = new Model\Cart;
 
        $page = intval($this->request->getQuery('page')) ?: 1;
        $limit = 10;
        $offset = ($page - 1) * $limit;
 
        $status = $this->request->getQuery('status') ?: 0;
        $userid = $_SESSION['user_id'];
        $morder = new Model\OrderInfo;
 
        // 需要评论的order_id
        $result = $morder->needComment();
        $needCommentOrderids = [];
        foreach ($result as $row) {
            $needCommentOrderids[$row['order_id']] = $row['order_id'];
        }
 
        if (! empty($status)) {
            $orderAttr = Model\OrderInfo::$orderStatus[$status];
        }
        $ossql = $sssql = $pssql = '';
        if (! empty($orderAttr['os'])) {
            $ossql = ' AND ' . db_create_in($orderAttr['os'], 'o.order_status');
        }
        if (! empty($orderAttr['ss'])) {
            $sssql = ' AND ' . db_create_in($orderAttr['ss'], 'o.shipping_status');
        }
        if (! empty($orderAttr['ps'])) {
            $pssql = ' AND ' . db_create_in($orderAttr['ps'], 'o.pay_status');
        }
 
        $conditions = '1 = 1';
        if ($status == 4) {
            if (empty($needCommentOrderids)) {
                $conditions .= ' AND 1 = 2';
            } else {
                $conditions .= ' AND o.order_id IN (' . implode(',', $needCommentOrderids) . ')';
            }
        }
        $conditions .= " AND o.user_id = '$userid' $ossql $sssql $pssql ORDER BY o.add_time DESC LIMIT $offset, $limit";
        $colls = Model\OrderInfo::find([
            'alias' => 'o',
            'columns' => "o.order_id, o.order_sn, o.order_status, o.shipping_status, o.pay_status, o.add_time, " .
                         "(o.goods_amount + o.shipping_fee + o.insure_fee + o.pay_fee + o.pack_fee + o.card_fee + o.tax - o.discount) AS total_fee, " .
                         "do.delivery_id, do.delivery_sn"
            ,
            'conditions' => $conditions,
            'join' => [
                [
                    'type' => 'LEFT',
                    'table' => 'delivery_order',
                    'alias' => 'do',
                    'on' => 'o.order_id = do.order_id'
                ]
            ],
        ]);
 
        $orderids = [];
        $orders = [];
        foreach ($colls as $row) {
            $orderids[] = $row['order_id'];
            $row['shipping_status'] = ($row['shipping_status'] == SS_SHIPPED_ING) ? SS_PREPARING : $row['shipping_status'];
 
            $row['status'] = Model\OrderInfo::getOrderStatus($row);
            $orders[$row['order_id']] = [
                'order_id'       => $row['order_id'],
                'status' => $row['status'],
                'order_sn'       => $row['order_sn'],
                'order_time'     => local_date($GLOBALS['_CFG']['time_format'], $row['add_time']),
                'order_status'   => $row['order_status'],
                'shipping_status'   => $row['shipping_status'],
                'pay_status'   => $row['pay_status'],
                'total_fee'      => price_format($row['total_fee'], false),
                'delivery_id'       => $row['delivery_id'],
                'delivery_sn'       => $row['delivery_sn'],
                'needComment' => isset($needCommentOrderids[$row['order_id']]) ? true : false,
                'goods' => [],
            ];
        }
 
        $statistics = $morder->getAllCount();
        $count = empty($status) ? $statistics['total'] : $statistics['count'][$status];
        $paginator = new \Juice\Paginator(['type' => 'emptyArray', 'config' => [
            'count' => $count,
            'limit' => $limit,
            'page' => $page,
        ]]);
        $paginate = $paginator->getPaginate();
        $data['paginate'] = $paginate;
 
        if ($orderids) {
            $orderidsStr = implode(',', $orderids);
            $params = [
                'alias' => 'og',
                'columns' => "og.rec_id, og.order_id, og.goods_id, og.goods_name, og.goods_sn, og.market_price, og.goods_number, " .
                    "og.goods_price, og.goods_attr, og.is_real, og.parent_id, og.is_gift, " .
                    "og.goods_price * og.goods_number AS subtotal, og.extension_code, g.goods_thumb, g.goods_img " .
                    ',c.comment_id ',
                'conditions' =>  "og.order_id IN ($orderidsStr)",
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = og.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'comment',
                        'alias' => 'c',
                        'on' => 'c.id_value = og.goods_id AND c.order_id = og.order_id '
                    ],
                ],
            ];
 
            $colls = Model\OrderGoods::find($params);
 
            foreach ($colls as $row) {
                if (isset($orders[$row['order_id']])) {
                    $row['market_price'] = price_format($row['market_price'], false);
                    $row['goods_price']  = price_format($row['goods_price'], false);
                    $row['subtotal']     = price_format($row['subtotal'], false);
                    $orders[$row['order_id']]['goods'][] = $row;
                }
            }
        }
 
        $data['user'] = $user;
        $data['orders'] = $orders;
        $data['status'] = $status;
        $data['statistics'] = $statistics;
        $data['navName'] = '';
        $data['userSidebar'] = 'order-list';
        echo $this->viewoutput('pc/order-list', $data);
    }
 
    /**
     * 地址
     */
    public function addressListAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        $user = $this->auth->user();
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
 
        $addressList = Model\UserAddress::findByUserId($user['user_id']);
        $regionsids = [];
        foreach ($addressList as $region_id => $row) {
            $row['province'] = isset($row['province']) ? intval($row['province']) : 0;
            $row['city']     = isset($row['city'])     ? intval($row['city'])     : 0;
            $row['district']  = isset($row['district'])  ? intval($row['district'])  : 0;
 
            $regionsids[] = $row['district'];
            $regionsids[] = $row['province'];
            $regionsids[] = $row['city'];
        }
        $regions = [];
        if ($regionsids) {
            $idsstr = implode(', ', $regionsids);
            $sql = 'SELECT region_id, region_name FROM ' . $GLOBALS['ecs']->table('region') .
                    " WHERE region_id IN ($idsstr)";
            $res = $GLOBALS['db']->query($sql);
            while ($row = $GLOBALS['db']->fetchRow($res)) {
                $regions[$row['region_id']] = $row;
            }
        }
        foreach ($addressList as $key => &$row) {
            $row['province_name'] = isset($regions[$row['province']]) ? $regions[$row['province']]['region_name'] : '';
            $row['city_name']     = isset($regions[$row['city']])     ? $regions[$row['city']]['region_name']     : '';
            $row['district_name'] = isset($regions[$row['district']])  ? $regions[$row['district']]['region_name']  : '';
        }
        unset($row);
 
        $status = $this->request->getQuery('status');
        $_SESSION['origin_from'] = $status;
        $regions = Model\Region::getAll();
 
        $data = [];
        $data['user'] = $user;
        $data['addressList'] = $addressList;
        $data['regions'] = $regions;
        $data['navName'] = '';
        $data['userSidebar'] = 'address-list';
        echo $this->viewoutput('pc/address-list', $data);
    }
 
    /**
     * 代理信息
     */
    public function agentInfoAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
 
        $user = $this->auth->user();
        $data = [];
        $agent = Model\Agent::findFirstByAgentId($user['agent_id']);
        if (empty($agent)) {
            $data['isEdit'] = false;
        } else {
            $data['isEdit'] = true;
        }
 
        $data['user'] = $user;
        $data['agent'] = $agent;
        $data['navName'] = '';
        $data['userSidebar'] = 'agent';
        echo $this->viewoutput('pc/agent-info', $data);
    }
 
    /**
     * 代理订单信息
     */
    public function agentOrderListAction()
    {
        global $db, $ecs, $_CFG, $_LANG, $err;
        include_once(ROOT_PATH . 'includes/lib_transaction.php');
        include_once(ROOT_PATH . 'languages/' .$_CFG['lang']. '/shopping_flow.php');
 
        $user = $this->auth->user();
 
        $page = intval($this->request->getQuery('page')) ?: 1;
        $limit = 10;
        $offset = ($page - 1) * $limit;
 
        $ossql = $sssql = $pssql = '';
        if (! empty($orderAttr['os'])) {
            $ossql = ' AND ' . db_create_in($orderAttr['os'], 'order_status');
        }
        if (! empty($orderAttr['ss'])) {
            $sssql = ' AND ' . db_create_in($orderAttr['ss'], 'shipping_status');
        }
        if (! empty($orderAttr['ps'])) {
            $pssql = ' AND ' . db_create_in($orderAttr['ps'], 'pay_status');
        }
        $orderids = [];
        $arr = [];
        $count = 0;
        if ($user['agent_id']) {
            $params = [
                'alias' => 'o',
                'columns' => "o.order_id, o.order_sn, o.order_status, o.shipping_status, o.pay_status, o.add_time, " .
                   "(o.goods_amount + o.shipping_fee + o.insure_fee + o.pay_fee + o.pack_fee + o.card_fee + o.tax - o.discount) AS total_fee , u.user_name",
                'conditions' =>  "o.agent_id = '{$user['agent_id']}' $ossql $sssql $pssql",
                'offset' => $offset,
                'limit' => $limit,
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'users',
                        'alias' => 'u',
                        'on' => 'u.user_id = o.user_id'
                    ]
                ],
                'order' => 'add_time DESC',
            ];
            $colls = Model\OrderInfo::find($params);
 
            foreach ($colls as $row)
            {
                $orderids[] = $row['order_id'];
                $row['shipping_status'] = ($row['shipping_status'] == SS_SHIPPED_ING) ? SS_PREPARING : $row['shipping_status'];
 
                $row['status'] = Model\OrderInfo::getOrderStatus($row);
                $orderTime = local_date($GLOBALS['_CFG']['time_format'], $row['add_time']);
                $dateArr = explode(' ', $orderTime);
                $arr[$row['order_id']] = [
                    'order_id'       => $row['order_id'],
                    'user_name' => $row['user_name'],
                    'status' => $row['status'],
                    'order_sn'       => $row['order_sn'],
                    'order_time'     => $orderTime,
                    'order_time_date'     => $orderTime,
                    'order_time_arr'     => $dateArr,
                    'order_status'   => $row['order_status'],
                    'shipping_status'   => $row['shipping_status'],
                    'pay_status'   => $row['pay_status'],
                    'total_fee'      => price_format($row['total_fee'], false),
                    'goods' => [],
                ];
            }
            unset($params['limit'], $params['offset']);
            $count = Model\OrderInfo::count($params);
        }
 
        $paginator = new \Juice\Paginator(['type' => 'emptyArray', 'config' => [
            'count' => $count,
            'limit' => $limit,
            'page' => $page,
        ]]);
        $paginate = $paginator->getPaginate();
 
        $orders = $arr;
        if ($orderids) {
            $orderidsStr = implode(',', $orderids);
            $params = [
                'alias' => 'og',
                'columns' => "og.rec_id, og.order_id, og.goods_id, og.goods_name, og.goods_sn, og.market_price, og.goods_number, " .
                    "og.goods_price, og.goods_attr, og.is_real, og.parent_id, og.is_gift, " .
                    "og.goods_price * og.goods_number AS subtotal, og.extension_code, g.goods_thumb, g.goods_img " .
                    ',c.comment_id ',
                'conditions' =>  "og.order_id IN ($orderidsStr)",
                'join' => [
                    [
                        'type' => 'LEFT',
                        'table' => 'goods',
                        'alias' => 'g',
                        'on' => 'g.goods_id = og.goods_id'
                    ],
                    [
                        'type' => 'LEFT',
                        'table' => 'comment',
                        'alias' => 'c',
                        'on' => 'c.id_value = og.goods_id AND c.order_id = og.order_id '
                    ]
                ],
            ];
            $colls = Model\OrderGoods::find($params);
 
            foreach ($colls as $row) {
                if (isset($orders[$row['order_id']])) {
                    $row['market_price'] = price_format($row['market_price'], false);
                    $row['goods_price']  = price_format($row['goods_price'], false);
                    $row['subtotal']     = price_format($row['subtotal'], false);
                    $orders[$row['order_id']]['goods'][] = $row;
                }
            }
        }
 
        $data = [];
        $data['user'] = $user;
        $data['orders'] = $orders;
        $data['paginate'] = $paginate;
        $data['navName'] = '';
        $data['userSidebar'] = 'agent-order-list';
        echo $this->viewoutput('pc/agent-order-list', $data);
    }
 
    /**
     * 绑定微信号
     */
    public function wechatBindAction()
    {
        $user = $this->auth->user();
 
        $wechat = new WechatSDK;
        $url = 'http://www.liowang.com/shop/wechat/oauth2/callback/bind';
        $state = ['env' => 'product'];
        if (isLocal()) {
            $state = ['env' => 'local'];
        }
        return $this->response->redirect($wechat->pcWebOauth2($url, $state), true);
    }
    /**
     * PC 微信回调 oauth2 绑定已登录用户
     */
    public function wechatOauth2CallbackBindAction()
    {
        global $ecs, $db, $user;
        include_once(ROOT_PATH . 'includes/lib_passport.php');
        $code = $this->request->getQuery('code');
        if (empty($code)) {
            echo '授权失败';
            exit;
        }
        $state = $this->request->getQuery('state');
        parse_str($state, $params);
        if (! islocal() && ! empty($params['env']) && $params['env'] == 'local') {
            return $this->response->redirect('http://liowang.dev.seafarer.me/shop/wechat/oauth2/callback/bind?' . http_build_query($this->request->getQuery()));
        }
 
        $wechat = new WechatSDK;
        $config = $this->config->wechat;
        $config->appid = $config->open_web_appid;
        $config->appsecret = $config->open_web_secret;
        $wechat->init($config);
        $response = $wechat->getUserTokenByCode($code);
        if (empty($response['errcode'])) {
            // 授权成功
            $ewxpcuser = Model\WxPcUser::findFirstByOpenid($response['openid']);
 
            $data = [];
            $isFirst = false;
            $isNeedCreate = false;
            $uuser = [];
            $wxpcuser = [];
            if ($ewxpcuser) {
                // 用户已经存在
                $euser = Model\User::findFirstByWxPcUserid($ewxpcuser['id']);
            } else {
                // 第一次访问
                $isFirst = true;
                $wxuser = Model\WxUser::findFirstByUnionid($response['unionid']);
                if (empty($wxuser)) {
                    $isNeedCreate = true;
                } else {
                    $euser = Model\User::findFirstByWxUserid($wxuser['id']);
                }
            }
            $wxpcuser['openid'] = $response['openid'];
            $wxpcuser['unionid'] = $response['unionid'];
            $wxpcuser['access_token'] = $response['access_token'];
            $wxpcuser['refresh_token'] = $response['refresh_token'];
            $wxpcuser['expired_at'] = $response['expires_in'] + time() - 200;
            $wxpcuser['scope'] = $response['scope'];
            $wxpcuser['ip'] = $this->request->getClientAddress();
            $info = $wechat->getUserInfoByOauth($response['openid'], $response['access_token']);
            $wxpcuser['nickname'] = $info['nickname'];
            $wxpcuser['gender'] = $info['sex'];
            $wxpcuser['language'] = $info['language'];
            $wxpcuser['city'] = $info['city'];
            $wxpcuser['country'] = $info['country'];
            $wxpcuser['province'] = $info['province'];
            $wxpcuser['headimgurl'] = $info['headimgurl'];
 
            if (! empty($euser) && $euser['user_id'] != $_SESSION['user_id']) {
                exit('该微信已被绑定');
            }
 
            if ($isFirst) {
                // 首次PC扫码登录
                $id = Model\WxPcUser::insert($wxpcuser);
                $uuser['wx_pc_userid'] = $id;
                Model\User::update($uuser, "user_id = {$_SESSION['user_id']}");
 
            } else {
                $uuser['wx_pc_userid'] = $ewxpcuser['id'];
                Model\User::update($uuser, "user_id = {$_SESSION['user_id']}");
                Model\WxPcUser::update($wxpcuser, "openid = '{$response['openid']}'");
            }
        } else {
            SystemException::error($response['errmsg']);
        }
        $referer = $this->url->get('user/info');
        return $this->response->redirect($referer, true);
    }
 
}
#5PageUserController->loginAction()
#6Phalcon\Dispatcher->dispatch()
#7Phalcon\Mvc\Application->handle()
/usr/local/www/liowang-shop-robotwar/shop/public/index.php (36)
<?php
 
error_reporting(E_ALL);
 
try {
 
    define('BASE_PATH', realpath('..') . '/');
    define('APP_PATH', BASE_PATH . 'app/');
    define('STORAGE_PATH', BASE_PATH . 'storage/');
 
    // 系统环境变量,参考laravel模式
    $systemEnvs = [
        'local' => [
            'juice-pc'
        ],
        'production' => [
            '10-10-92-111'
        ],
        'testing' => [
            '10-10-13-185'
        ],
    ];
 
    /**
     * 项目引导文件
     */
    $config = include __DIR__ . "/../app/start/start.php";
 
    /**
     * Handle the request
     */
    $application = new \Phalcon\Mvc\Application($di);
 
    $application->useImplicitView(false); // 禁用自动渲染
 
    echo $application->handle()->getContent();
 
} catch (\Exception $e) {
    //echo $e;
    throw $e;
Phalcon Framework 2.0.13
莉旺时尚
PHP{2025-05-04 19:40:05|| file:/usr/local/www/liowang-shop-robotwar/shop/app/libraries/PrettyExceptions/Library/Phalcon/Utils/PrettyExceptions.php line:440 msg:Cannot modify header information - headers already sent by (output started at /usr/local/www/liowang-shop-robotwar/shop/app/libraries/PrettyExceptions/Library/Phalcon/Utils/PrettyExceptions.php:339)||/shop/login}