php7实现微信支付、公众号相关接口代码

时间:2020-05-01

微信支付、公众号本身提供了sdk的,这里的代码仅用于测试,给大家一个参考。

基于Ci框架写的!

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * 微信支付类
 * @author wyzda.com
 */
class Wxpay
{
    // ci对象
    protected $CI;
    // 第三方用户唯一凭证
    private $app_id = '';
    // 第三方用户唯一凭证密钥
    private $app_secret = '';
    // 支付秘钥
    private $key = '';
    // 商户ID
    private $mch_id = '';
    // 证书位置
    private $cert_path = '';
    // 证书秘钥位置
    private $key_path = '';
    // CA证书位置
    private $ca_path = '';
    // 微信接口链接
    private $mch_url = '';
    // 本类对象
    protected static $obj;

    /**
     * Wxpay 构造方法
     */
    public function __construct()
    {
        $this->CI =& get_instance();
        //加载配置
        $this->CI->config->load('wechat');
        //加载微信配置
        $this->app_id = $this->CI->config->item('app_id', 'wechat');
        $this->app_secret = $this->CI->config->item('app_secret', 'wechat');
        $this->key = $this->CI->config->item('key', 'wechat');
        $this->mch_id = $this->CI->config->item('mch_id', 'wechat');
        $this->cert_path = $this->CI->config->item('cert_path', 'wechat');
        $this->key_path = $this->CI->config->item('key_path', 'wechat');
        $this->ca_path = $this->CI->config->item('ca_path', 'wechat');
        $this->mch_url = $this->CI->config->item('mch_url', 'wechat');
    }

    /**
     * 生成随机数
     * @return string
     * @author wyzda.com
     */
    private function get_nonce_str()
    {
        return md5(time() . uniqid());
    }

    /**
     * 获取签名
     * @param array $data 1维数组格式的数据
     * @return string
     * @author wyzda.com
     */
    private function get_sign($data)
    {
        // 排序
        ksort($data);
        // 拼接
        $str = '';
        foreach ($data as $k => $v) {
            if (empty($v) && ($v != 0)) {
                continue;
            }
            if ($k === 'sign') {
                continue;
            }
            if (empty($str)) {
                $str = $k . '=' . $v;
            } else {
                $str .= '&' . $k . '=' . $v;
            }
        }

        // 拼接API密钥
        $str .= '&key=' . $this->key;

        if (!isset($data['sign_type']) || $data['sign_type'] === 'MD5') {
            return strtoupper(md5($str));
        }
        return strtoupper(hash_hmac('sha256', $str, $this->key));
    }

    /**
     * 微信支付下单,参数详情请参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
     * @param string $openid 用户标识
     * @param string $body 商品描述
     * @param string $out_trade_no 商户订单号
     * @param int $total_fee 订单总金额,单位为分
     * @param string $notify_url 通知地址
     * @param array $extra_data 额外下单的数据,请添加到此数组中去,此数组数据会覆盖已设置的数据
     * @return array
     * @author wyzda.com
     */
    public function pay($openid, $body, $out_trade_no, $total_fee, $notify_url, $extra_data = [])
    {
        // 支付请求的数据
        $data = [
            'body' => $body,
            'out_trade_no' => $out_trade_no,
            'total_fee' => $total_fee,
            'notify_url' => $notify_url,
            'appid' => $this->app_id,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->get_nonce_str(),
            'spbill_create_ip' => $this->CI->input->ip_address(),
            'trade_type' => 'JSAPI',
            'sign_type' => 'MD5',
            'openid' => $openid
        ];
        // 添加额外的请求数据
        if (!empty($extra_data)) {
            foreach ($extra_data as $k => $v) {
                $data[$k] = $v;
            }
        }

        // 签名
        $sign = $this->get_sign($data);
        $data['sign'] = $sign;
        // 转成xml
        $xml = $this->array_to_xml($data);
        $res = $this->request($this->mch_url . '/pay/unifiedorder', $xml);
        return $this->xml_to_array($res);
    }

