'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=xydc#wechat_redirect', // 第三方 'qrConnect' => 'https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=SCOPE&state=STATE', // 永久ACCESS_TOKEN 'accessToken' => 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s', // 临时ACCESS_TOKEN 'tempToken' => 'https://api.weixin.qq.com/sns/oauth2/access_token?code=%s&appid=%s&secret=%s&grant_type=authorization_code', // 清除接口限制 'clearTokenQuota' => 'https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=%s', // 微信用户信息 'wxInfo' => 'https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN', // 获取userInfo 'userInfo' => 'https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN', // 统一下单 'unifiedorder' => 'https://api.mch.weixin.qq.com/pay/unifiedorder', // 原路退款接口 'refundOrder' => 'https://api.mch.weixin.qq.com/pay/unifiedorder', // 查询订单 'queryOrder' => 'https://api.mch.weixin.qq.com/pay/orderquery', // 企业付款到零钱 'transfers' => 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers', // 查询企业付款订单 'queryTransfer' => 'https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo', // 生成二维码 'makeQrcode' => 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s', // 换取二维码 'getQrcodeByTicket' => 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s', // 创建公众号菜单 'createMenu' => 'https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s', // 获取公众号菜单 'getMenu' => 'https://api.weixin.qq.com/cgi-bin/menu/get?access_token=%s', // 删除公众号菜单 'delMenu' => 'https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=%s', // 发送客服消息 'customMessage' => 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s', // 发送模板消息 'tplMessage' => 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s', // 获取消息模板列表 'templateList' => 'https://api.weixin.qq.com/cgi-bin/template/get_all_private_template?access_token=%s', // 添加媒体素材 'uploadMedia' => 'https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=%s&type=%s', // 生成短连接 'shortUrl' => 'https://api.weixin.qq.com/cgi-bin/shorturl?access_token=%s', // 清除接口限制 'clearTokenQuota'=> 'https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=%s', ]; private static $jsApiUrl = [ // jssdk 验证参数 'ticket' => 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=%s', // 永久TOKENresponseText 'token' => 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s', ]; // 支付证书 protected static $certPaths = [ 'cert_path'=> CMF_ROOT.'data/cert/apiclient_cert.pem', 'key_path'=> CMF_ROOT.'data/cert/apiclient_key.pem', ]; /** * 授权地址校验 */ public static function valid() { echo input('echostr'); exit; } /** * 获取授权后信息 */ public static function init() { $code = input('code', ''); // 获取token if ($code) { Wechat::wxInit(); } else { $url = urlencode(request()->domain() . request()->url()); Wechat::redirectUrl($url); } } /** * 授权认证 * @throws \think\Exception * @throws \think\exception\PDOException */ public static function auth() { $code = input('code', ''); // 获取token if ($code) { Wechat::wxAuth(); } else { $url = urlencode(request()->domain() . request()->url()); Wechat::redirectUrl($url); } } /** * 跳转授权 * @param string $url 回跳地址 * @return mixed */ public static function redirectUrl($url) { ob_clean(); $appid = Wechat::getConfigs('appid'); $redirectUrl = sprintf(self::$apiUrl['auth'], $appid, $url, 'snsapi_userinfo'); header("Location:" . $redirectUrl); exit; } /** * 跳转授权 * @param string $url 回跳地址 * @return mixed */ public static function makeRedirectUrl($url) { $appid = Wechat::getConfigs('appid'); return sprintf(self::$apiUrl['auth'], $appid, $url, 'snsapi_userinfo'); } /** * 获取配置 * @param string $key 键名 * @return array|mixed|string */ public static function getConfigs($key = '') { $defConfig = config('weixin.'); $config = cmf_get_option('wechat'); $config = $config ? $config : $defConfig; $config['notify'] = isset($defConfig['notify']) ? $defConfig['notify'] : []; if ($key) { return isset($config[$key]) ? $config[$key] : ''; } else { return $config ? $config : []; } } /** * 授权获取微信信息用于绑定 * @throws \think\Exception * @throws \think\exception\PDOException */ private static function wxAuth() { $wxInfo = Wechat::getWxInfo('', true); $openid = isset($wxInfo['openid']) ? $wxInfo['openid'] : ''; if (empty($wxInfo) || empty($openid)) { // 重新获取授权 Wechat::auth(); } // 验证微信信息是否存在 $cacheKey = 'updateWeixinInfo:' . $openid; $updateCheck = cache($cacheKey); $wxData = [ 'openid' => $openid, 'nickname' => isset($wxInfo['nickname']) ? $wxInfo['nickname'] : '', 'headimgurl' => isset($wxInfo['headimgurl']) ? $wxInfo['headimgurl'] : '', 'sex' => isset($wxInfo['sex']) ? $wxInfo['sex'] : 0, 'country' => isset($wxInfo['country']) ? $wxInfo['country'] : '', 'province' => isset($wxInfo['province']) ? $wxInfo['province'] : '', 'city' => isset($wxInfo['city']) ? $wxInfo['city'] : '', ]; if (empty(db('fans')->where(['openid' => $openid])->value('id'))) { $wxData['created_at'] = time(); $resId = db('fans')->insertGetId($wxData); // 每3个小时才更新一次微信信息 $expire = config('weixin.update_expire'); $expire = $expire ? $expire : 3 * 3600; cache($cacheKey, date('Y-m-d H:i:s'), ['expire' => $expire]); } else if (empty($updateCheck)) { // 更新微信用户信息 db('fans')->where(['openid' => $openid])->update($wxData); // 每3个小时才更新一次微信信息 $expire = config('weixin.update_expire'); $expire = $expire ? $expire : 3 * 3600; cache($cacheKey, date('Y-m-d H:i:s'), ['expire' => $expire]); } session('openid', $openid); session('wxInfo', $wxData); } /** * 微信注册初始化和授权登录 */ private static function wxInit() { $wxInfo = Wechat::getWxInfo('', true); $openid = isset($wxInfo['openid']) ? $wxInfo['openid'] : ''; $userInfo = Wechat::getUserInfo($openid); $wxInfo = $userInfo? $userInfo : $wxInfo; $cacheKey = 'weixin:init:'.get_client_ip().'_'.$openid; if (empty($wxInfo) || empty($openid)) { if(PRedis::get($cacheKey)<10){ // 重新获取授权 PRedis::inc($cacheKey, 1); Wechat::init(); }else{ Wechat::redirectUrl(url('/weixin/match/index','','',true)); exit; } } // 验证微信信息是否存在 PRedis::del($cacheKey); $cacheKey = 'updateFansInfo:' . $openid; $updateCheck = cache($cacheKey); $wxData = [ 'openid' => $openid, 'nickname' => isset($wxInfo['nickname']) ? $wxInfo['nickname'] : '', 'headimgurl' => isset($wxInfo['headimgurl']) ? $wxInfo['headimgurl'] : '', 'sex' => isset($wxInfo['sex']) ? $wxInfo['sex'] : 0, 'country' => isset($wxInfo['country']) ? $wxInfo['country'] : '', 'province' => isset($wxInfo['province']) ? $wxInfo['province'] : '', 'city' => isset($wxInfo['city']) ? $wxInfo['city'] : '', ]; // 用户信息 $userData = [ 'sex' => $wxData['sex'], 'openid' => $wxData['openid'], 'user_nickname' => $wxData['nickname'], 'last_login_time' => time(), 'last_login_ip' => get_client_ip(), 'avatar' => $wxData['headimgurl'], ]; // 微信用户不存在 $userId = 0; db()->startTrans(); $userInfo = db('user')->field('id,avatar') ->where(['openid' => $openid]) ->where('user_status','>=', 0) ->find(); if (empty(db('fans')->where(['openid' => $openid])->value('id'))) { // 验证更新注册用户 $userId = db('user') ->where(['openid' => $openid]) ->where('user_status','>=', 0) ->value('id'); if ($userId) { db('user')->where(['openid' => $openid]) ->where('user_status','>=', 0) ->update($userData); } else { $userData['wxInfo'] = $wxInfo; $result = Member::regMember($userData); $userId = isset($result['userId']) ? $result['userId'] : 0; if (!$userId) { db()->rollback(); return false; } } // 保存微信用户信息 $wxData['uid'] = $userId; $wxData['created_at'] = time(); $resId = db('fans')->insertGetId($wxData); if (!$resId) { db()->rollback(); return false; } // 每3个小时才更新一次微信信息 $expire = config('weixin.update_expire'); $expire = $expire ? $expire : 3 * 3600; cache($cacheKey, date('Y-m-d H:i:s'), ['expire' => $expire]); } else if (empty($updateCheck) || empty($userInfo)) { // 验证是否绑定用户,更新用户头像信息 $userId = isset($userInfo['id']) ? intval($userInfo['id']) : 0; if (!$userId) { // 注册用户 $userData['wxInfo'] = $wxInfo; $result = Member::regMember($userData); $userId = isset($result['userId']) ? $result['userId'] : 0; if (!$userId) { db()->rollback(); return false; } } // 更新微信用户信息 db('fans')->where(['openid' => $openid])->update($wxData); // 每3个小时才更新一次微信信息 $expire = config('weixin.update_expire'); $expire = $expire ? $expire : 3 * 3600; cache($cacheKey, date('Y-m-d H:i:s'), ['expire' => $expire]); }else{ $updateData = [ 'is_follow'=> isset($wxInfo['subscribe']) ? intval($wxInfo['subscribe']) : 0, 'last_login_time'=> time(), 'last_login_ip'=> get_client_ip(), 'updated_at'=> date('Y-m-d H:i:s') ]; db('user')->where(['openid' => $openid]) ->where('user_status','>=', 0) ->update($updateData); } db()->commit(); // 记录OPENID $userInfo = Member::getInfo(['openid' => $openid]); PRedis::set($cacheKey, $userInfo, 7 * 24 * 3600); session('userInfo', $userInfo); session('openid', $openid); session('wxInfo', $wxData); } /** * 获取ACCESS_TOKEN * @return bool|string */ public static function getAccessToken($key = '', $type = 'tempToken', $refresh = false) { $code = input('code', ''); $appid = Wechat::getConfigs('appid'); $appsecret = Wechat::getConfigs('appsecret'); $cacheKey = 'token:' . $type . ':' . $appid . '_' . $appsecret; $tokenData = PRedis::get($cacheKey); if (empty($tokenData) || $type == 'tempToken' || $refresh) { if ($type == 'tempToken') { $url = sprintf(self::$apiUrl[$type], $code, $appid, $appsecret); } else { $url = sprintf(self::$apiUrl[$type], $appid, $appsecret); } $countKey = "token:count"; $requestCount = PRedis::get($countKey); if($type == 'accessToken'){ if($requestCount >=8000){ PRedis::set("token:error", 'token请求次数超出警告值8000:'.date('Y-m-d H:i:s'), 3 * 24 *3600); return false; } PRedis::set($countKey, $requestCount+1, 24*3600); } $tokenData = httpRequest($url); PRedis::set("token:result", $tokenData, 3600); $code = isset($tokenData['errcode']) ? $tokenData['errcode'] : ''; if ($code || empty($tokenData)) { return $tokenData; } $token = isset($tokenData['access_token']) ? $tokenData['access_token'] : ''; $openid = isset($tokenData['openid']) ? $tokenData['openid'] : ''; $tokenData = [ 'token' => $token, 'openid' => $openid, 'data' => $tokenData, 'date' => date('Y-m-d H:i:s'), 'expire' => $type == 'tempToken' ? time() + 7000 : time() + 7000, ]; PRedis::set($cacheKey, $tokenData, 7200); } $expire = isset($tokenData['expire']) ? intval($tokenData['expire']) : 0; $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; if (($expire && $expire < time()) || empty($token)) { $tokenData = Wechat::getAccessToken($key, $type, true); } if ($key) { $tokenData = isset($tokenData[$key]) ? trim($tokenData[$key]) : ''; } return $tokenData; } /** * 获取微信用户信息 * @param string $openid 获取的用户OPENID,默认当前用户 * @return mixed */ public static function getWxInfo($curOpenid = '', $refreshToken = false, $saveData = false) { $type = $curOpenid ? 'accessToken' : 'tempToken'; $tokenData = Wechat::getAccessToken('', $type, $refreshToken); $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; $openid = isset($tokenData['openid']) ? trim($tokenData['openid']) : ''; if (empty($token)) { return false; } $openid = $curOpenid ? $curOpenid : $openid; $url = sprintf(self::$apiUrl['wxInfo'], $token, $openid); $result = httpRequest($url); $errcode = isset($result['errcode']) ? $result['errcode'] : ''; $cacheCount = PRedis::get('weixin:getWxInfo:' . $openid); if (empty($result) || ($errcode && $cacheCount <= 3)) { // 更新TOKEN重新获取 PRedis::set('weixin:getWxInfo:' . $openid, $cacheCount + 1, 600); $result = Wechat::getWxInfo($curOpenid, $errcode ? true : false); } PRedis::set('weixin:info:'.$openid, $result, 600); if ($saveData && empty($errcode)) { $wxData = [ 'openid' => $openid, 'nickname' => isset($result['nickname']) ? $result['nickname'] : '', 'headimgurl' => isset($result['headimgurl']) ? $result['headimgurl'] : '', 'sex' => isset($result['sex']) ? $result['sex'] : 0, 'country' => isset($result['country']) ? $result['country'] : '', 'province' => isset($result['province']) ? $result['province'] : '', 'city' => isset($result['city']) ? $result['city'] : '', ]; if (empty(db('fans')->where(['openid' => $openid])->value('id'))) { db('fans')->insertGetId($wxData); } else { db('fans')->where(['openid' => $openid])->update($wxData); } } return $result; } /** * 获取微信UserInfo用户信息 * @param string $openid 获取的用户OPENID,默认当前用户 * @return mixed */ public static function getUserInfo($curOpenid = '', $refreshToken = false, $saveData = false) { $type = 'accessToken'; $tokenData = Wechat::getAccessToken('', $type, $refreshToken); $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; $openid = isset($tokenData['openid']) ? trim($tokenData['openid']) : ''; if (empty($token)) { return false; } $openid = $curOpenid ? $curOpenid : $openid; $url = sprintf(self::$apiUrl['userInfo'], $token, $openid); $result = httpRequest($url); $errcode = isset($result['errcode']) ? $result['errcode'] : ''; $cacheCount = PRedis::get('weixin:getInfo:' . $openid); if (empty($result) || ($errcode && $cacheCount <= 3)) { // 更新TOKEN重新获取 PRedis::set('weixin:getInfo:' . $openid, $cacheCount + 1, 600); $result = Wechat::getUserInfo($curOpenid, $errcode ? true : false); } PRedis::set('weixin:userInfo:'.$openid, $result, 600); if ($saveData && empty($errcode)) { $wxData = [ 'openid' => $openid, 'nickname' => isset($result['nickname']) ? $result['nickname'] : '', 'headimgurl' => isset($result['headimgurl']) ? $result['headimgurl'] : '', 'sex' => isset($result['sex']) ? $result['sex'] : 0, 'country' => isset($result['country']) ? $result['country'] : '', 'province' => isset($result['province']) ? $result['province'] : '', 'city' => isset($result['city']) ? $result['city'] : '', ]; if (empty(db('fans')->where(['openid' => $openid])->value('id'))) { db('fans')->insertGetId($wxData); } else { db('fans')->where(['openid' => $openid])->update($wxData); } } return $result; } /** * 获取JSSDK ticket参数 * @author wesmiler */ private static function getTicket($refresh = false, $refreshToken = false) { $appid = Wechat::getConfigs('appid'); $appsecret = Wechat::getConfigs('appsecret'); $cacheKey = 'token:jsapiTicket:' . $appid . '_' . $appsecret; $ticketData = PRedis::get($cacheKey); $ticket = isset($ticketData['ticket']) ? $ticketData['ticket'] : ''; if (empty($ticket) || $refresh) { $tokenData = Wechat::getAccessToken('', 'accessToken', $refreshToken); $code = isset($tokenData['errcode']) ? $tokenData['errcode'] : ''; if ($code) { return $tokenData; } $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; $url = sprintf(self::$jsApiUrl['ticket'], $token); $result = httpRequest($url); if (empty($result)) { PRedis::set('token:jsapiTicket_error', $result, 3600); return false; } $ticket = isset($result['ticket']) ? $result['ticket'] : ''; $ticketData = [ 'ticket' => $ticket, 'expire' => time() + 6000, ]; PRedis::set($cacheKey, $ticketData, 7200); } $expire = isset($ticketData['expire']) ? intval($ticketData['expire']) : 0; if (empty($expire) || $expire < time()) { $ticket = Wechat::getTicket(true); } if (empty($ticket)) { $ticket = Wechat::getTicket(true, true); } return $ticket; } /** * 获取JSSDK签名参数 * @param string $url 请求地址 * @return array */ public static function getJssdkParams($url = '') { // token请求次数超出警告范围 $countKey = "token:count"; $requestCount = PRedis::get($countKey); if($requestCount>=5000){ return ['error'=> 'token请求失败次数已超出警告值5000']; } $countKey = "token:ticketCount:".get_client_ip(); $requestCount = PRedis::get($countKey); if($requestCount>=100){ return ['error'=> '分享参数请求次数过多请稍后重试']; } $result = Wechat::getTicket(); $url = $url ? $url : request()->domain() . request()->url(); $code = isset($result['errcode']) ? $result['errcode'] : ''; if ($code) { return $result; } $params = [ 'jsapi_ticket' => $result, 'noncestr' => uniqid('J'), 'timestamp' => time(), 'url' => $url, ]; PRedis::set($countKey, $requestCount+1, 30); $signature = Wechat::getJssdkSign($params); return [ 'appId' => Wechat::getConfigs('appid'), 'timestamp' => $params['timestamp'], 'nonceStr' => $params['noncestr'], 'signature' => $signature, 'url' => $url, ]; } /** * 获取JSSDK 签名 * @param $params 签名参数 * @return string */ private static function getJssdkSign($params) { $str = []; ksort($params); foreach ($params as $k => $val) { $str[] = $k . '=' . $val; } $str = implode('&', $str); return sha1($str); } /** * jsapi统一下单 * @param $order 订单参数 * @author wesmiler * @return array */ public static function jsapiUnifiedorder($order, $scene = 'jsapiPay') { $appId = Wechat::getConfigs('appid'); $mchId = Wechat::getConfigs('mch_id'); $notifyUrls = Wechat::getConfigs('notify'); $notifyUrl = isset($notifyUrls[$scene]) ? request()->domain().$notifyUrls[$scene] : request()->domain().'/api/notify/index'; $openid = isset($order['openid']) ? trim($order['openid']) : ''; $orderNo = isset($order['orderNo']) ? trim($order['orderNo']) : ''; $totalFee = isset($order['amount']) ? moneyFormat($order['amount']) : 0.00; // 测试支付金额 $payDebug = config('weixin.payDebug'); if ($payDebug) { $totalFee = 0.01; } if (empty($openid) || empty($orderNo) || empty($totalFee)) { return ['code' => 'error', 'message' => '参数错误']; } $unified = array( 'appid' => $appId, 'attach' => 'pay', //商家数据包,原样返回,如果填写中文,请注意转换为utf-8 'body' => isset($order['body']) ? trim($order['body']) : '订单支付', 'mch_id' => $mchId, 'nonce_str' => Wechat::createNonceStr(), 'notify_url' => $notifyUrl, 'openid' => $openid, //rade_type=JSAPI,此参数必传 //'timeStamp'=>time(), 'out_trade_no' => $orderNo, 'spbill_create_ip' => get_client_ip(), 'total_fee' => intval($totalFee * 100), //单位 转为分 'trade_type' => 'JSAPI', ); PRedis::set('orders:'.$scene.':'.$openid.':unified', $unified, 600); $unified['sign'] = Wechat::getPaySign($unified); PRedis::set('orders:'.$scene.':'.$openid.':unifiedSign', $unified, 600); $url = !empty(self::$apiUrl['unifiedorder']) ? trim(self::$apiUrl['unifiedorder']) : 'https://api.mch.weixin.qq.com/pay/unifiedorder'; $data = Wechat::arrayToXml($unified); PRedis::set('orders:'.$scene.':'.$openid.':unifiedXml', ['data'=> $unified,'result'=> $data], 600); $responseXml = Wechat::curlPost($url, $data); //禁止引用外部xml实体 libxml_disable_entity_loader(true); $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA); $unifiedOrder = (array)$unifiedOrder; PRedis::set('orders:'.$scene.':'.$openid.':unifiedResult', ['data'=> $unifiedOrder,'result'=> $data], 600); if ($unifiedOrder === false) { return ['code' => 'exception', 'message' => 'parase xml error']; } if (isset($unifiedOrder['return_code']) && $unifiedOrder['return_code'] != 'SUCCESS') { return ['code' => 'error', 'message' => $unifiedOrder['return_msg']]; } if (isset($unifiedOrder['result_code']) && $unifiedOrder['result_code'] != 'SUCCESS') { return ['code' => 'error', 'message' => $unifiedOrder['err_code']]; } // 返回支付参数 return Wechat::getJsapiPareams($unifiedOrder, $unified); } /** * 提现打款 * @param $order * @param string $scene * @return array|string[] */ public static function transferOrder($order, $scene='withdraw'){ $appId = Wechat::getConfigs('appid'); $mchId = Wechat::getConfigs('mch_id'); $openid = isset($order['openid']) ? trim($order['openid']) : ''; $orderNo = isset($order['orderNo']) ? trim($order['orderNo']) : ''; $totalFee = isset($order['amount']) ? moneyFormat($order['amount']) : 0.00; // 测试支付金额 $payDebug = config('weixin.payDebug'); if ($payDebug) { $totalFee = 0.3; } if (empty($openid) || empty($orderNo) || empty($totalFee)) { return ['code' => 'error', 'message' => '参数错误']; } $unified = array( 'mch_appid' => $appId, 'mchid' => trim($mchId), 'device_info' => uniqid(), 'nonce_str' => Wechat::createNonceStr(), 'partner_trade_no' => $orderNo, 'openid' => $openid, 'check_name' => isset($order['check_name']) && $order['check_name']? trim($order['check_name']) : 'NO_CHECK', // 是否校验真实姓名 'amount' => intval($totalFee * 100), //单位 转为分 'desc' => isset($order['body']) ? trim($order['body']) : '余额提现', 'spbill_create_ip' => get_client_ip(), ); // 是否校验真实姓名 if($unified['check_name'] == 'FORCE_CHECK'){ $unified['re_user_name'] = isset($order['real_name']) ? trim($order['real_name']) : ''; } PRedis::set('orders:'.$scene.':'.$openid.':unified', $unified, 600); $unified['sign'] = Wechat::getPaySign($unified); PRedis::set('orders:'.$scene.':'.$openid.':unifiedSign', $unified, 600); $url = !empty(self::$apiUrl['transfers']) ? trim(self::$apiUrl['transfers']) : 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'; $data = Wechat::arrayToXml($unified); PRedis::set('orders:'.$scene.':'.$openid.':unifiedXml', ['data'=> $unified,'result'=> $data], 600); $responseXml = Wechat::curlPost($url, $data, [], self::$certPaths); //禁止引用外部xml实体 libxml_disable_entity_loader(true); $result = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA); $result = (array)$result; PRedis::set('orders:'.$scene.':'.$openid.':unifiedResult', ['data'=> $result,'result'=> $data], 600); if ($result === false) { return ['code' => 'exception', 'message' => 'parase xml error']; } if (isset($result['return_code']) && $result['return_code'] != 'SUCCESS') { return ['code' => 'error', 'message' => Wechat::getError($result['return_msg']),'type'=>'return_code']; } if (isset($result['result_code']) && $result['result_code'] != 'SUCCESS') { return ['code' => 'error', 'message' => $result['err_code_des'],'error_code'=> $result['err_code'],'type'=>'result_code']; } return $result; } /** * 查询企业付款订单 * @param $trane_order_no 订单号 * @return string[] */ public static function queryTransferOrder($trane_order_no){ $appId = Wechat::getConfigs('appid'); $mchId = Wechat::getConfigs('mch_id'); if (empty($trane_order_no)) { return ['code' => 'error', 'message' => '参数错误']; } $unified = array( 'appid' => $appId, 'mch_id' => trim($mchId), 'nonce_str' => Wechat::createNonceStr(), 'partner_trade_no' => $trane_order_no, ); PRedis::set('orders:transfer:'.$trane_order_no.':unified', $unified, 600); $unified['sign'] = Wechat::getPaySign($unified); PRedis::set('orders:transfer:'.$trane_order_no.':unifiedSign', $unified, 600); $url = !empty(self::$apiUrl['queryTransfer']) ? trim(self::$apiUrl['queryTransfer']) : 'https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo'; $data = Wechat::arrayToXml($unified); PRedis::set('orders:transfer:'.$trane_order_no.':unifiedXml', ['data'=> $unified,'result'=> $data], 600); $responseXml = Wechat::curlPost($url, $data, [], self::$certPaths); //禁止引用外部xml实体 libxml_disable_entity_loader(true); $result = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA); $result = (array)$result; PRedis::set('orders:transfer:'.$trane_order_no.':unifiedResult', ['data'=> $result,'result'=> $data], 600); if ($result === false) { return ['code' => 'exception', 'message' => 'parase xml error']; } if (isset($result['return_code']) && $result['return_code'] != 'SUCCESS') { return ['code' => 'error', 'message' => Wechat::getError($result['return_msg']),'type'=>'return_code']; } if (isset($result['result_code']) && $result['result_code'] != 'SUCCESS') { return ['code' => 'error', 'message' => $result['err_code_des'],'error_code'=> $result['err_code'],'type'=>'result_code']; } return $result; } /** * 退款订单 */ public static function refundOrder(){ } /** * 获取JSAPI支付签名参数 * @param $unifiedOrder 统一下单结果 * @param array $unified 提交统一下单参数 * @return array */ public static function getJsapiPareams($unifiedOrder, $unified = []) { $time = time(); $prepayId = isset($unifiedOrder['prepay_id']) ? $unifiedOrder['prepay_id'] : ''; $params = array( "appId" => Wechat::getConfigs('appid'), "timeStamp" => "$time", //这里是字符串的时间戳,不是int,所以需加引号 "nonceStr" => isset($unified['nonce_str']) ? trim($unified['nonce_str']) : Wechat::createNonceStr(), "package" => "prepay_id=" . $prepayId, "signType" => 'MD5', ); // 重签名 $params['sign'] = Wechat::getPaySign($params); $params['prepay_id'] = $prepayId; return $params; } /** * 查询订单 * @param $outTradeNo 单号 * @return bool|\SimpleXMLElement */ public static function queryOrder($outTradeNo) { $params['appid'] = Wechat::getConfigs('appid'); $params['mch_id'] = Wechat::getConfigs('mch_id'); $params['nonce_str'] = Wechat::createNonceStr(); $params['out_trade_no'] = $outTradeNo; //获取签名数据 $params['sign'] = Wechat::getPaySign($params); $responseXml = Wechat::curlPost(self::$apiUrl['queryOrder'], Wechat::arrayToXml($params)); $result = Wechat::xmlToArray($responseXml); saveLogCache('OrderPay:jsapiPay:query:' . $outTradeNo, json_encode($result, 256)); $returnCode = isset($result['return_code']) ? $result['return_code'] : ''; $tradState = isset($result['trade_state']) ? $result['trade_state'] : ''; $resultCode = isset($result['result_code']) ? $result['result_code'] : ''; if ($resultCode && $returnCode && $tradState) { return $result; } else { return false; } } /** * XML转数组 * @param $xml * @return bool|\SimpleXMLElement */ private static function xmlToArray($xml) { if (empty($xml)) return false; libxml_disable_entity_loader(true); return simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA); } /** * 生成随机字符串 * @param int $length 长度 * @return string */ public static function createNonceStr($length = 16) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $str = ''; for ($i = 0; $i < $length; $i++) { $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); } return $str; } /** * @param $params * @param $key * @return string */ public static function getPaySign($params, $key = '') { ksort($params, SORT_STRING); $key = $key ? $key : Wechat::getConfigs('key'); $taskNo = isset($params['out_trade_no']) ? trim($params['out_trade_no']) : md5(json_encode($params)); $unSignParaString = Wechat::formatParams($params, false); saveLogCache('OrderPay:jsapiPay:makeSign:' . $taskNo, '签名字符串:' . $unSignParaString . "\n密钥:" . $key); $signStr = strtoupper(md5($unSignParaString . "&key=" . $key)); return $signStr; } /** * 验证JSAPI回调 * @param $notifyData * @return array|bool */ public static function checkJsapiNotify($notifyData) { if (empty($notifyData)) { return ['code' => 'error', 'message' => 'parse xml error']; } $orderNo = isset($notifyData['out_trade_no']) ? trim($notifyData['out_trade_no']) : ''; $returnCode = isset($notifyData['return_code']) ? trim($notifyData['return_code']) : ''; $resultCode = isset($notifyData['result_code']) ? trim($notifyData['result_code']) : ''; $nofitySign = isset($notifyData['sign']) ? trim($notifyData['sign']) : ''; if ($returnCode != 'SUCCESS') { $error = isset($notifyData['return_msg']) ? $notifyData['return_msg'] : ''; return ['code' => 'error', 'message' => $error]; } if ($resultCode != 'SUCCESS') { $error = isset($notifyData['err_code']) ? $notifyData['err_code'] : ''; return ['code' => 'error', 'message' => $error]; } // 验证签名 unset($notifyData['sign']); $sign = Wechat::getPaySign($notifyData); saveLogCache('OrderPay:jsapiNotify:checkSign:' . $orderNo, '返回签名:' . $nofitySign . "\n当前签名:" . $sign); if ($nofitySign == $sign) { echo ''; } return true; } /** * 请求接口 * @param string $url 地址 * @param string $postData xml参数 * @param array $options * @return mixed */ public static function curlPost($url = '', $postData = '', $options = array(), $cert=[]) { if (is_array($postData)) { $postData = http_build_query($postData); } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数 if (!empty($options)) { curl_setopt_array($ch, $options); } if($cert){ curl_setopt($ch,CURLOPT_HEADER,FALSE); curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE); if(isset($cert['cert_path']) && $cert['cert_path']){ curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM'); curl_setopt($ch,CURLOPT_SSLCERT, $cert['cert_path']); } if(isset($cert['key_path']) && $cert['key_path']) { curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM'); curl_setopt($ch, CURLOPT_SSLKEY, $cert['key_path']); } } //https请求 不验证证书和host curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $data = curl_exec($ch); var_dump($data); curl_close($ch); return $data; } /** * 数组转XML * @param $arr 数组数据 * @return string */ public static function arrayToXml($arr) { $xml = ""; foreach ($arr as $key => $val) { if ($key == 'detail') { $xml .= "<" . $key . ">"; } else { $xml .= "<" . $key . ">" . $val . ""; } } $xml .= ""; return $xml; } /** * 签名参数格式化 * @param $paraMap 参数 * @param bool $urlEncode 是否编码 * @return bool|string */ protected static function formatParams($paraMap, $urlEncode = false) { $buff = ""; ksort($paraMap); foreach ($paraMap as $k => $v) { if (null != $v && "null" != $v) { if ($urlEncode) { $v = urlencode($v); } $buff .= $k . "=" . $v . "&"; } } $reqPar = ''; if (strlen($buff) > 0) { $reqPar = substr($buff, 0, strlen($buff) - 1); } return $reqPar; } /** * 生成微信二维码 * @param int $sourceId 来源ID * @param $sceneStr 场景参数字符串或ID * @param $scene 场景标识:qrcode-用户二维码 * @param string $qrType 二维码生成类型:QR_SCENE, QR_STR_SCENE, QR_LIMIT_SCENE, QR_LIMIT_STR_SCENE * @param int $expire 有效期,配合场景类型使用,临时二维码最长30天有效期,0-永久 * @return array|bool * @throws \think\Exception * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException * @throws \think\exception\PDOException */ public static function makeQrcode($sourceId = 0, $sceneStr = '', $scene = 'qrcode', $qrType = 'QR_STR_SCENE', $expire = -1) { $expire = $expire>=0 ? $expire : 24 * 3600 * 20; $tokenData = Wechat::getAccessToken('', 'accessToken'); $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; $qrData = db('qrcode') ->where(['source_id' => $sourceId, 'scene' => $scene, 'status' => 1]) ->field('id,source_id,ticket,url,expire_at') ->find(); $qrcodeId = isset($qrData['id']) ? $qrData['id'] : 0; $qrcodeExpire = isset($qrData['expire_at']) ? strtotime($qrData['expire_at']) : 0; if (($expire==0 && $qrcodeId) || $qrcodeExpire > time() && $qrcodeId) { $ticket = isset($qrData['ticket']) ? $qrData['ticket'] : ''; $qrcode = sprintf(self::$apiUrl['getQrcodeByTicket'], $ticket); $qrData['qrcode'] = Wechat::loadImage($qrcode, $scene); if($qrData['qrcode']){ return $qrData; } } if (empty($token)) { $tokenData = Wechat::getAccessToken('', 'accessToken', true); $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; if (empty($token)) { return 1010; } } if (!in_array($qrType, ['QR_SCENE', 'QR_STR_SCENE', 'QR_LIMIT_SCENE', 'QR_LIMIT_STR_SCENE'])) { return 2111; } $data = [ 'expire_seconds' => $expire, 'action_name' => $qrType, ]; if (in_array($qrType, ['QR_SCENE', 'QR_LIMIT_SCENE'])) { $data['action_info'] = ['scene' => ['scene_id' => $sceneStr]]; } else { $data['action_info'] = ['scene' => ['scene_str' => $scene . '_' . $sceneStr]]; } $url = sprintf(self::$apiUrl['makeQrcode'], $token); $result = httpRequest($url, json_encode($data, 256)); $ticket = isset($result['ticket']) ? $result['ticket'] : ''; PRedis::set('qrcodes:result', ['url'=> $url, 'params'=> $data, 'result'=> $result], 600); if ($result && $ticket) { $qrData = [ 'source_id' => $sourceId, 'scene' => $scene, 'scene_str' => $sceneStr, 'ticket' => $ticket, 'expire_at' => $expire>0? date('Y-m-d H:i:s', time() + $expire) : null, 'url' => isset($result['url']) ? $result['url'] : '', ]; if ($qrcodeId) { $qrData['updated_at'] = date('Y-m-d H:i:s'); $qrcodeId = db('qrcode')->where(['id' => $qrcodeId])->update($qrData); } else { $qrData['created_at'] = date('Y-m-d H:i:s'); $qrcodeId = db('qrcode')->insertGetId($qrData); $qrData['id'] = $qrcodeId; } $qrcode = sprintf(self::$apiUrl['getQrcodeByTicket'], $ticket); $qrData['qrcode'] = Wechat::loadImage($qrcode, $scene, true); } return $qrcodeId > 0 ? $qrData : 1009; } /** * 下载图片 * @param $file 远程文件 * @param string $type 类型 * @return bool|string */ public static function loadImage($file, $type='qrcode', $refresh=false){ if(empty($file)){ return false; } $key = "members:{$type}:".md5($file); $qrcode = PRedis::get($key); if(empty($qrcode) || $refresh){ $qrcodeContent = file_get_contents($file); if($qrcodeContent){ if(!is_dir("upload/{$type}/weixin/")){ mkdir("upload/{$type}/weixin/", 0755, true); } $qrcode = "{$type}/weixin/QR_".md5($file).'.jpg'; file_put_contents("upload/".$qrcode, $qrcodeContent); PRedis::set($key, $qrcode, 7 * 24 * 3600); } } if(!file_exists('./upload/'.$qrcode)){ return false; } return cmf_get_image_preview_url($qrcode); } /** * 获取微信二维码数据 * @param $where 条件 * @param string $field 返回字段 * @return bool * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public static function getQrcode($where, $field = '') { if (!is_array($where)) { return false; } $where['status'] = 1; $field = $field ? $field : 'id,source_id,scene,ticket,url'; $info = db('qrcode') ->where($where) ->where('expire_at', '>', date('Y-m-d H:i:s')) ->field($field) ->find(); $ticket = isset($info['ticket']) ? $info['ticket'] : ''; if ($info && $ticket) { $info['qrcode'] = sprintf(self::$apiUrl['getQrcodeByTicket'], $ticket); } return $info; } /** * 处理扫码推荐关注 * @param $postObj */ public static function catchEvent($postObj) { $contentStr = '4444'; $inviteData = []; $openid = isset($postObj['FromUserName']) ? $postObj['FromUserName'] : ''; $event = isset($postObj['Event']) ? $postObj['Event'] : ''; $eventKey = isset($postObj['EventKey']) ? $postObj['EventKey'] : ''; $lockKey = "weixin:lock:" . $openid . '_' . $event; $lock = PRedis::get($lockKey); PRedis::set('weixin:sss:'.$openid, ['info'=> $postObj, ], 600); Wechat::sendCustomMsg($openid, ['content' => $contentStr]); if ($lock) { return false; } PRedis::set($lockKey, $postObj, 8); if ($event && $eventKey) { // 未关注 if ($event == 'subscribe') { $sceneStr = substr($eventKey, 8); $sceneData = $sceneStr ? explode('_', $sceneStr) : []; $scene = isset($sceneData[0]) ? $sceneData[0] : ''; $sceneValue = isset($sceneData[1]) ? $sceneData[1] : ''; // 用户信息注册或更新 $wxInfo = Wechat::getUserInfo($openid, false, true); if ($wxInfo) { $userData = [ 'sex' => $wxInfo['sex'], 'openid' => $openid, 'user_nickname' => $wxInfo['nickname'], 'avatar' => $wxInfo['headimgurl'], 'wxInfo' => $wxInfo, ]; // 验证注册用户,并且推送关注信息 PRedis::set('members:invite:'.$openid, ['reg'=> $userData, 'params'=> $postObj], 600); $res = Member::regMember($userData); PRedis::set('members:register:'.$openid, ['reg'=> $userData, 'result'=> $res], 600); // 注册奖励 $regUserId = isset($res['userId'])? $res['userId'] : 0; $regType = isset($res['type'])? $res['type'] : 0; if($regType == 2 && $regUserId){ $updateData = []; switch ($scene) { case 'qrcode': // 扫码推荐邀请 if ($sceneValue) { // 验证推荐人 $inviteUserId = Member::where(['id' => $sceneValue, 'user_type' => 2]) ->value('id'); if ($inviteUserId) { // 绑定上级推荐人 $updateData['parent_id'] = $inviteUserId; $userData['id'] = $regUserId; // 处理推荐奖励 PRedis::set('members:inviteAward:'.$openid, ['user'=> $userData,'inviteId'=> $inviteUserId], 600); $inviteData = Award::inviteUser($inviteUserId, $userData); } } break; } if($updateData){ Member::saveData(['id'=> $regUserId],$updateData); } } $siteInfo = cmf_get_site_info(); $nickname = isset($wxInfo['nickname']) ? $wxInfo['nickname'] : ''; $siteName = isset($siteInfo['site_name']) ? $siteInfo['site_name'] : '本公众号'; $followMsg = isset($siteInfo['follow_msg']) ? $siteInfo['follow_msg'] : ''; $contentStr = $followMsg ? str_replace('{nickname}', $nickname, $followMsg) : "Hi{$wxInfo['nickname']},欢迎关注{$siteName}!"; } } } // 已关注 if (in_array($event, ['SCAN', 'text', 'subscribe'])) { if ($event === 'SCAN' || ($event === 'subscribe' && $contentStr == '') || $event == 'text') { $wxInfo = Wechat::getUserInfo($openid, false, true); if ($wxInfo) { // 用户信息 $userData = [ 'sex' => $wxInfo['sex'], 'openid' => $openid, 'user_nickname' => $wxInfo['nickname'], 'avatar' => $wxInfo['headimgurl'], 'wxInfo' => $wxInfo, ]; // 验证注册用户,并发送关注信息 PRedis::set('weixin:reg:'.$openid, ['info'=> $wxInfo, 'regInfo'=> $userData], 600); Member::regMember($userData); $siteInfo = cmf_get_site_info(); $nickname = isset($wxInfo['nickname']) ? $wxInfo['nickname'] : ''; $siteName = isset($siteInfo['site_name']) ? $siteInfo['site_name'] : '本公众号'; $followMsg = isset($siteInfo['follow_msg']) ? $siteInfo['follow_msg'] : ''; $contentStr = $followMsg ? str_replace('{nickname}', $nickname, $followMsg) : "Hi【{$nickname}】,欢迎关注{$siteName}!"; } } // 更新关注 Member::saveData(['openid'=> $openid],['is_follow'=> isset($wxInfo['subscribe'])? intval($wxInfo['subscribe']) : 0]); $userInfo = Member::getInfo(['openid' => $openid]); PRedis::set('follow:'.$openid, $wxInfo, 600); session('userInfo', $userInfo); // 推送用户注册消息 $contentStr = $contentStr ? $contentStr : '您好,欢迎关注本公众号'; // $info = Member::where(['openid' => $openid])->field('id,real_name,mobile')->find(); $replyUrl = config('weixin.reply_url'); if ($replyUrl) { $url = Wechat::makeRedirectUrl(url('/weixin/match/index', '', '', true)); $contentStr .= "\n\n点击开启你的脱单之旅"; } Wechat::sendCustomMsg($openid, ['content' => $contentStr]); // 推送邀请注册奖励消息 if ($inviteData) { $inviteOpenid = isset($inviteData['openid']) ? $inviteData['openid'] : ''; $message = isset($inviteData['message']) ? $inviteData['message'] : ''; if ($inviteOpenid && $message) { Wechat::sendCustomMsg($openid, ['content' => $contentStr]); } } }else if($event == 'unsubscribe'){ $cacheKey = "weixin:auth:".$openid; PRedis::del($cacheKey); session('userInfo', null); } // 删除缓存锁 PRedis::del($lockKey); exit; } /** * 响应消息 * @param $postObj */ public static function responseText($postObj) { $openid = isset($postObj['FromUserName']) ? $postObj['FromUserName'] : ''; $msgId = isset($postObj['MsgId']) ? $postObj['MsgId'] : ''; $keyword = isset($postObj['Content']) ? trim($postObj['Content']) : ''; $cacheKey = "messages:replys:{$msgId}"; if(PRedis::get($cacheKey)){ return false; } // 验证关键词回复 PRedis::set($cacheKey, $postObj, 300); if($keyword){ $replyData = Reply::where(['keyword'=> $keyword, 'status'=> 1]) ->field('type,reply_type,title,media_id,content') ->find(); if($replyData){ $msgData = []; $type = isset($replyData['type'])? intval($replyData['type']) : 1; $replyType = isset($replyData['reply_type'])? intval($replyData['reply_type']) : 1; switch($type){ case 1: // 图片 // 文字回复 $content = isset($replyData['content'])? trim($replyData['content']) : ''; // 二维码回复 $mediaId = isset($replyData['media_id'])? trim($replyData['media_id']) : ''; if($mediaId){ $msgData = ['media_id'=> $mediaId]; } // 回复图片 if($msgData){ // 回复文字 if($content){ Wechat::sendCustomMsg($openid, ['content' => $content]); } PRedis::set('message:temp:'.$openid, ['text'=> $content, 'media'=> $msgData], 600); Wechat::sendCustomMsg($openid, $msgData,'image'); //Wechat::rebackOk(); exit; } break; case 2: // 文字 $content = isset($replyData['content'])? trim($replyData['content']) : ''; // 回复文字 if($content){ Wechat::sendCustomMsg($openid, ['content' => $content]); exit; } break; case 3: // 图文 break; } } } // 通用回复 /* $siteInfo = cmf_get_site_info(); $wxInfo = Wechat::getUserInfo($openid); $nickname = isset($wxInfo['nickname']) ? $wxInfo['nickname'] : ''; $siteName = isset($siteInfo['site_name']) ? $siteInfo['site_name'] : '本公众号'; $followMsg = isset($siteInfo['follow_msg']) ? $siteInfo['follow_msg'] : ''; $contentStr = $followMsg ? str_replace('{nickname}', $nickname, $followMsg) : "Hi{$nickname},欢迎关注{$siteName}!"; $replyUrl = config('weixin.reply_url'); if ($replyUrl) { $url = Wechat::makeRedirectUrl(url('/weixin/match/index', '', '', true)); $contentStr .= "\n\n点击开启你的脱单之旅"; } Wechat::sendCustomMsg($openid, ['content' => $contentStr]); */ //Wechat::rebackOk(); echo ' '; exit; } /** * 响应消息 * @param $fromUsername 发送用户 * @param $toUsername 接收用户 * @param $contentStr 发送内容 * @param string $msgType 消息类型 */ public static function responseTplMsg($fromUsername, $toUsername, $contentStr, $msgType = 'text') { $textTpl = " %s "; $resultStr = sprintf($textTpl, $fromUsername, $toUsername, time(), $msgType, $contentStr); echo $resultStr; exit; } /** * 创建菜单 * @param array $menus 菜单数组数据 * @params $delete 是否删除就菜单 * @return bool */ public static function createMenu($menus = [], $delete = false) { $weixinConfig = config('weixin.'); $menus = $menus ? $menus : (isset($weixinConfig['menus']) ? $weixinConfig['menus'] : []); if (empty($menus)) { return false; } $tokenData = Wechat::getAccessToken('', 'accessToken'); $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; if (empty($token)) { return false; } // 删除菜单 if ($delete) { Wechat::delMenu(); } $url = sprintf(self::$apiUrl['createMenu'], $token); $result = httpRequest($url, json_encode(['button' => $menus], 256)); $errorCode = isset($result['errcode']) ? $result['errcode'] : true; if ($errorCode != 0) { return false; } return $result; } /** * 删除菜单 * @return bool */ public static function delMenu() { $tokenData = Wechat::getAccessToken('', 'accessToken'); $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; $url = sprintf(self::$apiUrl['delMenu'], $token); $result = httpRequest($url); $errorCode = isset($result['errcode']) ? $result['errcode'] : true; if ($errorCode == 0) { return false; } return true; } /** * 查询菜单 * @return bool */ public static function getMenu() { $tokenData = Wechat::getAccessToken('', 'accessToken'); $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; $url = sprintf(self::$apiUrl['getMenu'], $token); $result = httpRequest($url); return $result; } /** * 发送客服消息 * @param $openid 接受用户OPENID * @param $content 消息内容:数组 * @param string $msgType 消息类型 * @return mixed */ public static function sendCustomMsg($openid, $content, $msgType = 'text') { $data = [ 'touser' => $openid, 'msgtype' => $msgType, $msgType => $content ]; $lockKey = 'weixin:custonLock:' . $openid . '_' . md5(json_encode($data)); if (PRedis::get($lockKey)) { return false; } $tokenData = Wechat::getAccessToken('', 'accessToken'); $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; PRedis::set($lockKey, $data, 5); $url = sprintf(self::$apiUrl['customMessage'], $token); $result = httpRequest($url, json_encode($data, 256)); PRedis::set('weixin:customLock:' . $openid . '_' . date('YmdHis'), ['data' => $data, 'tokenData' => $tokenData, 'result' => $result], 3); return $result; } /** * 发送模板消息 * @param $openid OPENID * @param $params 参数:title-标题(必填),type-模板类型标识字符串(必填),keywords-模板字段数据(必填),url-模板跳转链接,remark-模板备注信息 * @return array|int */ public static function sendTplMsg($openid, $params, $formatUrl=true) { $title = isset($params['title']) ? $params['title'] : ''; $remark = isset($params['remark']) ? $params['remark'] : ''; $type = isset($params['type']) ? $params['type'] : 'default'; $keywords = isset($params['keywords']) ? $params['keywords'] : []; $keywords = $keywords ? $keywords : []; if ($title) { $keywords['first'] = ['value' => $title, 'color' => '#173177']; } if ($remark) { $keywords['remark'] = ['value' => $remark, 'color' => '#173177']; } $templates = cmf_get_option('templates'); $templateId = isset($templates[$type . '_template']) ? trim($templates[$type . '_template']) : ''; if (empty($templateId)) { return 2110; } ksort($keywords); $tplData = [ 'touser' => $openid, 'template_id' => $templateId, 'data' => $keywords, ]; $url = isset($params['url']) ? trim($params['url']) : ''; if ($url) { $tplData['url'] = $formatUrl? Wechat::makeRedirectUrl($url) : $url; } // 删除旧数据,新增消息记录 $expire = config('weixin.message_expire'); $expire = $expire? $expire : 7; $day = date('Y-m-d H:i:s', strtotime("-{$expire} days")); $deleteKey = "messages:delete:".$openid; if(!PRedis::get($deleteKey)){ Message::where('created_at','<=', $day)->delete(); PRedis::set($deleteKey, $day, 120); } $data = [ 'title' => $title, 'type' => 2, 'msg_tpl' => $type, 'openid' => $openid, 'content' => json_encode($tplData, 256), 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), 'status' => 1, ]; $id = Message::insertGetId($data); $tokenData = Wechat::getAccessToken('', 'accessToken'); $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; if (empty($token)) { return 1010; } $url = sprintf(self::$apiUrl['tplMessage'], $token); $result = httpRequest($url, json_encode($tplData, 256)); $code = isset($result['errcode']) ? $result['errcode'] : true; PRedis::set('messages:result:'.$openid.'_'.date('YmdHi'), ['url'=> $url,'params'=> $tplData,'result'=> $result], 600); if ($code == 'ok') { Message::where(['id' => $id]) ->update(['send_at' => date('Y-m-d H:i:s'), 'status' => 2]); return ['id' => $id, 'message' => 2112]; } else { $result = httpRequest($url, json_encode($tplData, 256)); $code = isset($result['errcode']) ? $result['errcode'] : true; PRedis::set('messages:result:'.$openid.'_'.date('YmdHi'), ['url'=> $url,'params'=> $tplData,'result'=> $result], 600); if ($code === 0) { Message::where(['id' => $id]) ->update(['send_at' => date('Y-m-d H:i:s'), 'status' => 2]); return ['id' => $id, 'message' => 2112]; } } return 2113; } /** * 获取消息模板列表 * @return int|mixed */ public static function getTemplateList(){ $tokenData = Wechat::getAccessToken('', 'accessToken'); $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; if (empty($token)) { return 1010; } $url = sprintf(self::$apiUrl['templateList'], $token); $result = httpRequest($url); PRedis::set('messages:templates', $result, 600); return $result; } /** * 上传媒体素材 * @param $file 素材文件本地相对路径 * @param int $title 素材名称或标题 * @param int $type 素材类型:1-图片,2-语音,3-视频,4-缩略图 * @param bool $refresh 是否刷新缓存重新上传 * @return bool|int|mixed */ public static function uploadMedia($file, $title='', $type=1, $refresh=false){ $mediaFile = 'upload/'.$file; if(!file_exists($mediaFile)){ return false; } $types = [1=>'image',2=>'voice',3=>'video',4=>'thumb']; $typeValue = isset($types[$type])? $types[$type] : ''; if(!$typeValue){ return false; } $cacheKey = "medias:{$typeValue}:".md5($mediaFile); $mediaData = PRedis::get($cacheKey); if($mediaData && !$refresh){ return $mediaData; } $tokenData = Wechat::getAccessToken('', 'accessToken'); $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; if (empty($token)) { return false; } @chmod($mediaFile, 0755); $url = sprintf(self::$apiUrl['uploadMedia'], $token, $typeValue); if(class_exists('CURLFile')){ $postData = ['media'=> new \CURLFile(realpath($mediaFile))]; }else{ $postData = ['media'=> '@'.realpath($mediaFile)]; } $result = httpFilePost($url, $postData); $mediaId = isset($result['media_id'])? $result['media_id'] : ''; $mediaUrl = isset($result['url'])? $result['url'] : ''; // 存库处理 if($result && $mediaId && $mediaUrl){ $data = [ 'type'=> $type, 'title'=> $title? $title : '素材文件', 'url'=> $file, 'media_id'=> $mediaId, 'media_url'=> $mediaUrl, 'updated_at'=> date('Y-m-d H:i:s'), ]; $id = Medias::insertGetId($data); if(!$id){ return false; } $result['id'] = $id; PRedis::set($cacheKey, $result); return $result; } return false; } public static function makeShortUrl($url){ $tokenData = Wechat::getAccessToken('', 'accessToken'); $token = isset($tokenData['token']) ? trim($tokenData['token']) : ''; //$token = '30_WxzS6qNfeaIr1v3Rx_0Ons1D54Usci17_pVyeQGWJbarCZQNV-lo6nD3IKEJmzY_8ZHClzno32BQhNXzfIF-cu_5DMDnqBV9FtnjUXL4k0zD6oskOXj7I6WNuDDjxTz20P_EELHJrbphEDJ6TRWdAAALJO'; if (empty($token)) { return $url; } $apiUrl = sprintf(self::$apiUrl['shortUrl'], $token); $result = httpFilePost($apiUrl, json_encode(['action'=> 'long2short','long_url'=> $url], 256)); $shortUrl = isset($result['short_url'])? trim($result['short_url']) : ''; return $shortUrl? $shortUrl : $url; } /** * 生成普通参数二维码 * @param $str 参数 * @param bool $refresh 是否重新生成 * @return bool */ public static function makeNormalQrcode($str, $refresh = false, $size = 8, $margin=2) { $qrFile = '/upload/qrcode/member/'; if (!is_dir($qrFile)) { @mkdir('.' . $qrFile, 0755, true); } $qrFile = $qrFile . 'U_' . strtoupper(md5($str . '_' . $size)) . '.jpg'; if (is_file($qrFile) && !$refresh) { return false; } QRcode::png($str, '.' . $qrFile, 2, $size, $margin); if(!file_exists('.'.$qrFile)){ return false; } return $qrFile; } /** * 返回给微信 */ public static function rebackOk(){ echo ''; exit; } } ?>