    /**
     * 请求url
     * @param string $url 请求网址
     * @param array|string $data 请求数据,针对post请求
     * @param bool $https 是否使用https连接
     * @return bool|string
     * @author wyzda.com
     */
    private function request($url, $data = [], $https = false)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, 3);
        if (!empty($data)) {
            if (is_array($data)) {
                foreach ($data as $k2 => $v) {
                    if (preg_match('/^@/i', $v)) {
                        // 文件
                        $v = '@' . realpath(ltrim($v, '@'));
                        if (version_compare(PHP_VERSION, '5.5.0') >= 0) {
                            // php 版本 > 5.5,使用CURLFile传文件
                            $cfile = new CURLFile(ltrim($v, '@'));
                            $data[$k2] = $cfile;
                        } else {
                            $data[$k2] = $v;
                        }
                    }
                }
            }
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        }
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        if ($https) {
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLCERT, $this->cert_path);
            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLKEY, $this->key_path);
        }
        $res = curl_exec($ch);
        curl_close($ch);
        return $res;
    }

    /**
     * 数组转xml
     * @param array $data 一维数组格式数据
     * @return string
     * @author wyzda.com
     */
    public function array_to_xml($data)
    {
        $xml = '<xml>';
        foreach ($data as $k => $v) {
            $xml .= '<' . $k . '><![CDATA[' . $v . ']]></' . $k . '>';
        }
        $xml .= '</xml>';
        return $xml;
    }

    /**
     * 通过静态方法获取微信操作实例
     * @return Wxpay
     * @author wyzda.com
     */
    public static function get_instance()
    {
        if (is_null(self::$obj)) {
            self::$obj = new self();
        }
        return self::$obj;
    }

    /**
     * 获取微信支付推送的消息
     * @return false|string
     * @author wyzda.com
     */
    public function get_push_msg()
    {
        return file_get_contents('php://input');
    }

    /**
     * 将微信支付推送过来的消息转为数组
     * @param string $xml
     * @return array
     * @author wyzda.com
     */
    public function xml_to_array($xml = '')
    {
        if (empty($xml)) {
            $xml = $this->get_push_msg();
        }
        try {
            $xml = preg_replace('/<!\[CDATA\[(.*)\]\]>/isU', '$1', $xml);
            return json_decode(json_encode(simplexml_load_string($xml)), true);
        } catch (\Exception $e) {
            return [];
        }
    }

    /**
     * 微信订单查询,商户订单号与微信订单号二选一,参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2
     * @param string $out_trade_no 商户订单号
     * @param string $transaction_id 微信订单号
     * @return array
     * @author wyzda.com
     */
    public function query_order($out_trade_no, $transaction_id = '')
    {
        $data = [
            'appid' => $this->app_id,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->get_nonce_str(),
            'sign_type' => 'MD5'
        ];
        if (!empty($out_trade_no)) {
            $data['out_trade_no'] = $out_trade_no;
        }

        if (!empty($transaction_id)) {
            $data['transaction_id'] = $transaction_id;
        }

        // 签名
        $sign = $this->get_sign($data);
        $data['sign'] = $sign;
        // 转成xml
        $xml = $this->array_to_xml($data);
        $res = $this->request($this->mch_url . '/pay/orderquery', $xml);
        return $this->xml_to_array($res);
    }

    /**
     * 关闭订单,参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3
     * @param string $out_trade_no 商户订单号
     * @return array
     * @author wyzda.com
     */
    public function close_order($out_trade_no)
    {
        $data = [
            'appid' => $this->app_id,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->get_nonce_str(),
            'sign_type' => 'MD5',
            'out_trade_no' => $out_trade_no
        ];

        // 签名
        $sign = $this->get_sign($data);
        $data['sign'] = $sign;
        // 转成xml
        $xml = $this->array_to_xml($data);
        $res = $this->request($this->mch_url . '/pay/closeorder', $xml);
        return $this->xml_to_array($res);
    }

    /**
     * 申请退款,商户订单号与微信订单号二选一,参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
     * @param string $out_trade_no 商户订单号
     * @param string $out_refund_no 商户退款单号
     * @param int $total_fee 订单总金额,单位为分
     * @param int $refund_fee 退款总金额,单位为分
     * @param string $transaction_id 微信订单号
     * @param array $extra_data 额外退款的数据,请添加到此数组中去,此数组数据会覆盖已设置的数据
     * @return array
     * @author wyzda.com
     */
    public function refund($out_trade_no, $out_refund_no, $total_fee, $refund_fee, $transaction_id = '', $extra_data = [])
    {
        $data = [
            'appid' => $this->app_id,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->get_nonce_str(),
            'sign_type' => 'MD5',
            'out_refund_no' => $out_refund_no,
            'total_fee' => $total_fee,
            'refund_fee' => $refund_fee,
            'transaction_id' => $transaction_id,
        ];
        if (!empty($out_trade_no)) {
            $data['out_trade_no'] = $out_trade_no;
        }
        if (!empty($transaction_id)) {
            $data['transaction_id'] = $transaction_id;
        }
        // 添加额外的请求数据
        if (!empty($extra_data)) {
            foreach ($extra_data as $k => $v) {
                $data[$k] = $v;
            }
        }
        // 签名
        $sign = $this->get_sign($data);
        $data['sign'] = $sign;
        // 转成xml
        $xml = $this->array_to_xml($data);
        $res = $this->request($this->mch_url . '/secapi/pay/refund', $xml, true);
        return $this->xml_to_array($res);
    }

    /**
     * 查询退款,微信订单号查询的优先级是: refund_id > out_refund_no > transaction_id > out_trade_no,四个中选一个,参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5
     * @param string $out_refund_no 商户退款单号
     * @param string $out_trade_no 商户订单号
     * @param string $refund_id 微信退款单号
     * @param string $transaction_id 微信订单号
     * @param int $offset 偏移量
     * @return array
     * @author wyzda.com
     */
    public function query_refund($out_refund_no, $out_trade_no = '', $refund_id = '', $transaction_id = '', $offset = 0)
    {
        $data = [
            'appid' => $this->app_id,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->get_nonce_str(),
            'sign_type' => 'MD5'
        ];
        if (!empty($out_refund_no)) {
            $data['out_refund_no'] = $out_refund_no;
        }
        if (!empty($out_trade_no)) {
            $data['out_trade_no'] = $out_trade_no;
        }
        if (!empty($refund_id)) {
            $data['refund_id'] = $refund_id;
        }
        if (!empty($transaction_id)) {
            $data['transaction_id'] = $transaction_id;
        }
        if ($offset > 0) {
            $data['offset'] = $offset;
        }

        // 签名
        $sign = $this->get_sign($data);
        $data['sign'] = $sign;
        // 转成xml
        $xml = $this->array_to_xml($data);
        $res = $this->request($this->mch_url . '/pay/refundquery', $xml);
        return $this->xml_to_array($res);
    }

    /**
     * 获取对账单
     * @param string $bill_date 下载对账单的日期,格式:20140603
     * @param string $bill_type 账单类型,ALL(默认值),返回当日所有订单信息(不含充值退款订单),SUCCESS,返回当日成功支付的订单(不含充值退款订单),REFUND,返回当日退款订单(不含充值退款订单),RECHARGE_REFUND,返回当日充值退款订单
     * @param bool $tar_type 压缩账单
     * @return array
     * @author wyzda.com
     */
    public function get_downloadbill($bill_date, $bill_type = 'ALL', $tar_type = false)
    {
        $data = [
            'appid' => $this->app_id,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->get_nonce_str(),
            'sign_type' => 'MD5',
            'bill_date' => $bill_date,
            'bill_type' => $bill_type
        ];
        if ($tar_type) {
            $data['tar_type'] = 'GZIP';
        }

        // 签名
        $sign = $this->get_sign($data);
        $data['sign'] = $sign;
        // 转成xml
        $xml = $this->array_to_xml($data);
        $res = $this->request($this->mch_url . '/pay/downloadbill', $xml);
        if (!preg_match('/^<xml>/i', $res)) {
            if ($tar_type === true) {
                // GZIP压缩流解析
                $res = gzdecode($res);
            }
            // 数据流
            $res_arr = explode(PHP_EOL, $res);
            $data = [];
            $pre_len = 0;
            $i = -1;
            foreach ($res_arr as $v) {
                if (empty($v)) {
                    continue;
                }
                $v_arr = explode(',', $v);
                $len = count($v_arr);
                if ($len != $pre_len) {
                    $i++;
                    $pre_len = $len;
                }
                $data[$i][] = $v_arr;
            }
            return $data;
        }
        return $this->xml_to_array($res);
    }

    /**
     * 获取资金账单
     * @param string $bill_date 下载对账单的日期,格式:20140603
     * @param string $account_type 账单的资金来源账户:Basic  基本账户,Operation 运营账户,Fees 手续费账户
     * @param bool $tar_type 压缩账单
     * @return array
     * @author wyzda.com
     */
    public function get_downloadfundflow($bill_date, $account_type = 'Basic', $tar_type = false)
    {
        $data = [
            'appid' => $this->app_id,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->get_nonce_str(),
            'sign_type' => 'HMAC-SHA256',
            'bill_date' => $bill_date,
            'account_type' => $account_type
        ];
        if ($tar_type) {
            $data['tar_type'] = 'GZIP';
        }

        // 签名
        $sign = $this->get_sign($data);
        $data['sign'] = $sign;
        // 转成xml
        $xml = $this->array_to_xml($data);
        $res = $this->request($this->mch_url . '/pay/downloadfundflow', $xml, true);
        if (!preg_match('/^<xml>/i', $res)) {
            if ($tar_type === true) {
                // GZIP压缩流解析
                $res = gzdecode($res);
            }
            // 数据流
            $res_arr = explode(PHP_EOL, $res);
            $data = [];
            $pre_len = 0;
            $i = -1;
            foreach ($res_arr as $v) {
                if (empty($v)) {
                    continue;
                }
                $v_arr = explode(',', $v);
                $len = count($v_arr);
                if ($len != $pre_len) {
                    $i++;
                    $pre_len = $len;
                }
                $data[$i][] = $v_arr;
            }
            return $data;
        }
        return $this->xml_to_array($res);
    }

    /**
     * 获取支付结果通知,参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
     * @return array
     * @author wyzda.com
     */
    public function notify_pay_result()
    {
        return $this->xml_to_array($this->get_push_msg());
    }

    /**
     * 确定收到微信支付结果/退款结果
     * @return string
     * @author wyzda.com
     */
    public function return_success()
    {
        $xml = /** @lang text */
            <<<XML
        <xml>
          <return_code><![CDATA[SUCCESS]]></return_code>
          <return_msg><![CDATA[OK]]></return_msg>
        </xml>
XML;
        return $xml;
    }

    /**
     * 微信退款通知结果
     * @return array
     * @author wyzda.com
     */
    public function notify_refund_result()
    {
        $res = $this->xml_to_array($this->get_push_msg());
        //对req_info字段值解密
        if (isset($res['req_info'])) {
            $res['req_info'] = $this->decrypt($res['req_info']);
        }
        return $res;
    }

    /**
     * 解密微信支付退款数据
     * @param string $str 待解密字符串
     * @return array
     * @author wyzda.com
     */
    public function decrypt($str)
    {
        $str = base64_decode($str);
        $str = openssl_decrypt($str, 'aes-256-ecb', md5($this->key), OPENSSL_RAW_DATA);
        return $this->xml_to_array($str);
    }

    /**
     * 拉取订单评价数据
     * @param string $begin_time 按用户评论时间批量拉取的起始时间,格式为yyyyMMddHHmmss
     * @param string $end_time 按用户评论时间批量拉取的结束时间,格式为yyyyMMddHHmmss
     * @param int $offset 位移
     * @param int $limit 条数
     * @return array|string
     * @author wyzda.com
     */
    public function batchquerycomment($begin_time, $end_time, $offset = 0, $limit = 100)
    {
        $data = [
            'appid' => $this->app_id,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->get_nonce_str(),
            'sign_type' => 'HMAC-SHA256',
            'begin_time' => $begin_time,
            'end_time' => $end_time,
            'offset' => $offset,
            'limit' => $limit
        ];
        // 签名
        $sign = $this->get_sign($data);
        $data['sign'] = $sign;
        // 转成xml
        $xml = $this->array_to_xml($data);
        $res = $this->request($this->mch_url . '/billcommentsp/batchquerycomment', $xml, true);
        if (preg_match('/^<xml>/i', $res)) {
            return $this->xml_to_array($res);
        }
        return $res;
    }

    /**
     * 发起一个微信支付请求的所需参数
     * @param string $prepay_id 通过微信支付统一下单接口拿到
     * @param string $sign_type 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
     * @return array
     * @author wyzda.com
     */
    public function get_jssdk_pay($prepay_id, $sign_type = 'MD5')
    {
        $data = [
            'appId' => $this->app_id,
            'timeStamp' => time(),
            'nonceStr' => $this->get_nonce_str(),
            'package' => 'prepay_id=' . $prepay_id,
            'signType' => $sign_type
        ];
        $data['paySign'] = $this->get_sign($data);
        return $data;
    }
}
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * 微信公众号类
 * @author wyzda.com
 */
class Weixin
{
    // 自定义token
    private $token = '';
    // 第三方用户唯一凭证
    private $app_id = '';
    // 第三方用户唯一凭证密钥
    private $app_secret = '';
    // 微信接口域名
    private $api_url = '';
    private $open_url = '';
    // token存放key
    private $token_key = '';
    // 微信公众号access_token
    private $access_token = '';
    private $ticket = '';
    // 微信公众号推送消息
    private $we_chat_msg = [];
    // ci对象
    protected $CI;
    // 本类对象
    protected static $obj;

    public function __construct()
    {
        $this->CI =& get_instance();
        //加载配置
        $this->CI->config->load('wechat');
        //加载微信配置
        $this->token = $this->CI->config->item('token', 'wechat');
        $this->app_id = $this->CI->config->item('app_id', 'wechat');
        $this->app_secret = $this->CI->config->item('app_secret', 'wechat');
        $this->api_url = $this->CI->config->item('api_url', 'wechat');
        $this->token_key = md5($this->app_id . $this->app_secret);
        $this->open_url = $this->CI->config->item('open_url', 'wechat');
        //加载缓存
        $this->CI->load->driver('cache');
    }

    /**
     * 通过静态方法获取微信操作实例
     * @return Weixin
     * @author wyzda.com
     */
    public static function get_instance()
    {
        if (is_null(self::$obj)) {
            self::$obj = new self();
        }
        return self::$obj;
    }

    /**
     * 接口配置
     * @author wyzda.com
     */
    public function echo_str()
    {
        if ($this->check_signature()) {
            echo $this->CI->input->get('echostr');
        } else {
            echo '';
        }
        exit();
    }

    /**
     * 验证微信签名
     * @return bool
     * @author wyzda.com
     */
    private function check_signature()
    {
        $signature = $this->CI->input->get('signature') ?: '';
        $timestamp = $this->CI->input->get('timestamp') ?: '';
        $nonce = $this->CI->input->get('nonce') ?: '';
        $tmpArr = [$timestamp, $nonce, $this->token];
        sort($tmpArr, SORT_STRING);
        $tmpStr = sha1(implode($tmpArr));
        if ($tmpStr === $signature) {
            return true;
        }
        return false;
    }

    /**
     * 获取access_token
     * @return string
     * @author wyzda.com
     */
    public function get_access_token()
    {
        if (!empty($this->access_token)) {
            return $this->access_token;
        }
        $token = $this->get_cache($this->token_key);
        if (!empty($token)) {
            $this->access_token = $token;
        } else {
            $token = $this->reacquire_access_token();
        }
        return $token;
    }

    /**
     * 实时获取微信access_token
     * @return string
     * @author wyzda.com
     */
    public function reacquire_access_token()
    {
        $res = $this->request($this->api_url . '/cgi-bin/token?grant_type=client_credential&appid=' . $this->app_id . '&secret=' . $this->app_secret);
        if (!empty($res)) {
            $res_arr = json_decode($res, true);
            if (!isset($res_arr['errcode'])) {
                $token = $res_arr['access_token'];
                $this->access_token = $token;
                //存储token
                $this->set_cache($this->token_key, $token, time() - 300 + $res_arr['expires_in']);
                return $token;
            }
        }
        return '';
    }

    /**
     * 请求url
     * @param string $url 请求网址
     * @param array|string $data 请求数据,针对post请求
     * @return bool|string
     * @author wyzda.com
     */
    public function request($url, $data = [])
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, 3);
        if (!empty($data)) {
            if (is_array($data)) {
                foreach ($data as $k2 => $v) {
                    if (preg_match('/^@/i', $v)) {
                        // 文件
                        $v = '@' . realpath(ltrim($v, '@'));
                        if (version_compare(PHP_VERSION, '5.5.0') >= 0) {
                            // php 版本 > 5.5,使用CURLFile传文件
                            $cfile = new CURLFile(ltrim($v, '@'));
                            $data[$k2] = $cfile;
                        } else {
                            $data[$k2] = $v;
                        }
                    }
                }
            }
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        }
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        $res = curl_exec($ch);
        curl_close($ch);
        return $res;
    }

    /**
     * 请求url
     * @param string $url 请求网址
     * @param array|string $data 请求数据,针对post请求
     * @return array
     * @author wyzda.com
     */
    public function request_array($url, $data = [])
    {
        $res = $this->request($url, $data);
        return json_decode($res, true);
    }

    /**
     * 获取微信推送的消息
     * @return false|string
     * @author wyzda.com
     */
    public function get_push_msg()
    {
        return file_get_contents('php://input');
    }

    /**
     * 获取微信推送的消息,根据标签名获取数据
     * @param string $key 微信返回消息的标签名
     * @return mixed|string
     * @author wyzda.com
     */
    public function get_string_value($key)
    {
        $msg = $this->get_push_msg();
        if (preg_match('/<' . $key . '><\!\[CDATA\[([^\]]+)\]\]><\/' . $key . '>/iU', $msg, $mat)) {
            return $mat[1];
        }
        return '';
    }

    /**
     * 获取微信推送的消息,根据标签名获取数据
     * @param string $key 微信返回消息的标签名
     * @return mixed|string
     * @author wyzda.com
     */
    public function get_int_value($key)
    {
        $msg = $this->get_push_msg();
        if (preg_match('/<' . $key . '>([\d]+)<\/' . $key . '>/iU', $msg, $mat)) {
            return $mat[1];
        }
        return 0;
    }

    /**
     * 将微信推送过来的消息转为数组
     * @param string $xml
     * @return array
     * @author wyzda.com
     */
    public function xml_to_array($xml = '')
    {
        if (empty($xml)) {
            $xml = $this->get_push_msg();
        }
        try {
            $xml = preg_replace('/<!\[CDATA\[(.*)\]\]>/isU', '$1', $xml);
            return json_decode(json_encode(simplexml_load_string($xml)), true);
        } catch (\Exception $e) {
            return [];
        }
    }

    /**
     * 获取微信推送过来的信息,格式为数组
     * @return array
     * @author wyzda.com
     */
    public function get_wechat_msg()
    {
        if (empty($this->we_chat_msg)) {
            $this->we_chat_msg = $this->xml_to_array();
        }
        return $this->we_chat_msg;
    }

    /**
     * 发送微信模板消息
     * @param string $touser 接收者openid
     * @param string $templateId 模板ID
     * @param array $data 模板数据
     * @param string $url 模板跳转链接(海外帐号没有跳转能力)
     * @param string $miniprogram_app_id 所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏)
     * @param string $miniprogram_pagepath 所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),要求该小程序已发布,暂不支持小游戏
     * @return bool
     * @author wyzda.com
     */
    public function send_template_msg($touser, $templateId, $data, $url = '', $miniprogram_app_id = '', $miniprogram_pagepath = '')
    {
        $templateData = [
            'touser' => $touser,
            'template_id' => $templateId,
            'data' => $data
        ];
        if (!empty($url)) {
            $templateData['url'] = $url;
        }
        if (!empty($miniprogram_app_id)) {
            $templateData['miniprogram']['appid'] = $miniprogram_app_id;
        }
        if (!empty($miniprogram_pagepath)) {
            $templateData['miniprogram']['pagepath'] = $miniprogram_pagepath;
        }
        $res = $this->request_array($this->api_url . '/cgi-bin/message/template/send?access_token=' . $this->get_access_token(), json_encode($templateData));
        return $res['errcode'] === 0;
    }


    /**
     * 获取用户基本信息
     * @param string $open_id 普通用户的标识,对当前公众号唯一
     * @return array
     * @author wyzda.com
     */
    public function get_user_info($open_id)
    {
        return $this->request_array($this->api_url . '/cgi-bin/user/info?access_token=' . $this->get_access_token() . '&openid=' . $open_id . '&lang=zh_CN');
    }

    /**
     * 获取微信授权链接
     * @param string $uri 授权后回调链接
     * @param string $state 自定义参数
     * @param string $scope snsapi_base|snsapi_userinfo
     * @return string
     * @author wyzda.com
     */
    public function get_redirect_uri($uri, $state = '', $scope = 'snsapi_userinfo')
    {
        $uri = urlencode($uri);
        return $this->open_url . '/connect/oauth2/authorize?appid=' . $this->app_id . '&redirect_uri=' . $uri . '&response_type=code&scope=' . $scope . '&state=' . $state . '#wechat_redirect';
    }

    /**
     * 通过code换取网页授权access_token
     * @param string $code code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
     * @return array
     * @author wyzda.com
     */
    public function get_oauth2_token($code)
    {
        return $this->request_array($this->api_url . '/sns/oauth2/access_token?appid=' . $this->app_id . '&secret=' . $this->app_secret . '&code=' . $code . '&grant_type=authorization_code');
    }

    /**
     * 设置缓存
     * @param string $key 变量名
     * @param mixed $value 变量值
     * @param int $ttl 过期时间,单位秒
     * @author wyzda.com
     */
    public function set_cache($key, $value, $ttl = 3600)
    {
        $this->CI->cache->file->save($key, $value, $ttl);
    }

    /**
     * 获取缓存
     * @param string $key 变量名
     * @return mixed
     * @author wyzda.com
     */
    public function get_cache($key)
    {
        return $this->CI->cache->file->get($key);
    }

    /**
     * 刷新access_token(如果需要)
     * @param string $refresh_token 填写通过access_token获取到的refresh_token参数
     * @return array
     * @author wyzda.com
     */
    public function refresh_oauth2_token($refresh_token)
    {
        return $this->request_array($this->api_url . '/sns/oauth2/refresh_token?appid=' . $this->app_id . '&grant_type=refresh_token&refresh_token=' . $refresh_token);
    }

    /**
     * 拉取用户信息(需scope为 snsapi_userinfo)
     * @param string $open_id 用户的唯一标识
     * @param string $access_token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     * @return array
     * @author wyzda.com
     */
    public function get_oauth2_userinfo($open_id, $access_token)
    {
        return $this->request_array($this->api_url . '/sns/userinfo?access_token=' . $access_token . '&openid=' . $open_id . '&lang=zh_CN');
    }

    /**
     * 获取jsapi_ticket
     * @return mixed|string
     * @author wyzda.com
     */
    public function get_ticket()
    {
        if (!empty($this->ticket)) {
            return $this->ticket;
        }
        $ticket = $this->get_cache('ticket');
        if (!empty($ticket)) {
            $this->ticket = $ticket;
        } else {
            $this->ticket = $this->reacquire_get_ticket();
        }
        return $this->ticket;
    }

    /**
     * 实时获取jsapi_ticket
     * @return mixed|string
     * @author wyzda.com
     */
    public function reacquire_get_ticket()
    {
        $res = $this->request_array($this->api_url . '/cgi-bin/ticket/getticket?access_token=' . $this->get_access_token() . '&type=jsapi');
        if (isset($res['ticket'])) {
            $this->ticket = $res['ticket'];
            $this->set_cache('ticket', $this->ticket, $res['expires_in'] - 300);
            return $this->ticket;
        }
        return '';
    }

    /**
     * 获取签名
     * @param array $data 1维数组格式的数据
     * @return string
     * @author wyzda.com
     */
    private function get_sign($data)
    {
        // 排序
        ksort($data);
        // 拼接
        $str = '';
        foreach ($data as $k => $v) {
            if (empty($str)) {
                $str = $k . '=' . $v;
            } else {
                $str .= '&' . $k . '=' . $v;
            }
        }
        return sha1($str);
    }

    /**
     * 生成随机数
     * @return string
     * @author wyzda.com
     */
    private function get_nonce_str()
    {
        return md5(time() . uniqid());
    }

    /**
     * 获取JS-SDK的配置信息
     * @param string $url 当前网页的URL,不包含#及其后面部分
     * @return array
     * @author wyzda.com
     */
    public function get_jssdk_config($url)
    {
        $data = [
            'noncestr' => $this->get_nonce_str(),
            'jsapi_ticket' => $this->get_ticket(),
            'timestamp' => time(),
            'url' => $url
        ];
        $data['signature'] = $this->get_sign($data);
        $data['app_id'] = $this->app_id;
        return $data;
    }
}