├── .gitignore ├── MIT-LICENSE.txt ├── README.md ├── Wechat ├── Lib │ ├── Cache.php │ ├── Common.php │ ├── Prpcrypt.php │ └── Tools.php ├── Loader.php ├── WechatCard.php ├── WechatCustom.php ├── WechatDevice.php ├── WechatExtends.php ├── WechatHardware.php ├── WechatMedia.php ├── WechatMenu.php ├── WechatMessage.php ├── WechatOauth.php ├── WechatPay.php ├── WechatPoi.php ├── WechatReceive.php ├── WechatScript.php ├── WechatService.php └── WechatUser.php ├── composer.json ├── include.php └── test.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | composer.lock 4 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014-2017 Anyon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![star](https://gitcode.com/ThinkAdmin/wechat-php-sdk/star/badge.svg)](https://gitcode.com/ThinkAdmin/ThinkAdmin) 2 | [![star](https://gitee.com/zoujingli/wechat-php-sdk/badge/star.svg?theme=gvp)](https://gitee.com/zoujingli/ThinkAdmin) 3 | [![Latest Stable Version](https://poser.pugx.org/zoujingli/wechat-php-sdk/v/stable)](https://packagist.org/packages/zoujingli/wechat-php-sdk) 4 | [![Total Downloads](https://poser.pugx.org/zoujingli/wechat-php-sdk/downloads)](https://packagist.org/packages/zoujingli/wechat-php-sdk) 5 | [![Latest Unstable Version](https://poser.pugx.org/zoujingli/wechat-php-sdk/v/unstable)](https://packagist.org/packages/zoujingli/wechat-php-sdk) 6 | [![License](https://poser.pugx.org/zoujingli/wechat-php-sdk/license)](https://packagist.org/packages/zoujingli/wechat-php-sdk) 7 | 8 | 此`SDK`运行最底要求`PHP`版本`5.4`, 建议在`PHP7`上运行以获取最佳性能。 9 | 10 | 微信的部分接口需要缓存数据在本地,因此对目录需要有写权限。 11 | 12 | 我们鼓励大家使用`composer`来管理您的第三方库,方便后期更新操作(尤其是接口类)。 13 | 14 | 近期`access_token`经常无故失效,`SDK`已加入失败状态检测,自动重新获取`access_token`并返回结果. 15 | 16 | 此`SDK`已历经数个线上项目验证与考验,可靠性与稳定性极高,欢迎`fork`或`star`此项目。 17 | 18 | ## 新微信开发工具推荐 19 | 20 | * 微信服务号、微信小程序、微信支付、支付宝支付 21 | * Gitee 仓库 WeChatDevloper: https://gitee.com/zoujingli/WeChatDeveloper 22 | * Github 仓库 WeChatDeveloper:https://github.com/zoujingli/WeChatDeveloper 23 | * Gitcode 仓库 WeChatDeveloper:https://gitcode.com/ThinkAdmin/WeChatDeveloper 24 | 25 | **微信SDK开发帮助及交流** 26 | -- 27 | 28 | * **在做微信开发前,必需先阅读微信官方文档,此SDK也是基于之上进行的封装。** 29 | 30 | * **文档链接地址**:http://www.kancloud.cn/zoujingli/wechat-php-sdk 31 | 32 | * **ThinkAdmin**:https://github.com/zoujingli/ThinkAdmin 33 | 34 | * **开发交流QQ群:513350915(新)** 35 | 36 | **若对您有帮助,可以赞助并支持下作者哦,谢谢!** 37 | -- 38 | ![](http://doc.thinkadmin.top/static/img/pay.png) 39 | 40 | **官方接口文档链接** 41 | -- 42 | 43 | * 使用前需先打开微信帐号的开发模式,详细步骤请查看微信公众平台接口使用说明: 44 | * 微信公众平台: http://mp.weixin.qq.com/wiki/ 45 | * 微信企业平台: http://qydev.weixin.qq.com/wiki/ 46 | * 微信开放平台:https://open.weixin.qq.com/ 47 | * 微信支付接入文档:https://mp.weixin.qq.com/cgi-bin/readtemplate?t=business/course2_tmpl&lang=zh_CN 48 | * 微信商户平台:https://pay.weixin.qq.com 49 | 50 | **微信`SDK`项目源文件托管** 51 | -- 52 | 53 | * SDK 为开源项目,你可以把它用于任何地址,并不受任何约束,欢迎`fork`项目。 54 | * 通过 [Gitee](http://gitee.com/zoujingli/wechat-php-sdk) 下载 SDK 源代码 55 | * 通过 [Github](https://github.com/zoujingli/wechat-php-sdk) 下载 SDK 源代码 56 | * 通过 [Gitcode](http://gitcode.com/ThinkAdmin/wechat-php-sdk) 下载 SDK 源代码 57 | * 通过 [Composer](https://getcomposer.org) 包管理工具下载 SDK 源代码 58 | 59 | **微信`SDK`封装对接及功能** 60 | -- 61 | 62 | * 接入验证 (初级权限) 63 | * 自动回复(文本、图片、语音、视频、音乐、图文) (初级权限) 64 | * 菜单操作(查询、创建、删除) (菜单权限) 65 | * 客服消息(文本、图片、语音、视频、音乐、图文) (认证权限) 66 | * 二维码(创建临时、永久二维码,获取二维码URL) (服务号、认证权限) 67 | * 长链接转短链接接口 (服务号、认证权限) 68 | * 标签操作(查询、创建、修改、移动用户到标签) (认证权限) 69 | * 网页授权(基本授权,用户信息授权) (服务号、认证权限) 70 | * 用户信息(查询用户基本信息、获取关注者列表) (认证权限) 71 | * 多客服功能(客服管理、获取客服记录、客服会话管理) (认证权限) 72 | * 媒体文件(上传、获取) (认证权限) 73 | * 高级群发 (认证权限) 74 | * 模板消息(设置所属行业、添加模板、发送模板消息) (服务号、认证权限) 75 | * 卡券管理(创建、修改、删除、发放、门店管理等) (认证权限) 76 | * 语义理解 (服务号、认证权限) 77 | * 获取微信服务器IP列表 (初级权限) 78 | * 微信JSAPI授权(获取ticket、获取签名) (初级权限) 79 | * 数据统计(用户、图文、消息、接口分析数据) (认证权限) 80 | * 微信支付(网页支付、扫码支付、交易退款、给粉丝打款)(认证服务号并开通支付功能) 81 | 82 | **接口权限备注:** 83 | -- 84 | 85 | * 初级权限:基本权限,任何正常的公众号都有此权限 86 | * 菜单权限:正常的服务号、认证后的订阅号拥有此权限 87 | * 认证权限:分为订阅号、服务号认证,如前缀服务号则仅认证的服务号有此权限 88 | * 支付权限:仅认证后的服务号可以申请此权限 89 | 90 | **微信开放第三方平台** --- (案例及文档整理中) 91 | -- 92 | 93 | * 公众号授权服务 94 | * 公众号推送消息代处理 95 | * 公众号基础业务代处理 96 | * 公众号支付代发起 97 | 98 | ## 版权说明 99 | 100 | **wechat-php-sdk** 遵循 **MIT** 开源协议发布,并免费提供使用。 101 | 102 | 本项目包含的第三方源码和二进制文件的版权信息将另行标注,请在对应文件查看。 103 | 104 | 版权所有 Copyright © 2014-2023 by ThinkAdmin (https://thinkadmin.top) All rights reserved。 105 | 106 | -------------------------------------------------------------------------------- /Wechat/Lib/Cache.php: -------------------------------------------------------------------------------- 1 | 25 | * @date 2016-08-20 17:50 26 | */ 27 | class Cache 28 | { 29 | 30 | /** 31 | * 缓存位置 32 | * @var string 33 | */ 34 | static public $cachepath; 35 | 36 | /** 37 | * 设置缓存 38 | * @param string $name 39 | * @param string $value 40 | * @param int $expired 41 | * @return mixed 42 | */ 43 | static public function set($name, $value, $expired = 0) 44 | { 45 | if (isset(Loader::$callback['CacheSet'])) { 46 | return call_user_func_array(Loader::$callback['CacheSet'], func_get_args()); 47 | } 48 | $data = serialize(array('value' => $value, 'expired' => $expired > 0 ? time() + $expired : 0)); 49 | return self::check() && file_put_contents(self::$cachepath . $name, $data); 50 | } 51 | 52 | /** 53 | * 读取缓存 54 | * @param string $name 55 | * @return mixed 56 | */ 57 | static public function get($name) 58 | { 59 | if (isset(Loader::$callback['CacheGet'])) { 60 | return call_user_func_array(Loader::$callback['CacheGet'], func_get_args()); 61 | } 62 | if (self::check() && ($file = self::$cachepath . $name) && file_exists($file) && ($data = file_get_contents($file)) && !empty($data)) { 63 | $data = unserialize($data); 64 | if (isset($data['expired']) && ($data['expired'] > time() || $data['expired'] === 0)) { 65 | return isset($data['value']) ? $data['value'] : null; 66 | } 67 | } 68 | return null; 69 | } 70 | 71 | /** 72 | * 删除缓存 73 | * @param string $name 74 | * @return mixed 75 | */ 76 | static public function del($name) 77 | { 78 | if (isset(Loader::$callback['CacheDel'])) { 79 | return call_user_func_array(Loader::$callback['CacheDel'], func_get_args()); 80 | } 81 | return self::check() && @unlink(self::$cachepath . $name); 82 | } 83 | 84 | /** 85 | * 输出内容到日志 86 | * @param string $line 87 | * @param string $filename 88 | * @return mixed 89 | */ 90 | static public function put($line, $filename = '') 91 | { 92 | if (isset(Loader::$callback['CachePut'])) { 93 | return call_user_func_array(Loader::$callback['CachePut'], func_get_args()); 94 | } 95 | empty($filename) && $filename = date('Ymd') . '.log'; 96 | return self::check() && file_put_contents(self::$cachepath . $filename, '[' . date('Y/m/d H:i:s') . "] {$line}\n", FILE_APPEND); 97 | } 98 | 99 | /** 100 | * 检查缓存目录 101 | * @return bool 102 | */ 103 | static protected function check() 104 | { 105 | empty(self::$cachepath) && self::$cachepath = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Cache' . DIRECTORY_SEPARATOR; 106 | self::$cachepath = rtrim(self::$cachepath, '/\\') . DIRECTORY_SEPARATOR; 107 | if (!is_dir(self::$cachepath) && !mkdir(self::$cachepath, 0755, true)) { 108 | return false; 109 | } 110 | return true; 111 | } 112 | 113 | /** 114 | * 文件缓存,成功返回文件路径 115 | * @param string $content 文件内容 116 | * @param string $filename 文件名称 117 | * @return bool|string 118 | */ 119 | static public function file($content, $filename = '') 120 | { 121 | if (isset(Loader::$callback['CacheFile'])) { 122 | return call_user_func_array(Loader::$callback['CacheFile'], func_get_args()); 123 | } 124 | empty($filename) && $filename = md5($content) . '.' . self::getFileExt($content); 125 | if (self::check() && file_put_contents(self::$cachepath . $filename, $content)) { 126 | return self::$cachepath . $filename; 127 | } 128 | return false; 129 | } 130 | 131 | /** 132 | * 根据文件流读取文件后缀 133 | * @param string $content 134 | * @return string 135 | */ 136 | static public function getFileExt($content) 137 | { 138 | $types = array( 139 | 255216 => 'jpg', 7173 => 'gif', 6677 => 'bmp', 13780 => 'png', 140 | 7368 => 'mp3', 4838 => 'wma', 7784 => 'mid', 6063 => 'xml', 141 | ); 142 | $typeInfo = @unpack("C2chars", substr($content, 0, 2)); 143 | $typeCode = intval($typeInfo['chars1'] . $typeInfo['chars2']); 144 | return isset($types[$typeCode]) ? $types[$typeCode] : 'mp4'; 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /Wechat/Lib/Common.php: -------------------------------------------------------------------------------- 1 | 28 | * @date 2016/05/28 11:55 29 | */ 30 | class Common 31 | { 32 | 33 | /** API接口URL需要使用此前缀 */ 34 | const API_BASE_URL_PREFIX = 'https://api.weixin.qq.com'; 35 | const API_URL_PREFIX = 'https://api.weixin.qq.com/cgi-bin'; 36 | const GET_TICKET_URL = '/ticket/getticket?'; 37 | const AUTH_URL = '/token?grant_type=client_credential&'; 38 | public $token; 39 | public $encodingAesKey; 40 | public $encrypt_type; 41 | public $appid; 42 | public $appsecret; 43 | public $access_token; 44 | public $postxml; 45 | public $_msg; 46 | public $errCode = 0; 47 | public $errMsg = ""; 48 | public $config = array(); 49 | private $_retry = false; 50 | 51 | /** 52 | * 构造方法 53 | * @param array $options 54 | */ 55 | public function __construct($options = array()) 56 | { 57 | $config = Loader::config($options); 58 | $this->token = isset($config['token']) ? $config['token'] : ''; 59 | $this->appid = isset($config['appid']) ? $config['appid'] : ''; 60 | $this->appsecret = isset($config['appsecret']) ? $config['appsecret'] : ''; 61 | $this->encodingAesKey = isset($config['encodingaeskey']) ? $config['encodingaeskey'] : ''; 62 | $this->config = $config; 63 | } 64 | 65 | /** 66 | * 当前当前错误代码 67 | * @return int 68 | */ 69 | public function getErrorCode() 70 | { 71 | return $this->errCode; 72 | } 73 | 74 | /** 75 | * 获取当前错误内容 76 | * @return string 77 | */ 78 | public function getError() 79 | { 80 | return $this->errMsg; 81 | } 82 | 83 | /** 84 | * 获取当前操作公众号APPID 85 | * @return string 86 | */ 87 | public function getAppid() 88 | { 89 | return $this->appid; 90 | } 91 | 92 | /** 93 | * 获取SDK配置参数 94 | * @return array 95 | */ 96 | public function getConfig() 97 | { 98 | return $this->config; 99 | } 100 | 101 | 102 | /** 103 | * 接口验证 104 | * @return bool 105 | */ 106 | public function valid() 107 | { 108 | $encryptStr = ""; 109 | if ($_SERVER['REQUEST_METHOD'] == "POST") { 110 | $postStr = file_get_contents("php://input"); 111 | $disableEntities = libxml_disable_entity_loader(true); 112 | $array = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA); 113 | libxml_disable_entity_loader($disableEntities); 114 | $this->encrypt_type = isset($_GET["encrypt_type"]) ? $_GET["encrypt_type"] : ''; 115 | if ($this->encrypt_type == 'aes') { 116 | $encryptStr = $array['Encrypt']; 117 | !class_exists('Prpcrypt', false) && require __DIR__ . '/Prpcrypt.php'; 118 | $pc = new Prpcrypt($this->encodingAesKey); 119 | $array = $pc->decrypt($encryptStr, $this->appid); 120 | if (!isset($array[0]) || intval($array[0]) > 0) { 121 | $this->errCode = $array[0]; 122 | $this->errMsg = $array[1]; 123 | Tools::log("Interface Authentication Failed. {$this->errMsg}[{$this->errCode}]", "ERR - {$this->appid}"); 124 | return false; 125 | } 126 | $this->postxml = $array[1]; 127 | empty($this->appid) && $this->appid = $array[2]; 128 | } else { 129 | $this->postxml = $postStr; 130 | } 131 | } elseif (isset($_GET["echostr"])) { 132 | if ($this->checkSignature()) { 133 | @ob_clean(); 134 | exit($_GET["echostr"]); 135 | } 136 | return false; 137 | } 138 | if (!$this->checkSignature($encryptStr)) { 139 | $this->errMsg = 'Interface authentication failed, please use the correct method to call.'; 140 | return false; 141 | } 142 | return true; 143 | } 144 | 145 | /** 146 | * 验证来自微信服务器 147 | * @param string $str 148 | * @return bool 149 | */ 150 | private function checkSignature($str = '') 151 | { 152 | $signature = isset($_GET["msg_signature"]) ? $_GET["msg_signature"] : (isset($_GET["signature"]) ? $_GET["signature"] : ''); 153 | $timestamp = isset($_GET["timestamp"]) ? $_GET["timestamp"] : ''; 154 | $nonce = isset($_GET["nonce"]) ? $_GET["nonce"] : ''; 155 | $tmpArr = array($this->token, $timestamp, $nonce, $str); 156 | sort($tmpArr, SORT_STRING); 157 | if (sha1(implode($tmpArr)) == $signature) { 158 | return true; 159 | } 160 | return false; 161 | } 162 | 163 | /** 164 | * 获取公众号访问 access_token 165 | * @param string $appid 如在类初始化时已提供,则可为空 166 | * @param string $appsecret 如在类初始化时已提供,则可为空 167 | * @param string $token 手动指定access_token,非必要情况不建议用 168 | * @return bool|string 169 | */ 170 | public function getAccessToken($appid = '', $appsecret = '', $token = '') 171 | { 172 | if (!$appid || !$appsecret) { 173 | list($appid, $appsecret) = array($this->appid, $this->appsecret); 174 | } 175 | if ($token) { 176 | return $this->access_token = $token; 177 | } 178 | $cache = 'wechat_access_token_' . $appid; 179 | if (($access_token = Tools::getCache($cache)) && !empty($access_token)) { 180 | return $this->access_token = $access_token; 181 | } 182 | # 检测事件注册 183 | if (isset(Loader::$callback[__FUNCTION__])) { 184 | return $this->access_token = call_user_func_array(Loader::$callback[__FUNCTION__], array(&$this, &$cache)); 185 | } 186 | $result = Tools::httpGet(self::API_URL_PREFIX . self::AUTH_URL . 'appid=' . $appid . '&secret=' . $appsecret); 187 | if ($result) { 188 | $json = json_decode($result, true); 189 | if (!$json || isset($json['errcode'])) { 190 | $this->errCode = $json['errcode']; 191 | $this->errMsg = $json['errmsg']; 192 | Tools::log("Get New AccessToken Error. {$this->errMsg}[{$this->errCode}]", "ERR - {$this->appid}"); 193 | return false; 194 | } 195 | $this->access_token = $json['access_token']; 196 | Tools::log("Get New AccessToken Success.", "MSG - {$this->appid}"); 197 | Tools::setCache($cache, $this->access_token, 5000); 198 | return $this->access_token; 199 | } 200 | return false; 201 | } 202 | 203 | /** 204 | * 接口失败重试 205 | * @param string $method SDK方法名称 206 | * @param array $arguments SDK方法参数 207 | * @return bool|mixed 208 | */ 209 | protected function checkRetry($method, $arguments = array()) 210 | { 211 | Tools::log("Run {$method} Faild. {$this->errMsg}[{$this->errCode}]", "ERR - {$this->appid}"); 212 | if (!$this->_retry && in_array($this->errCode, array('40014', '40001', '41001', '42001'))) { 213 | ($this->_retry = true) && $this->resetAuth(); 214 | $this->errCode = 40001; 215 | $this->errMsg = 'no access'; 216 | Tools::log("Retry Run {$method} ...", "MSG - {$this->appid}"); 217 | return call_user_func_array(array($this, $method), $arguments); 218 | } 219 | return false; 220 | } 221 | 222 | /** 223 | * 删除验证数据 224 | * @param string $appid 如在类初始化时已提供,则可为空 225 | * @return bool 226 | */ 227 | public function resetAuth($appid = '') 228 | { 229 | $authname = 'wechat_access_token_' . (empty($appid) ? $this->appid : $appid); 230 | Tools::log("Reset Auth And Remove Old AccessToken.", "MSG - {$this->appid}"); 231 | $this->access_token = ''; 232 | Tools::removeCache($authname); 233 | return true; 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /Wechat/Lib/Prpcrypt.php: -------------------------------------------------------------------------------- 1 | PKCS7Encoder::$block_size) { 56 | $pad = 0; 57 | } 58 | return substr($text, 0, (strlen($text) - $pad)); 59 | } 60 | 61 | } 62 | 63 | /** 64 | * 接收和推送给公众平台消息的加解密 65 | * @category WechatSDK 66 | * @subpackage library 67 | * @date 2016/06/28 11:59 68 | */ 69 | class Prpcrypt 70 | { 71 | 72 | public $key; 73 | 74 | function __construct($k) 75 | { 76 | $this->key = base64_decode($k . "="); 77 | } 78 | 79 | /** 80 | * 对明文进行加密 81 | * @param string $text 需要加密的明文 82 | * @param string $appid 公众号APPID 83 | * @return array 84 | */ 85 | public function encrypt($text, $appid) 86 | { 87 | try { 88 | //获得16位随机字符串,填充到明文之前 89 | $random = $this->getRandomStr();//"aaaabbbbccccdddd"; 90 | $text = $random . pack("N", strlen($text)) . $text . $appid; 91 | $iv = substr($this->key, 0, 16); 92 | $pkc_encoder = new PKCS7Encoder; 93 | $text = $pkc_encoder->encode($text); 94 | $encrypted = openssl_encrypt($text, 'AES-256-CBC', substr($this->key, 0, 32), OPENSSL_ZERO_PADDING, $iv); 95 | return array(ErrorCode::$OK, $encrypted); 96 | } catch (Exception $e) { 97 | return array(ErrorCode::$EncryptAESError, null); 98 | } 99 | } 100 | 101 | /** 102 | * 对密文进行解密 103 | * @param string $encrypted 需要解密的密文 104 | * @param string $appid 公众号APPID 105 | * @return array 106 | */ 107 | public function decrypt($encrypted, $appid) 108 | { 109 | try { 110 | $iv = substr($this->key, 0, 16); 111 | $decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', substr($this->key, 0, 32), OPENSSL_ZERO_PADDING, $iv); 112 | } catch (Exception $e) { 113 | return array(ErrorCode::$DecryptAESError, null); 114 | } 115 | try { 116 | $pkc_encoder = new PKCS7Encoder; 117 | $result = $pkc_encoder->decode($decrypted); 118 | if (strlen($result) < 16) { 119 | return array(ErrorCode::$DecryptAESError, null); 120 | } 121 | $content = substr($result, 16, strlen($result)); 122 | $len_list = unpack("N", substr($content, 0, 4)); 123 | $xml_len = $len_list[1]; 124 | $xml_content = substr($content, 4, $xml_len); 125 | $from_appid = substr($content, $xml_len + 4); 126 | return array(0, $xml_content, $from_appid); 127 | } catch (Exception $e) { 128 | return array(ErrorCode::$IllegalBuffer, null); 129 | } 130 | 131 | } 132 | 133 | /** 134 | * 随机生成16位字符串 135 | * @return string 生成的字符串 136 | */ 137 | function getRandomStr() 138 | { 139 | $str = ""; 140 | $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; 141 | $max = strlen($str_pol) - 1; 142 | for ($i = 0; $i < 16; $i++) { 143 | $str .= $str_pol[mt_rand(0, $max)]; 144 | } 145 | return $str; 146 | } 147 | 148 | } 149 | 150 | /** 151 | * 仅用作类内部使用 152 | * 不用于官方API接口的errCode码 153 | * Class ErrorCode 154 | */ 155 | class ErrorCode 156 | { 157 | 158 | public static $OK = 0; 159 | public static $ValidateSignatureError = 40001; 160 | public static $ParseXmlError = 40002; 161 | public static $ComputeSignatureError = 40003; 162 | public static $IllegalAesKey = 40004; 163 | public static $ValidateAppidError = 40005; 164 | public static $EncryptAESError = 40006; 165 | public static $DecryptAESError = 40007; 166 | public static $IllegalBuffer = 40008; 167 | public static $EncodeBase64Error = 40009; 168 | public static $DecodeBase64Error = 40010; 169 | public static $GenReturnXmlError = 40011; 170 | public static $errCode = array( 171 | '0' => '处理成功', 172 | '40001' => '校验签名失败', 173 | '40002' => '解析xml失败', 174 | '40003' => '计算签名失败', 175 | '40004' => '不合法的AESKey', 176 | '40005' => '校验AppID失败', 177 | '40006' => 'AES加密失败', 178 | '40007' => 'AES解密失败', 179 | '40008' => '公众平台发送的xml不合法', 180 | '40009' => 'Base64编码失败', 181 | '40010' => 'Base64解码失败', 182 | '40011' => '公众帐号生成回包xml失败' 183 | ); 184 | 185 | /** 186 | * 获取错误消息内容 187 | * @param string $err 188 | * @return bool 189 | */ 190 | public static function getErrText($err) 191 | { 192 | if (isset(self::$errCode[$err])) { 193 | return self::$errCode[$err]; 194 | } 195 | return false; 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /Wechat/Lib/Tools.php: -------------------------------------------------------------------------------- 1 | 27 | * @date 2016/05/28 11:55 28 | */ 29 | class Tools 30 | { 31 | 32 | /** 33 | * 判断字符串是否经过编码方法 34 | * @param string $str 35 | * @return bool 36 | */ 37 | static public function isBase64($str) 38 | { 39 | if ($str == base64_encode(base64_decode($str))) { 40 | return true; 41 | } else { 42 | return false; 43 | } 44 | } 45 | 46 | /** 47 | * 产生随机字符串 48 | * @param int $length 指定字符长度 49 | * @param string $str 字符串前缀 50 | * @return string 51 | */ 52 | static public function createNoncestr($length = 32, $str = "") 53 | { 54 | $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; 55 | for ($i = 0; $i < $length; $i++) { 56 | $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); 57 | } 58 | return $str; 59 | } 60 | 61 | /** 62 | * 数据生成签名 63 | * @param array $data 签名数组 64 | * @param string $method 签名方法 65 | * @return bool|string 签名值 66 | */ 67 | static public function getSignature($data, $method = "sha1") 68 | { 69 | if (!function_exists($method)) { 70 | return false; 71 | } 72 | ksort($data); 73 | $params = array(); 74 | foreach ($data as $key => $value) { 75 | $params[] = "{$key}={$value}"; 76 | } 77 | return $method(join('&', $params)); 78 | } 79 | 80 | /** 81 | * 生成支付签名 82 | * @param array $option 83 | * @param string $partnerKey 84 | * @return string 85 | */ 86 | static public function getPaySign($option, $partnerKey) 87 | { 88 | ksort($option); 89 | $buff = ''; 90 | foreach ($option as $k => $v) { 91 | $buff .= "{$k}={$v}&"; 92 | } 93 | return strtoupper(md5("{$buff}key={$partnerKey}")); 94 | } 95 | 96 | /** 97 | * XML编码 98 | * @param mixed $data 数据 99 | * @param string $root 根节点名 100 | * @param string $item 数字索引的子节点名 101 | * @param string $id 数字索引子节点key转换的属性名 102 | * @return string 103 | */ 104 | static public function arr2xml($data, $root = 'xml', $item = 'item', $id = 'id') 105 | { 106 | return "<{$root}>" . self::_data_to_xml($data, $item, $id) . ""; 107 | } 108 | 109 | /** 110 | * XML内容生成 111 | * @param array $data 数据 112 | * @param string $item 子节点 113 | * @param string $id 节点ID 114 | * @param string $content 节点内容 115 | * @return string 116 | */ 117 | static private function _data_to_xml($data, $item = 'item', $id = 'id', $content = '') 118 | { 119 | foreach ($data as $key => $val) { 120 | is_numeric($key) && $key = "{$item} {$id}=\"{$key}\""; 121 | $content .= "<{$key}>"; 122 | if (is_array($val) || is_object($val)) { 123 | $content .= self::_data_to_xml($val); 124 | } elseif (is_numeric($val)) { 125 | $content .= $val; 126 | } else { 127 | $content .= ''; 128 | } 129 | list($_key,) = explode(' ', $key . ' '); 130 | $content .= ""; 131 | } 132 | return $content; 133 | } 134 | 135 | /** 136 | * 将xml转为array 137 | * @param string $xml 138 | * @return array 139 | */ 140 | static public function xml2arr($xml) 141 | { 142 | $disableEntities = libxml_disable_entity_loader(true); 143 | $result = json_decode(Tools::json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); 144 | libxml_disable_entity_loader($disableEntities); 145 | return $result; 146 | } 147 | 148 | /** 149 | * 生成安全JSON数据 150 | * @param array $array 151 | * @return string 152 | */ 153 | static public function json_encode($array) 154 | { 155 | return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) { 156 | return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE"); 157 | }, json_encode($array)); 158 | } 159 | 160 | /** 161 | * 以get方式提交请求 162 | * @param $url 163 | * @return bool|mixed 164 | */ 165 | static public function httpGet($url) 166 | { 167 | $curl = curl_init(); 168 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); 169 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); 170 | curl_setopt($curl, CURLOPT_SSLVERSION, 1); 171 | curl_setopt($curl, CURLOPT_URL, $url); 172 | curl_setopt($curl, CURLOPT_TIMEOUT, 30); 173 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 174 | list($content, $status) = array(curl_exec($curl), curl_getinfo($curl), curl_close($curl)); 175 | return (intval($status["http_code"]) === 200) ? $content : false; 176 | } 177 | 178 | /** 179 | * 以post方式提交请求 180 | * @param string $url 181 | * @param array|string $data 182 | * @return bool|mixed 183 | */ 184 | static public function httpPost($url, $data) 185 | { 186 | $curl = curl_init(); 187 | curl_setopt($curl, CURLOPT_URL, $url); 188 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); 189 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); 190 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 191 | curl_setopt($curl, CURLOPT_HEADER, false); 192 | curl_setopt($curl, CURLOPT_POST, true); 193 | curl_setopt($curl, CURLOPT_POSTFIELDS, self::_buildPost($data)); 194 | list($content, $status) = array(curl_exec($curl), curl_getinfo($curl), curl_close($curl)); 195 | return (intval($status["http_code"]) === 200) ? $content : false; 196 | } 197 | 198 | /** 199 | * 使用证书,以post方式提交xml到对应的接口url 200 | * @param string $url POST提交的内容 201 | * @param array $data 请求的地址 202 | * @param string $ssl_cer 证书Cer路径 | 证书内容 203 | * @param string $ssl_key 证书Key路径 | 证书内容 204 | * @param int $second 设置请求超时时间 205 | * @return bool|mixed 206 | */ 207 | static public function httpsPost($url, $data, $ssl_cer = null, $ssl_key = null, $second = 30) 208 | { 209 | $curl = curl_init(); 210 | curl_setopt($curl, CURLOPT_URL, $url); 211 | curl_setopt($curl, CURLOPT_TIMEOUT, $second); 212 | curl_setopt($curl, CURLOPT_HEADER, false); 213 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); 214 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); 215 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 216 | if (!is_null($ssl_cer) && file_exists($ssl_cer) && is_file($ssl_cer)) { 217 | curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM'); 218 | curl_setopt($curl, CURLOPT_SSLCERT, $ssl_cer); 219 | } 220 | if (!is_null($ssl_key) && file_exists($ssl_key) && is_file($ssl_key)) { 221 | curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM'); 222 | curl_setopt($curl, CURLOPT_SSLKEY, $ssl_key); 223 | } 224 | curl_setopt($curl, CURLOPT_POST, true); 225 | curl_setopt($curl, CURLOPT_POSTFIELDS, self::_buildPost($data)); 226 | list($content, $status) = array(curl_exec($curl), curl_getinfo($curl), curl_close($curl)); 227 | return (intval($status["http_code"]) === 200) ? $content : false; 228 | } 229 | 230 | /** 231 | * POST数据过滤处理 232 | * @param array $data 233 | * @return array 234 | */ 235 | static private function _buildPost(&$data) 236 | { 237 | if (is_array($data)) { 238 | foreach ($data as &$value) { 239 | if (is_string($value) && $value[0] === '@' && class_exists('CURLFile', false)) { 240 | $filename = realpath(trim($value, '@')); 241 | file_exists($filename) && $value = new CURLFile($filename); 242 | } 243 | } 244 | } 245 | return $data; 246 | } 247 | 248 | /** 249 | * 读取微信客户端IP 250 | * @return null|string 251 | */ 252 | static public function getAddress() 253 | { 254 | foreach (array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP', 'REMOTE_ADDR') as $header) { 255 | if (!isset($_SERVER[$header]) || ($spoof = $_SERVER[$header]) === null) { 256 | continue; 257 | } 258 | sscanf($spoof, '%[^,]', $spoof); 259 | if (!filter_var($spoof, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { 260 | $spoof = null; 261 | } else { 262 | return $spoof; 263 | } 264 | } 265 | return '0.0.0.0'; 266 | } 267 | 268 | /** 269 | * 设置缓存,按需重载 270 | * @param string $cachename 271 | * @param mixed $value 272 | * @param int $expired 273 | * @return bool 274 | */ 275 | static public function setCache($cachename, $value, $expired = 0) 276 | { 277 | return Cache::set($cachename, $value, $expired); 278 | } 279 | 280 | /** 281 | * 获取缓存,按需重载 282 | * @param string $cachename 283 | * @return mixed 284 | */ 285 | static public function getCache($cachename) 286 | { 287 | return Cache::get($cachename); 288 | } 289 | 290 | /** 291 | * 清除缓存,按需重载 292 | * @param string $cachename 293 | * @return bool 294 | */ 295 | static public function removeCache($cachename) 296 | { 297 | return Cache::del($cachename); 298 | } 299 | 300 | /** 301 | * SDK日志处理方法 302 | * @param string $msg 日志行内容 303 | * @param string $type 日志级别 304 | */ 305 | static public function log($msg, $type = 'MSG') 306 | { 307 | Cache::put($type . ' - ' . $msg); 308 | } 309 | 310 | } 311 | -------------------------------------------------------------------------------- /Wechat/Loader.php: -------------------------------------------------------------------------------- 1 | 24 | * @date 2016/10/26 10:21 25 | */ 26 | spl_autoload_register(function ($class) { 27 | if (0 === stripos($class, 'Wechat\\')) { 28 | $filename = dirname(__DIR__) . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; 29 | if (file_exists($filename)) require $filename; 30 | } 31 | }); 32 | 33 | /** 34 | * 微信SDK加载器 35 | * @author Anyon 36 | * @date 2016-08-21 11:06 37 | */ 38 | class Loader 39 | { 40 | 41 | /** 42 | * 事件注册函数 43 | * @var array 44 | */ 45 | static public $callback = array(); 46 | 47 | /** 48 | * 配置参数 49 | * @var array 50 | */ 51 | static protected $config = array(); 52 | 53 | /** 54 | * 对象缓存 55 | * @var array 56 | */ 57 | static protected $cache = array(); 58 | 59 | /** 60 | * 动态注册SDK事件处理函数 61 | * @param string $event 事件名称(getAccessToken|getJsTicket) 62 | * @param string $method 处理方法(可以是普通方法或者类中的方法) 63 | * @param string|null $class 处理对象(可以直接使用的类实例) 64 | */ 65 | static public function register($event, $method, $class = null) 66 | { 67 | if (!empty($class) && class_exists($class, false) && method_exists($class, $method)) { 68 | self::$callback[$event] = array($class, $method); 69 | } else { 70 | self::$callback[$event] = $method; 71 | } 72 | } 73 | 74 | /** 75 | * 获取微信SDK接口对象(别名函数) 76 | * @param string $type 接口类型(Card|Custom|Device|Extends|Media|Menu|Oauth|Pay|Receive|Script|User|Poi) 77 | * @param array $config SDK配置(token,appid,appsecret,encodingaeskey,mch_id,partnerkey,ssl_cer,ssl_key,qrc_img) 78 | * @return WechatCard|WechatCustom|WechatDevice|WechatExtends|WechatMedia|WechatMenu|WechatOauth|WechatPay|WechatPoi|WechatReceive|WechatScript|WechatService|WechatUser 79 | */ 80 | static public function get_instance($type, $config = array()) 81 | { 82 | return self::get($type, $config); 83 | } 84 | 85 | /** 86 | * 获取微信SDK接口对象 87 | * @param string $type 接口类型(Card|Custom|Device|Extends|Media|Menu|Oauth|Pay|Receive|Script|User|Poi) 88 | * @param array $config SDK配置(token,appid,appsecret,encodingaeskey,mch_id,partnerkey,ssl_cer,ssl_key,qrc_img) 89 | * @return WechatCard|WechatCustom|WechatDevice|WechatExtends|WechatMedia|WechatMenu|WechatOauth|WechatPay|WechatPoi|WechatReceive|WechatScript|WechatService|WechatUser 90 | */ 91 | static public function get($type, $config = array()) 92 | { 93 | $index = md5(strtolower($type) . md5(json_encode(self::$config))); 94 | if (!isset(self::$cache[$index])) { 95 | $basicName = 'Wechat' . ucfirst(strtolower($type)); 96 | $className = "\\Wechat\\{$basicName}"; 97 | // 注册类的无命名空间别名,兼容未带命名空间的老版本SDK 98 | !class_exists($basicName, false) && class_alias($className, $basicName); 99 | self::$cache[$index] = new $className(self::config($config)); 100 | } 101 | return self::$cache[$index]; 102 | } 103 | 104 | /** 105 | * 设置配置参数 106 | * @param array $config 107 | * @return array 108 | */ 109 | static public function config($config = array()) 110 | { 111 | !empty($config) && self::$config = array_merge(self::$config, $config); 112 | if (!empty(self::$config['cachepath'])) { 113 | Cache::$cachepath = self::$config['cachepath']; 114 | } 115 | if (empty(self::$config['component_verify_ticket'])) { 116 | self::$config['component_verify_ticket'] = Cache::get('component_verify_ticket'); 117 | } 118 | if (empty(self::$config['token']) && !empty(self::$config['component_token'])) { 119 | self::$config['token'] = self::$config['component_token']; 120 | } 121 | if (empty(self::$config['appsecret']) && !empty(self::$config['component_appsecret'])) { 122 | self::$config['appsecret'] = self::$config['component_appsecret']; 123 | } 124 | if (empty(self::$config['encodingaeskey']) && !empty(self::$config['component_encodingaeskey'])) { 125 | self::$config['encodingaeskey'] = self::$config['component_encodingaeskey']; 126 | } 127 | return self::$config; 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /Wechat/WechatCustom.php: -------------------------------------------------------------------------------- 1 | access_token && !$this->getAccessToken()) { 49 | return false; 50 | } 51 | $result = Tools::httpPost(self::API_URL_PREFIX . self::CUSTOM_SERVICE_GET_RECORD . "access_token={$this->access_token}", Tools::json_encode($data)); 52 | if ($result) { 53 | $json = json_decode($result, true); 54 | if (empty($json) || !empty($json['errcode'])) { 55 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 56 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 57 | return $this->checkRetry(__FUNCTION__, func_get_args()); 58 | } 59 | return $json; 60 | } 61 | return false; 62 | } 63 | 64 | /** 65 | * 获取多客服客服基本信息 66 | * 67 | * @return bool|array 68 | */ 69 | public function getCustomServiceKFlist() 70 | { 71 | if (!$this->access_token && !$this->getAccessToken()) { 72 | return false; 73 | } 74 | $result = Tools::httpGet(self::API_URL_PREFIX . self::CUSTOM_SERVICE_GET_KFLIST . "access_token={$this->access_token}"); 75 | if ($result) { 76 | $json = json_decode($result, true); 77 | if (empty($json) || !empty($json['errcode'])) { 78 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 79 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 80 | return $this->checkRetry(__FUNCTION__, func_get_args()); 81 | } 82 | return $json; 83 | } 84 | return false; 85 | } 86 | 87 | /** 88 | * 获取多客服在线客服接待信息 89 | * 90 | * @return bool|array 91 | */ 92 | public function getCustomServiceOnlineKFlist() 93 | { 94 | if (!$this->access_token && !$this->getAccessToken()) { 95 | return false; 96 | } 97 | $result = Tools::httpGet(self::API_URL_PREFIX . self::CUSTOM_SERVICE_GET_ONLINEKFLIST . "access_token={$this->access_token}"); 98 | if ($result) { 99 | $json = json_decode($result, true); 100 | if (empty($json) || !empty($json['errcode'])) { 101 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 102 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 103 | return $this->checkRetry(__FUNCTION__, func_get_args()); 104 | } 105 | return $json; 106 | } 107 | return false; 108 | } 109 | 110 | /** 111 | * 创建指定多客服会话 112 | * @tutorial 当用户已被其他客服接待或指定客服不在线则会失败 113 | * @param string $openid //用户openid 114 | * @param string $kf_account //客服账号 115 | * @param string $text //附加信息,文本会展示在客服人员的多客服客户端,可为空 116 | * @return bool|array 117 | */ 118 | public function createKFSession($openid, $kf_account, $text = '') 119 | { 120 | if (!$this->access_token && !$this->getAccessToken()) { 121 | return false; 122 | } 123 | $data = array("openid" => $openid, "kf_account" => $kf_account); 124 | $text !== '' && $data["text"] = $text; 125 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::CUSTOM_SESSION_CREATE . "access_token={$this->access_token}", Tools::json_encode($data)); 126 | if ($result) { 127 | $json = json_decode($result, true); 128 | if (empty($json) || !empty($json['errcode'])) { 129 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 130 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 131 | return $this->checkRetry(__FUNCTION__, func_get_args()); 132 | } 133 | return $json; 134 | } 135 | return false; 136 | } 137 | 138 | /** 139 | * 关闭指定多客服会话 140 | * @tutorial 当用户被其他客服接待时则会失败 141 | * @param string $openid //用户openid 142 | * @param string $kf_account //客服账号 143 | * @param string $text //附加信息,文本会展示在客服人员的多客服客户端,可为空 144 | * @return bool | array //成功返回json数组 145 | * { 146 | * "errcode": 0, 147 | * "errmsg": "ok", 148 | * } 149 | */ 150 | public function closeKFSession($openid, $kf_account, $text = '') 151 | { 152 | $data = array("openid" => $openid, "kf_account" => $kf_account); 153 | if ($text) { 154 | $data["text"] = $text; 155 | } 156 | if (!$this->access_token && !$this->getAccessToken()) { 157 | return false; 158 | } 159 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::CUSTOM_SESSION_CLOSE . "access_token={$this->access_token}", Tools::json_encode($data)); 160 | if ($result) { 161 | $json = json_decode($result, true); 162 | if (empty($json) || !empty($json['errcode'])) { 163 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 164 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 165 | return $this->checkRetry(__FUNCTION__, func_get_args()); 166 | } 167 | return $json; 168 | } 169 | return false; 170 | } 171 | 172 | /** 173 | * 获取用户会话状态 174 | * @param string $openid //用户openid 175 | * @return bool | array //成功返回json数组 176 | * { 177 | * "errcode" : 0, 178 | * "errmsg" : "ok", 179 | * "kf_account" : "test1@test", //正在接待的客服 180 | * "createtime": 123456789, //会话接入时间 181 | * } 182 | */ 183 | public function getKFSession($openid) 184 | { 185 | if (!$this->access_token && !$this->getAccessToken()) { 186 | return false; 187 | } 188 | $result = Tools::httpGet(self::API_BASE_URL_PREFIX . self::CUSTOM_SESSION_GET . "access_token={$this->access_token}" . '&openid=' . $openid); 189 | if ($result) { 190 | $json = json_decode($result, true); 191 | if (empty($json) || !empty($json['errcode'])) { 192 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 193 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 194 | return $this->checkRetry(__FUNCTION__, func_get_args()); 195 | } 196 | return $json; 197 | } 198 | return false; 199 | } 200 | 201 | /** 202 | * 获取聊天记录 203 | * @param array $data 数据结构 {"starttime" : 987654321,"endtime" : 987654321,"msgid" : 1,"number" : 10000} 204 | * @return bool|array 205 | */ 206 | public function getCustomMsgList($data) 207 | { 208 | if (!$this->access_token && !$this->getAccessToken()) { 209 | return false; 210 | } 211 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::CUSTOM_SERVICE_GET_MSG_LIST . "access_token={$this->access_token}", Tools::json_encode($data)); 212 | if ($result) { 213 | $json = json_decode($result, true); 214 | if (empty($json) || !empty($json['errcode'])) { 215 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 216 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 217 | return $this->checkRetry(__FUNCTION__, func_get_args()); 218 | } 219 | return $json; 220 | } 221 | return false; 222 | } 223 | 224 | /** 225 | * 获取指定客服的会话列表 226 | * @param string $kf_account //用户openid 227 | * @return bool | array //成功返回json数组 228 | * array( 229 | * 'sessionlist' => array ( 230 | * array ( 231 | * 'openid'=>'OPENID', //客户 openid 232 | * 'createtime'=>123456789, //会话创建时间,UNIX 时间戳 233 | * ), 234 | * array ( 235 | * 'openid'=>'OPENID', //客户 openid 236 | * 'createtime'=>123456789, //会话创建时间,UNIX 时间戳 237 | * ), 238 | * ) 239 | * ) 240 | */ 241 | public function getKFSessionlist($kf_account) 242 | { 243 | if (!$this->access_token && !$this->getAccessToken()) { 244 | return false; 245 | } 246 | $result = Tools::httpGet(self::API_BASE_URL_PREFIX . self::CUSTOM_SESSION_GET_LIST . "access_token={$this->access_token}" . '&kf_account=' . $kf_account); 247 | if ($result) { 248 | $json = json_decode($result, true); 249 | if (empty($json) || !empty($json['errcode'])) { 250 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 251 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 252 | return $this->checkRetry(__FUNCTION__, func_get_args()); 253 | } 254 | return $json; 255 | } 256 | return false; 257 | } 258 | 259 | /** 260 | * 获取未接入会话列表 261 | * @return bool|array 262 | */ 263 | public function getKFSessionWait() 264 | { 265 | if (!$this->access_token && !$this->getAccessToken()) { 266 | return false; 267 | } 268 | $result = Tools::httpGet(self::API_BASE_URL_PREFIX . self::CUSTOM_SESSION_GET_WAIT . "access_token={$this->access_token}"); 269 | if ($result) { 270 | $json = json_decode($result, true); 271 | if (empty($json) || !empty($json['errcode'])) { 272 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 273 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 274 | return $this->checkRetry(__FUNCTION__, func_get_args()); 275 | } 276 | return $json; 277 | } 278 | return false; 279 | } 280 | 281 | /** 282 | * 添加客服账号 283 | * 284 | * @param string $account 完整客服账号(账号前缀@公众号微信号,账号前缀最多10个字符) 285 | * @param string $nickname 客服昵称,最长6个汉字或12个英文字符 286 | * @param string $password 客服账号明文登录密码,会自动加密 287 | * @return bool|array 288 | */ 289 | public function addKFAccount($account, $nickname, $password) 290 | { 291 | $data = array("kf_account" => $account, "nickname" => $nickname, "password" => md5($password)); 292 | if (!$this->access_token && !$this->getAccessToken()) { 293 | return false; 294 | } 295 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::CS_KF_ACCOUNT_ADD_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 296 | if ($result) { 297 | $json = json_decode($result, true); 298 | if (empty($json) || !empty($json['errcode'])) { 299 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 300 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 301 | return $this->checkRetry(__FUNCTION__, func_get_args()); 302 | } 303 | return $json; 304 | } 305 | return false; 306 | } 307 | 308 | /** 309 | * 修改客服账号信息 310 | * 311 | * @param string $account //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符 312 | * @param string $nickname //客服昵称,最长6个汉字或12个英文字符 313 | * @param string $password //客服账号明文登录密码,会自动加密 314 | * @return bool|array 315 | * 成功返回结果 316 | * { 317 | * "errcode": 0, 318 | * "errmsg": "ok", 319 | * } 320 | */ 321 | public function updateKFAccount($account, $nickname, $password) 322 | { 323 | $data = array("kf_account" => $account, "nickname" => $nickname, "password" => md5($password)); 324 | if (!$this->access_token && !$this->getAccessToken()) { 325 | return false; 326 | } 327 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::CS_KF_ACCOUNT_UPDATE_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 328 | if ($result) { 329 | $json = json_decode($result, true); 330 | if (empty($json) || !empty($json['errcode'])) { 331 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 332 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 333 | return $this->checkRetry(__FUNCTION__, func_get_args()); 334 | } 335 | return $json; 336 | } 337 | return false; 338 | } 339 | 340 | /** 341 | * 删除客服账号 342 | * @param string $account 完整客服账号(账号前缀@公众号微信号,账号前缀最多10个字符) 343 | * @return bool|array 344 | */ 345 | public function deleteKFAccount($account) 346 | { 347 | if (!$this->access_token && !$this->getAccessToken()) { 348 | return false; 349 | } 350 | $result = Tools::httpGet(self::API_BASE_URL_PREFIX . self::CS_KF_ACCOUNT_DEL_URL . "access_token={$this->access_token}" . '&kf_account=' . $account); 351 | if ($result) { 352 | $json = json_decode($result, true); 353 | if (empty($json) || !empty($json['errcode'])) { 354 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 355 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 356 | return $this->checkRetry(__FUNCTION__, func_get_args()); 357 | } 358 | return $json; 359 | } 360 | return false; 361 | } 362 | 363 | /** 364 | * 上传客服头像 365 | * @param string $account 完整客服账号(账号前缀@公众号微信号,账号前缀最多10个字符) 366 | * @param string $imgfile 头像文件完整路径,如:'D:\user.jpg'。头像文件必须JPG格式,像素建议640*640 367 | * @return bool|array 368 | */ 369 | public function setKFHeadImg($account, $imgfile) 370 | { 371 | if (!$this->access_token && !$this->getAccessToken()) { 372 | return false; 373 | } 374 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::CS_KF_ACCOUNT_UPLOAD_HEADIMG_URL . "access_token={$this->access_token}&kf_account={$account}", array('media' => '@' . $imgfile)); 375 | if ($result) { 376 | $json = json_decode($result, true); 377 | if (empty($json) || !empty($json['errcode'])) { 378 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 379 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 380 | return $this->checkRetry(__FUNCTION__, func_get_args()); 381 | } 382 | return $json; 383 | } 384 | return false; 385 | } 386 | 387 | } 388 | -------------------------------------------------------------------------------- /Wechat/WechatDevice.php: -------------------------------------------------------------------------------- 1 | 25 | * @date 2016-08-22 10:35 26 | */ 27 | class WechatDevice extends Common 28 | { 29 | 30 | const SHAKEAROUND_DEVICE_APPLYID = '/shakearound/device/applyid?'; //申请设备ID 31 | const SHAKEAROUND_DEVICE_APPLYSTATUS = '/shakearound/device/applystatus?'; //查询设备ID申请审核状态 32 | const SHAKEAROUND_DEVICE_UPDATE = '/shakearound/device/update?'; //编辑设备信息 33 | const SHAKEAROUND_DEVICE_SEARCH = '/shakearound/device/search?'; //查询设备列表 34 | const SHAKEAROUND_DEVICE_BINDLOCATION = '/shakearound/device/bindlocation?'; //配置设备与门店ID的关系 35 | const SHAKEAROUND_DEVICE_BINDPAGE = '/shakearound/device/bindpage?'; //配置设备与页面的绑定关系 36 | const SHAKEAROUND_MATERIAL_ADD = '/shakearound/material/add?'; //上传摇一摇图片素材 37 | const SHAKEAROUND_PAGE_ADD = '/shakearound/page/add?'; //增加页面 38 | const SHAKEAROUND_PAGE_UPDATE = '/shakearound/page/update?'; //编辑页面 39 | const SHAKEAROUND_PAGE_SEARCH = '/shakearound/page/search?'; //查询页面列表 40 | const SHAKEAROUND_PAGE_DELETE = '/shakearound/page/delete?'; //删除页面 41 | const SHAKEAROUND_USER_GETSHAKEINFO = '/shakearound/user/getshakeinfo?'; //获取摇周边的设备及用户信息 42 | const SHAKEAROUND_STATISTICS_DEVICE = '/shakearound/statistics/device?'; //以设备为维度的数据统计接口 43 | const SHAKEAROUND_STATISTICS_PAGE = '/shakearound/statistics/page?'; //以页面为维度的数据统计接口 44 | 45 | 46 | /** 47 | * 申请设备ID 48 | * @param array $data 49 | * @return bool|array 50 | */ 51 | public function applyShakeAroundDevice($data) 52 | { 53 | if (!$this->access_token && !$this->getAccessToken()) { 54 | return false; 55 | } 56 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_APPLYID . "access_token={$this->access_token}", Tools::json_encode($data)); 57 | if ($result) { 58 | $json = json_decode($result, true); 59 | if (empty($json) || !empty($json['errcode'])) { 60 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 61 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 62 | return $this->checkRetry(__FUNCTION__, func_get_args()); 63 | } 64 | return $json; 65 | } 66 | return false; 67 | } 68 | 69 | /** 70 | * 查询设备ID申请审核状态 71 | * @param int $apply_id 72 | * @return bool|array 73 | */ 74 | public function applyStatusShakeAroundDevice($apply_id) 75 | { 76 | if (!$this->access_token && !$this->getAccessToken()) { 77 | return false; 78 | } 79 | $data = array("apply_id" => $apply_id); 80 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_APPLYSTATUS . "access_token={$this->access_token}", Tools::json_encode($data)); 81 | if ($result) { 82 | $json = json_decode($result, true); 83 | if (empty($json) || !empty($json['errcode'])) { 84 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 85 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 86 | return $this->checkRetry(__FUNCTION__, func_get_args()); 87 | } 88 | return $json; 89 | } 90 | return false; 91 | } 92 | 93 | /** 94 | * 编辑设备信息 95 | * @param array $data 96 | * @return bool 97 | */ 98 | public function updateShakeAroundDevice($data) 99 | { 100 | if (!$this->access_token && !$this->getAccessToken()) { 101 | return false; 102 | } 103 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_UPDATE . "access_token={$this->access_token}", Tools::json_encode($data)); 104 | if ($result) { 105 | $json = json_decode($result, true); 106 | if (empty($json) || !empty($json['errcode'])) { 107 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 108 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 109 | return $this->checkRetry(__FUNCTION__, func_get_args()); 110 | } 111 | return true; 112 | } 113 | return false; 114 | } 115 | 116 | 117 | /** 118 | * 查询设备列表 119 | * @param $data 120 | * @return bool|array 121 | */ 122 | public function searchShakeAroundDevice($data) 123 | { 124 | if (!$this->access_token && !$this->getAccessToken()) { 125 | return false; 126 | } 127 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_SEARCH . "access_token={$this->access_token}", Tools::json_encode($data)); 128 | if ($result) { 129 | $json = json_decode($result, true); 130 | if (empty($json) || !empty($json['errcode'])) { 131 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 132 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 133 | return $this->checkRetry(__FUNCTION__, func_get_args()); 134 | } 135 | return $json; 136 | } 137 | return false; 138 | } 139 | 140 | /** 141 | * 配置设备与门店的关联关系 142 | * @param string $device_id 设备编号,若填了UUID、major、minor,则可不填设备编号,若二者都填,则以设备编号为优先 143 | * @param int $poi_id 待关联的门店ID 144 | * @param string $uuid UUID、major、minor,三个信息需填写完整,若填了设备编号,则可不填此信息 145 | * @param int $major 146 | * @param int $minor 147 | * @return bool|array 148 | */ 149 | public function bindLocationShakeAroundDevice($device_id, $poi_id, $uuid = '', $major = 0, $minor = 0) 150 | { 151 | if (!$this->access_token && !$this->getAccessToken()) { 152 | return false; 153 | } 154 | if (!$device_id) { 155 | if (!$uuid || !$major || !$minor) { 156 | return false; 157 | } 158 | $device_identifier = array('uuid' => $uuid, 'major' => $major, 'minor' => $minor); 159 | } else { 160 | $device_identifier = array('device_id' => $device_id); 161 | } 162 | $data = array('device_identifier' => $device_identifier, 'poi_id' => $poi_id); 163 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_BINDLOCATION . "access_token={$this->access_token}", Tools::json_encode($data)); 164 | if ($result) { 165 | $json = json_decode($result, true); 166 | if (empty($json) || !empty($json['errcode'])) { 167 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 168 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 169 | return $this->checkRetry(__FUNCTION__, func_get_args()); 170 | } 171 | return $json; 172 | } 173 | return false; 174 | } 175 | 176 | /** 177 | * 配置设备与其他公众账号门店的关联关系 178 | * @param type $device_identifier 设备信息 179 | * @param type $poi_id 待关联的门店ID 180 | * @param type $poi_appid 目标微信appid 181 | * @return boolean 182 | */ 183 | public function bindLocationOtherShakeAroundDevice($device_identifier, $poi_id, $poi_appid) 184 | { 185 | if (!$this->access_token && !$this->getAccessToken()) { 186 | return false; 187 | } 188 | $data = array('device_identifier' => $device_identifier, 'poi_id' => $poi_id, "type" => 2, "poi_appid" => $poi_appid); 189 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_BINDLOCATION . "access_token={$this->access_token}", Tools::json_encode($data)); 190 | if ($result) { 191 | $json = json_decode($result, true); 192 | if (empty($json) || !empty($json['errcode'])) { 193 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 194 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 195 | return $this->checkRetry(__FUNCTION__, func_get_args()); 196 | } 197 | return $json; 198 | } 199 | return false; 200 | } 201 | 202 | /** 203 | * 配置设备与页面的关联关系 204 | * @param string $device_id 设备编号,若填了UUID、major、minor,则可不填设备编号,若二者都填,则以设备编号为优先 205 | * @param array $page_ids 待关联的页面列表 206 | * @param int $bind 关联操作标志位, 0 为解除关联关系,1 为建立关联关系 207 | * @param int $append 新增操作标志位, 0 为覆盖,1 为新增 208 | * @param string $uuid UUID、major、minor,三个信息需填写完整,若填了设备编号,则可不填此信息 209 | * @param int $major 210 | * @param int $minor 211 | * @return bool|array 212 | */ 213 | public function bindPageShakeAroundDevice($device_id, $page_ids = array(), $bind = 1, $append = 1, $uuid = '', $major = 0, $minor = 0) 214 | { 215 | if (!$this->access_token && !$this->getAccessToken()) { 216 | return false; 217 | } 218 | if (!$device_id) { 219 | if (!$uuid || !$major || !$minor) { 220 | return false; 221 | } 222 | $device_identifier = array('uuid' => $uuid, 'major' => $major, 'minor' => $minor); 223 | } else { 224 | $device_identifier = array('device_id' => $device_id); 225 | } 226 | $data = array('device_identifier' => $device_identifier, 'page_ids' => $page_ids, 'bind' => $bind, 'append' => $append); 227 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_BINDPAGE . "access_token={$this->access_token}", Tools::json_encode($data)); 228 | if ($result) { 229 | $json = json_decode($result, true); 230 | if (empty($json) || !empty($json['errcode'])) { 231 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 232 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 233 | return $this->checkRetry(__FUNCTION__, func_get_args()); 234 | } 235 | return $json; 236 | } 237 | return false; 238 | } 239 | 240 | 241 | /** 242 | * 上传在摇一摇页面展示的图片素材 243 | * @param array $data {"media":'@Path\filename.jpg'} 244 | * @return bool|array 245 | */ 246 | public function uploadShakeAroundMedia($data) 247 | { 248 | if (!$this->access_token && !$this->getAccessToken()) { 249 | return false; 250 | } 251 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_MATERIAL_ADD . "access_token={$this->access_token}", $data); 252 | if ($result) { 253 | $json = json_decode($result, true); 254 | if (empty($json) || !empty($json['errcode'])) { 255 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 256 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 257 | return $this->checkRetry(__FUNCTION__, func_get_args()); 258 | } 259 | return $json; 260 | } 261 | return false; 262 | } 263 | 264 | 265 | /** 266 | * 增加摇一摇出来的页面信息 267 | * @param string $title 在摇一摇页面展示的主标题,不超过6 个字 268 | * @param string $description 在摇一摇页面展示的副标题,不超过7 个字 269 | * @param string $icon_url 在摇一摇页面展示的图片, 格式限定为:jpg,jpeg,png,gif; 建议120*120 , 限制不超过200*200 270 | * @param string $page_url 跳转链接 271 | * @param string $comment 页面的备注信息,不超过15 个字,可不填 272 | * @return bool|array 273 | */ 274 | public function addShakeAroundPage($title, $description, $icon_url, $page_url, $comment = '') 275 | { 276 | if (!$this->access_token && !$this->getAccessToken()) { 277 | return false; 278 | } 279 | $data = array("title" => $title, "description" => $description, "icon_url" => $icon_url, "page_url" => $page_url, "comment" => $comment); 280 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_PAGE_ADD . "access_token={$this->access_token}", Tools::json_encode($data)); 281 | if ($result) { 282 | $json = json_decode($result, true); 283 | if (empty($json) || !empty($json['errcode'])) { 284 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 285 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 286 | return $this->checkRetry(__FUNCTION__, func_get_args()); 287 | } 288 | return $json; 289 | } 290 | return false; 291 | } 292 | 293 | 294 | /** 295 | * 编辑摇一摇出来的页面信息 296 | * @param int $page_id 297 | * @param string $title 在摇一摇页面展示的主标题,不超过6 个字 298 | * @param string $description 在摇一摇页面展示的副标题,不超过7 个字 299 | * @param string $icon_url 在摇一摇页面展示的图片, 格式限定为:jpg,jpeg,png,gif; 建议120*120 , 限制不超过200*200 300 | * @param string $page_url 跳转链接 301 | * @param string $comment 页面的备注信息,不超过15 个字,可不填 302 | * @return bool|array 303 | */ 304 | public function updateShakeAroundPage($page_id, $title, $description, $icon_url, $page_url, $comment = '') 305 | { 306 | if (!$this->access_token && !$this->getAccessToken()) { 307 | return false; 308 | } 309 | $data = array("page_id" => $page_id, "title" => $title, "description" => $description, "icon_url" => $icon_url, "page_url" => $page_url, "comment" => $comment); 310 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_PAGE_UPDATE . "access_token={$this->access_token}", Tools::json_encode($data)); 311 | if ($result) { 312 | $json = json_decode($result, true); 313 | if (empty($json) || !empty($json['errcode'])) { 314 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 315 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 316 | return $this->checkRetry(__FUNCTION__, func_get_args()); 317 | } 318 | return $json; 319 | } 320 | return false; 321 | } 322 | 323 | 324 | /** 325 | * 查询已有的页面 326 | * @param array $page_ids 327 | * @param int $begin 328 | * @param int $count 329 | * @return bool|mixed 330 | */ 331 | public function searchShakeAroundPage($page_ids = array(), $begin = 0, $count = 1) 332 | { 333 | if (!$this->access_token && !$this->getAccessToken()) { 334 | return false; 335 | } 336 | if (!empty($page_ids)) { 337 | $data = array('page_ids' => $page_ids); 338 | } else { 339 | $data = array('begin' => $begin, 'count' => $count); 340 | } 341 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_PAGE_SEARCH . "access_token={$this->access_token}", Tools::json_encode($data)); 342 | if ($result) { 343 | $json = json_decode($result, true); 344 | if (empty($json) || !empty($json['errcode'])) { 345 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 346 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 347 | return $this->checkRetry(__FUNCTION__, func_get_args()); 348 | } 349 | return $json; 350 | } 351 | return false; 352 | } 353 | 354 | 355 | /** 356 | * 删除已有的页面 357 | * @param array $page_ids 358 | * @return bool|array 359 | */ 360 | public function deleteShakeAroundPage($page_ids = array()) 361 | { 362 | if (!$this->access_token && !$this->getAccessToken()) { 363 | return false; 364 | } 365 | $data = array('page_ids' => $page_ids); 366 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_PAGE_DELETE . "access_token={$this->access_token}", Tools::json_encode($data)); 367 | if ($result) { 368 | $json = json_decode($result, true); 369 | if (empty($json) || !empty($json['errcode'])) { 370 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 371 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 372 | return $this->checkRetry(__FUNCTION__, func_get_args()); 373 | } 374 | return $json; 375 | } 376 | return false; 377 | } 378 | 379 | 380 | /** 381 | * 获取设备信息 382 | * @param string $ticket 摇周边业务的ticket(可在摇到的URL中得到,ticket生效时间为30 分钟) 383 | * @return bool|array 384 | */ 385 | public function getShakeInfoShakeAroundUser($ticket) 386 | { 387 | if (!$this->access_token && !$this->getAccessToken()) { 388 | return false; 389 | } 390 | $data = array('ticket' => $ticket); 391 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_USER_GETSHAKEINFO . "access_token={$this->access_token}", Tools::json_encode($data)); 392 | if ($result) { 393 | $json = json_decode($result, true); 394 | if (empty($json) || !empty($json['errcode'])) { 395 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 396 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 397 | return $this->checkRetry(__FUNCTION__, func_get_args()); 398 | } 399 | return $json; 400 | } 401 | return false; 402 | } 403 | 404 | 405 | /** 406 | * 以设备为维度的数据统计接口 407 | * @param int $device_id 设备编号,若填了UUID、major、minor,即可不填设备编号,二者选其一 408 | * @param int $begin_date 起始日期时间戳,最长时间跨度为30 天 409 | * @param int $end_date 结束日期时间戳,最长时间跨度为30 天 410 | * @param string $uuid UUID、major、minor,三个信息需填写完成,若填了设备编辑,即可不填此信息,二者选其一 411 | * @param int $major 412 | * @param int $minor 413 | * @return bool|array 414 | */ 415 | public function deviceShakeAroundStatistics($device_id, $begin_date, $end_date, $uuid = '', $major = 0, $minor = 0) 416 | { 417 | if (!$this->access_token && !$this->getAccessToken()) { 418 | return false; 419 | } 420 | if (!$device_id) { 421 | if (!$uuid || !$major || !$minor) { 422 | return false; 423 | } 424 | $device_identifier = array('uuid' => $uuid, 'major' => $major, 'minor' => $minor); 425 | } else { 426 | $device_identifier = array('device_id' => $device_id); 427 | } 428 | $data = array('device_identifier' => $device_identifier, 'begin_date' => $begin_date, 'end_date' => $end_date); 429 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_STATISTICS_DEVICE . "access_token={$this->access_token}", Tools::json_encode($data)); 430 | if ($result) { 431 | $json = json_decode($result, true); 432 | if (empty($json) || !empty($json['errcode'])) { 433 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 434 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 435 | return $this->checkRetry(__FUNCTION__, func_get_args()); 436 | } 437 | return $json; 438 | } 439 | return false; 440 | } 441 | 442 | 443 | /** 444 | * 以页面为维度的数据统计接口 445 | * @param int $page_id 指定页面的ID 446 | * @param int $begin_date 起始日期时间戳,最长时间跨度为30 天 447 | * @param int $end_date 结束日期时间戳,最长时间跨度为30 天 448 | * @return bool|array 449 | */ 450 | public function pageShakeAroundStatistics($page_id, $begin_date, $end_date) 451 | { 452 | if (!$this->access_token && !$this->getAccessToken()) { 453 | return false; 454 | } 455 | $data = array('page_id' => $page_id, 'begin_date' => $begin_date, 'end_date' => $end_date); 456 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_STATISTICS_DEVICE . "access_token={$this->access_token}", Tools::json_encode($data)); 457 | if ($result) { 458 | $json = json_decode($result, true); 459 | if (empty($json) || !empty($json['errcode'])) { 460 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 461 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 462 | return $this->checkRetry(__FUNCTION__, func_get_args()); 463 | } 464 | return $json; 465 | } 466 | return false; 467 | } 468 | 469 | } 470 | -------------------------------------------------------------------------------- /Wechat/WechatExtends.php: -------------------------------------------------------------------------------- 1 | 26 | * @date 2016-08-22 10:32 27 | */ 28 | class WechatExtends extends Common 29 | { 30 | 31 | const QR_LIMIT_SCENE = 1; 32 | 33 | /** 语义理解 */ 34 | const SEMANTIC_API_URL = '/semantic/semproxy/search?'; 35 | const QRCODE_IMG_URL = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='; 36 | const QRCODE_CREATE_URL = '/qrcode/create?'; 37 | const SHORT_URL = '/shorturl?'; 38 | const QR_SCENE = 0; 39 | 40 | /** 数据分析接口 */ 41 | static $DATACUBE_URL_ARR = array(//用户分析 42 | 'user' => array( 43 | 'summary' => '/datacube/getusersummary?', //获取用户增减数据(getusersummary) 44 | 'cumulate' => '/datacube/getusercumulate?', //获取累计用户数据(getusercumulate) 45 | ), 46 | 'article' => array(//图文分析 47 | 'summary' => '/datacube/getarticlesummary?', //获取图文群发每日数据(getarticlesummary) 48 | 'total' => '/datacube/getarticletotal?', //获取图文群发总数据(getarticletotal) 49 | 'read' => '/datacube/getuserread?', //获取图文统计数据(getuserread) 50 | 'readhour' => '/datacube/getuserreadhour?', //获取图文统计分时数据(getuserreadhour) 51 | 'share' => '/datacube/getusershare?', //获取图文分享转发数据(getusershare) 52 | 'sharehour' => '/datacube/getusersharehour?', //获取图文分享转发分时数据(getusersharehour) 53 | ), 54 | 'upstreammsg' => array(//消息分析 55 | 'summary' => '/datacube/getupstreammsg?', //获取消息发送概况数据(getupstreammsg) 56 | 'hour' => '/datacube/getupstreammsghour?', //获取消息分送分时数据(getupstreammsghour) 57 | 'week' => '/datacube/getupstreammsgweek?', //获取消息发送周数据(getupstreammsgweek) 58 | 'month' => '/datacube/getupstreammsgmonth?', //获取消息发送月数据(getupstreammsgmonth) 59 | 'dist' => '/datacube/getupstreammsgdist?', //获取消息发送分布数据(getupstreammsgdist) 60 | 'distweek' => '/datacube/getupstreammsgdistweek?', //获取消息发送分布周数据(getupstreammsgdistweek) 61 | 'distmonth' => '/datacube/getupstreammsgdistmonth?', //获取消息发送分布月数据(getupstreammsgdistmonth) 62 | ), 63 | 'interface' => array(//接口分析 64 | 'summary' => '/datacube/getinterfacesummary?', //获取接口分析数据(getinterfacesummary) 65 | 'summaryhour' => '/datacube/getinterfacesummaryhour?', //获取接口分析分时数据(getinterfacesummaryhour) 66 | ) 67 | ); 68 | 69 | /** 70 | * 获取二维码图片 71 | * @param string $ticket 传入由getQRCode方法生成的ticket参数 72 | * @return string url 返回http地址 73 | */ 74 | public function getQRUrl($ticket) 75 | { 76 | return self::QRCODE_IMG_URL . urlencode($ticket); 77 | } 78 | 79 | /** 80 | * 长链接转短链接接口 81 | * @param string $long_url 传入要转换的长url 82 | * @return bool|string url 成功则返回转换后的短url 83 | */ 84 | public function getShortUrl($long_url) 85 | { 86 | if (!$this->access_token && !$this->getAccessToken()) { 87 | return false; 88 | } 89 | $data = array('action' => 'long2short', 'long_url' => $long_url); 90 | $result = Tools::httpPost(self::API_URL_PREFIX . self::SHORT_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 91 | if ($result) { 92 | $json = json_decode($result, true); 93 | if (empty($json) || !empty($json['errcode'])) { 94 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 95 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 96 | return $this->checkRetry(__FUNCTION__, func_get_args()); 97 | } 98 | return $json['short_url']; 99 | } 100 | return false; 101 | } 102 | 103 | /** 104 | * 创建二维码ticket 105 | * @param int|string $scene_id 自定义追踪id,临时二维码只能用数值型 106 | * @param int $type 0:临时二维码;1:永久二维码(此时expire参数无效);2:永久二维码(此时expire参数无效) 107 | * @param int $expire 临时二维码有效期,最大为2592000秒(30天) 108 | * @return bool|array ('ticket'=>'qrcode字串','expire_seconds'=>2592000,'url'=>'二维码图片解析后的地址') 109 | */ 110 | public function getQRCode($scene_id, $type = 0, $expire = 2592000) 111 | { 112 | if (!$this->access_token && !$this->getAccessToken()) { 113 | return false; 114 | } 115 | $type = ($type && is_string($scene_id)) ? 2 : $type; 116 | $data = array( 117 | 'action_name' => $type ? ($type == 2 ? "QR_LIMIT_STR_SCENE" : "QR_LIMIT_SCENE") : "QR_SCENE", 118 | 'expire_seconds' => $expire, 119 | 'action_info' => array('scene' => ($type == 2 ? array('scene_str' => $scene_id) : array('scene_id' => $scene_id))) 120 | ); 121 | if ($type == 1) { 122 | unset($data['expire_seconds']); 123 | } 124 | $result = Tools::httpPost(self::API_URL_PREFIX . self::QRCODE_CREATE_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 125 | if ($result) { 126 | $json = json_decode($result, true); 127 | if (empty($json) || !empty($json['errcode'])) { 128 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 129 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 130 | return $this->checkRetry(__FUNCTION__, func_get_args()); 131 | } 132 | return $json; 133 | } 134 | return false; 135 | } 136 | 137 | /** 138 | * 语义理解接口 139 | * @param string $uid 用户唯一id(非开发者id),用户区分公众号下的不同用户(建议填入用户openid) 140 | * @param string $query 输入文本串 141 | * @param string $category 需要使用的服务类型,多个用“,”隔开,不能为空 142 | * @param float $latitude 纬度坐标,与经度同时传入;与城市二选一传入 143 | * @param float $longitude 经度坐标,与纬度同时传入;与城市二选一传入 144 | * @param string $city 城市名称,与经纬度二选一传入 145 | * @param string $region 区域名称,在城市存在的情况下可省略;与经纬度二选一传入 146 | * @return bool|array 147 | */ 148 | public function querySemantic($uid, $query, $category, $latitude = 0.00, $longitude = 0.00, $city = "", $region = "") 149 | { 150 | if (!$this->access_token && !$this->getAccessToken()) { 151 | return false; 152 | } 153 | $data = array( 154 | 'query' => $query, 155 | 'category' => $category, 156 | 'appid' => $this->appid, 157 | 'uid' => '' 158 | ); 159 | //地理坐标或城市名称二选一 160 | if ($latitude) { 161 | $data['latitude'] = $latitude; 162 | $data['longitude'] = $longitude; 163 | } elseif ($city) { 164 | $data['city'] = $city; 165 | } elseif ($region) { 166 | $data['region'] = $region; 167 | } 168 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::SEMANTIC_API_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 169 | if ($result) { 170 | $json = json_decode($result, true); 171 | if (empty($json) || !empty($json['errcode'])) { 172 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 173 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 174 | return $this->checkRetry(__FUNCTION__, func_get_args()); 175 | } 176 | return $json; 177 | } 178 | return false; 179 | } 180 | 181 | /** 182 | * 获取统计数据 183 | * @param string $type 数据分类(user|article|upstreammsg|interface)分别为(用户分析|图文分析|消息分析|接口分析) 184 | * @param string $subtype 数据子分类,参考 DATACUBE_URL_ARR 常量定义部分 或者README.md说明文档 185 | * @param string $begin_date 开始时间 186 | * @param string $end_date 结束时间 187 | * @return bool|array 成功返回查询结果数组,其定义请看官方文档 188 | */ 189 | public function getDatacube($type, $subtype, $begin_date, $end_date = '') 190 | { 191 | if (!$this->access_token && !$this->getAccessToken()) { 192 | return false; 193 | } 194 | if (!isset(self::$DATACUBE_URL_ARR[$type]) || !isset(self::$DATACUBE_URL_ARR[$type][$subtype])) { 195 | return false; 196 | } 197 | $data = array('begin_date' => $begin_date, 'end_date' => $end_date ? $end_date : $begin_date); 198 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::$DATACUBE_URL_ARR[$type][$subtype] . "access_token={$this->access_token}", Tools::json_encode($data)); 199 | if ($result) { 200 | $json = json_decode($result, true); 201 | if (empty($json) || !empty($json['errcode'])) { 202 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 203 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 204 | return $this->checkRetry(__FUNCTION__, func_get_args()); 205 | } 206 | return isset($json['list']) ? $json['list'] : $json; 207 | } 208 | return false; 209 | } 210 | 211 | } 212 | -------------------------------------------------------------------------------- /Wechat/WechatHardware.php: -------------------------------------------------------------------------------- 1 | access_token && !$this->getAccessToken()) { 40 | return false; 41 | } 42 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::DEVICE_COMPEL_UNBINDHTTPS . "access_token={$this->access_token}", Tools::json_encode($data)); 43 | if ($result) { 44 | $json = json_decode($result, true); 45 | if (empty($json) || !empty($json['errcode'])) { 46 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 47 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 48 | return $this->checkRetry(__FUNCTION__, func_get_args()); 49 | } 50 | return $json; 51 | } 52 | return false; 53 | } 54 | 55 | 56 | public function transmsg($data) 57 | { 58 | if (!$this->access_token && !$this->getAccessToken()) { 59 | return false; 60 | } 61 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::DEVICE_TRANSMSG . "access_token={$this->access_token}", Tools::json_encode($data)); 62 | //dump($result); 63 | if ($result) { 64 | $json = json_decode($result, true); 65 | if (empty($json) || !empty($json['errcode'])) { 66 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 67 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 68 | return $this->checkRetry(__FUNCTION__, func_get_args()); 69 | } 70 | return $json; 71 | } 72 | return false; 73 | } 74 | 75 | public function getQrcode($product_id) 76 | { 77 | if (!$this->access_token && !$this->getAccessToken()) { 78 | return false; 79 | } 80 | $result = Tools::httpGet(self::API_BASE_URL_PREFIX . self::DEVICE_GETQRCODE . "access_token={$this->access_token}&product_id=$product_id"); 81 | if ($result) { 82 | $json = json_decode($result, true); 83 | if (empty($json) || !empty($json['errcode'])) { 84 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 85 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 86 | return $this->checkRetry(__FUNCTION__, func_get_args()); 87 | } 88 | return $json; 89 | } 90 | return false; 91 | } 92 | 93 | /** 94 | * 设备授权 95 | * @param $data 96 | * @return bool|mixed 97 | */ 98 | public function deviceAuthorize($data) 99 | { 100 | if (!$this->access_token && !$this->getAccessToken()) { 101 | return false; 102 | } 103 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::DEVICE_AUTHORIZE_DEVICE . "access_token={$this->access_token}", Tools::json_encode($data)); 104 | if ($result) { 105 | $json = json_decode($result, true); 106 | if (empty($json) || !empty($json['errcode'])) { 107 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 108 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 109 | return $this->checkRetry(__FUNCTION__, func_get_args()); 110 | } 111 | return $json; 112 | } 113 | return false; 114 | } 115 | 116 | /** 117 | * 获取设备二维码 118 | * @param $data 119 | * @return bool|mixed 120 | */ 121 | public function getDeviceQrcode($data) 122 | { 123 | if (!$this->access_token && !$this->getAccessToken()) { 124 | return false; 125 | } 126 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::DEVICE_CREATE_QRCODE . "access_token={$this->access_token}", Tools::json_encode($data)); 127 | if ($result) { 128 | $json = json_decode($result, true); 129 | if (empty($json) || !empty($json['errcode'])) { 130 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 131 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 132 | return $this->checkRetry(__FUNCTION__, func_get_args()); 133 | } 134 | return $json; 135 | } 136 | return false; 137 | } 138 | 139 | /** 140 | * 获取设备状态 141 | * @param $device_id 142 | * @return bool|mixed 143 | */ 144 | public function getDeviceStat($device_id) 145 | { 146 | if (!$this->access_token && !$this->getAccessToken()) { 147 | return false; 148 | } 149 | $result = Tools::httpGet(self::API_BASE_URL_PREFIX . self::DEVICE_GET_STAT . "access_token={$this->access_token}&device_id=$device_id"); 150 | if ($result) { 151 | $json = json_decode($result, true); 152 | if (empty($json) || !empty($json['errcode'])) { 153 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 154 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 155 | return $this->checkRetry(__FUNCTION__, func_get_args()); 156 | } 157 | return $json; 158 | } 159 | return false; 160 | } 161 | 162 | } -------------------------------------------------------------------------------- /Wechat/WechatMedia.php: -------------------------------------------------------------------------------- 1 | 27 | * @date 2016/10/26 14:47 28 | */ 29 | class WechatMedia extends Common 30 | { 31 | 32 | const MEDIA_UPLOAD_URL = '/media/upload?'; 33 | const MEDIA_UPLOADIMG_URL = '/media/uploadimg?'; 34 | const MEDIA_GET_URL = '/media/get?'; 35 | const MEDIA_VIDEO_UPLOAD = '/media/uploadvideo?'; 36 | const MEDIA_FOREVER_UPLOAD_URL = '/material/add_material?'; 37 | const MEDIA_FOREVER_NEWS_UPLOAD_URL = '/material/add_news?'; 38 | const MEDIA_FOREVER_NEWS_UPDATE_URL = '/material/update_news?'; 39 | const MEDIA_FOREVER_GET_URL = '/material/get_material?'; 40 | const MEDIA_FOREVER_DEL_URL = '/material/del_material?'; 41 | const MEDIA_FOREVER_COUNT_URL = '/material/get_materialcount?'; 42 | const MEDIA_FOREVER_BATCHGET_URL = '/material/batchget_material?'; 43 | const MEDIA_UPLOADNEWS_URL = '/media/uploadnews?'; 44 | 45 | /** 46 | * 上传临时素材,有效期为3天(认证后的订阅号可用) 47 | * 注意:上传大文件时可能需要先调用 set_time_limit(0) 避免超时 48 | * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义 49 | * 注意:临时素材的media_id是可复用的! 50 | * @param array $data {"media":'@Path\filename.jpg'} 51 | * @param string $type 类型:图片:image 语音:voice 视频:video 缩略图:thumb 52 | * @return bool|array 53 | */ 54 | public function uploadMedia($data, $type) 55 | { 56 | if (!$this->access_token && !$this->getAccessToken()) { 57 | return false; 58 | } 59 | if (Tools::isBase64($data['media'])) { 60 | $cache_file = Cache::file(base64_decode($data['media'])); 61 | $data['media'] = "@{$cache_file}"; 62 | } 63 | $result = Tools::httpPost(self::API_URL_PREFIX . self::MEDIA_UPLOAD_URL . "access_token={$this->access_token}&type={$type}", $data); 64 | !empty($cache_file) && @unlink($cache_file); 65 | if ($result) { 66 | $json = json_decode($result, true); 67 | if (empty($json) || !empty($json['errcode'])) { 68 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 69 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 70 | return $this->checkRetry(__FUNCTION__, func_get_args()); 71 | } 72 | return $json; 73 | } 74 | return false; 75 | } 76 | 77 | /** 78 | * 获取临时素材(认证后的订阅号可用) 79 | * @param string $media_id 媒体文件id 80 | * @param bool $is_video 是否为视频文件,默认为否 81 | * @return bool|array 82 | */ 83 | public function getMedia($media_id, $is_video = false) 84 | { 85 | if (!$this->access_token && !$this->getAccessToken()) { 86 | return false; 87 | } 88 | $result = Tools::httpGet(self::API_URL_PREFIX . self::MEDIA_GET_URL . "access_token={$this->access_token}" . '&media_id=' . $media_id); 89 | if ($result) { 90 | if (is_string($result)) { 91 | $json = json_decode($result, true); 92 | if (isset($json['errcode'])) { 93 | $this->errCode = $json['errcode']; 94 | $this->errMsg = $json['errmsg']; 95 | return $this->checkRetry(__FUNCTION__, func_get_args()); 96 | } 97 | } 98 | return $result; 99 | } 100 | return false; 101 | } 102 | 103 | /** 104 | * 获取临时素材(认证后的订阅号可用) 包含返回的http头信息 105 | * @param string $media_id 媒体文件id 106 | * @param bool $is_video 是否为视频文件,默认为否 107 | * @return bool|array 108 | */ 109 | public function getMediaWithHttpInfo($media_id, $is_video = false) 110 | { 111 | if (!$this->access_token && !$this->getAccessToken()) { 112 | return false; 113 | } 114 | $url_prefix = $is_video ? str_replace('https', 'http', self::API_URL_PREFIX) : self::API_URL_PREFIX; 115 | $url = $url_prefix . self::MEDIA_GET_URL . "access_token={$this->access_token}" . '&media_id=' . $media_id; 116 | $oCurl = curl_init(); 117 | if (stripos($url, "https://") !== false) { 118 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, false); 119 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false); 120 | curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); 121 | } 122 | curl_setopt($oCurl, CURLOPT_URL, $url); 123 | curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1); 124 | $sContent = curl_exec($oCurl); 125 | $aStatus = curl_getinfo($oCurl); 126 | $result = array(); 127 | if (intval($aStatus["http_code"]) !== 200) { 128 | return false; 129 | } 130 | if ($sContent) { 131 | if (is_string($sContent)) { 132 | $json = json_decode($sContent, true); 133 | if (isset($json['errcode'])) { 134 | $this->errCode = $json['errcode']; 135 | $this->errMsg = $json['errmsg']; 136 | return $this->checkRetry(__FUNCTION__, func_get_args()); 137 | } 138 | } 139 | $result['content'] = $sContent; 140 | $result['info'] = $aStatus; 141 | return $result; 142 | } 143 | return false; 144 | } 145 | 146 | /** 147 | * 上传图片,本接口所上传的图片不占用公众号的素材库中图片数量的5000个的限制。图片仅支持jpg/png格式,大小必须在1MB以下。 (认证后的订阅号可用) 148 | * 注意:上传大文件时可能需要先调用 set_time_limit(0) 避免超时 149 | * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义 150 | * @param array $data {"media":'@Path\filename.jpg'} 151 | * @return bool|array 152 | */ 153 | public function uploadImg($data) 154 | { 155 | if (!$this->access_token && !$this->getAccessToken()) { 156 | return false; 157 | } 158 | if (Tools::isBase64($data['media'])) { 159 | $cache_file = Cache::file(base64_decode($data['media'])); 160 | $data['media'] = "@{$cache_file}"; 161 | } 162 | $result = Tools::httpPost(self::API_URL_PREFIX . self::MEDIA_UPLOADIMG_URL . "access_token={$this->access_token}", $data); 163 | !empty($cache_file) && @unlink($cache_file); 164 | if ($result) { 165 | $json = json_decode($result, true); 166 | if (empty($json) || !empty($json['errcode'])) { 167 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 168 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 169 | return $this->checkRetry(__FUNCTION__, func_get_args()); 170 | } 171 | return $json; 172 | } 173 | return false; 174 | } 175 | 176 | /** 177 | * 上传永久素材(认证后的订阅号可用) 178 | * 新增的永久素材也可以在公众平台官网素材管理模块中看到 179 | * 注意:上传大文件时可能需要先调用 set_time_limit(0) 避免超时 180 | * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义 181 | * @param array $data {"media":'@Path\filename.jpg'}, 支持base64格式 182 | * @param string $type 类型:图片:image 语音:voice 视频:video 缩略图:thumb 183 | * @param bool $is_video 是否为视频文件,默认为否 184 | * @param array $video_info 视频信息数组,非视频素材不需要提供 array('title'=>'视频标题','introduction'=>'描述') 185 | * @return bool|array 186 | */ 187 | public function uploadForeverMedia($data, $type, $is_video = false, $video_info = array()) 188 | { 189 | if (!$this->access_token && !$this->getAccessToken()) { 190 | return false; 191 | } 192 | $is_video && ($data['description'] = Tools::json_encode($video_info)); 193 | if (Tools::isBase64($data['media'])) { 194 | $cache_file = Cache::file(base64_decode($data['media'])); 195 | $data['media'] = "@{$cache_file}"; 196 | } 197 | $result = Tools::httpPost(self::API_URL_PREFIX . self::MEDIA_FOREVER_UPLOAD_URL . "access_token={$this->access_token}&type={$type}", $data); 198 | !empty($cache_file) && @unlink($cache_file); 199 | if ($result) { 200 | $json = json_decode($result, true); 201 | if (empty($json) || !empty($json['errcode'])) { 202 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 203 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 204 | return $this->checkRetry(__FUNCTION__, func_get_args()); 205 | } 206 | return $json; 207 | } 208 | return false; 209 | } 210 | 211 | /** 212 | * 上传永久图文素材(认证后的订阅号可用) 213 | * 新增的永久素材也可以在公众平台官网素材管理模块中看到 214 | * @param array $data 消息结构{"articles":[{...}]} 215 | * @return bool|array 216 | */ 217 | public function uploadForeverArticles($data) 218 | { 219 | if (!$this->access_token && !$this->getAccessToken()) { 220 | return false; 221 | } 222 | $result = Tools::httpPost(self::API_URL_PREFIX . self::MEDIA_FOREVER_NEWS_UPLOAD_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 223 | if ($result) { 224 | $json = json_decode($result, true); 225 | if (empty($json) || !empty($json['errcode'])) { 226 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 227 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 228 | return $this->checkRetry(__FUNCTION__, func_get_args()); 229 | } 230 | return $json; 231 | } 232 | return false; 233 | } 234 | 235 | /** 236 | * 修改永久图文素材(认证后的订阅号可用) 237 | * 永久素材也可以在公众平台官网素材管理模块中看到 238 | * @param string $media_id 图文素材id 239 | * @param array $data 消息结构{"articles":[{...}]} 240 | * @param int $index 更新的文章在图文素材的位置,第一篇为0,仅多图文使用 241 | * @return bool|array 242 | */ 243 | public function updateForeverArticles($media_id, $data, $index = 0) 244 | { 245 | if (!$this->access_token && !$this->getAccessToken()) { 246 | return false; 247 | } 248 | !isset($data['index']) && $data['index'] = $index; 249 | !isset($data['media_id']) && $data['media_id'] = $media_id; 250 | $result = Tools::httpPost(self::API_URL_PREFIX . self::MEDIA_FOREVER_NEWS_UPDATE_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 251 | if ($result) { 252 | $json = json_decode($result, true); 253 | if (empty($json) || !empty($json['errcode'])) { 254 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 255 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 256 | return $this->checkRetry(__FUNCTION__, func_get_args()); 257 | } 258 | return $json; 259 | } 260 | return false; 261 | } 262 | 263 | /** 264 | * 获取永久素材(认证后的订阅号可用) 265 | * 返回图文消息数组或二进制数据,失败返回false 266 | * @param string $media_id 媒体文件id 267 | * @param bool $is_video 是否为视频文件,默认为否 268 | * @return bool|array 269 | */ 270 | public function getForeverMedia($media_id, $is_video = false) 271 | { 272 | if (!$this->access_token && !$this->getAccessToken()) { 273 | return false; 274 | } 275 | $data = array('media_id' => $media_id); 276 | $result = Tools::httpPost(self::API_URL_PREFIX . self::MEDIA_FOREVER_GET_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 277 | if ($result) { 278 | if (is_string($result) && ($json = json_decode($result, true))) { 279 | if (isset($json['errcode'])) { 280 | $this->errCode = $json['errcode']; 281 | $this->errMsg = $json['errmsg']; 282 | return $this->checkRetry(__FUNCTION__, func_get_args()); 283 | } 284 | return $json; 285 | } 286 | return $result; 287 | } 288 | return false; 289 | } 290 | 291 | /** 292 | * 删除永久素材(认证后的订阅号可用) 293 | * @param string $media_id 媒体文件id 294 | * @return bool 295 | */ 296 | public function delForeverMedia($media_id) 297 | { 298 | if (!$this->access_token && !$this->getAccessToken()) { 299 | return false; 300 | } 301 | $data = array('media_id' => $media_id); 302 | $result = Tools::httpPost(self::API_URL_PREFIX . self::MEDIA_FOREVER_DEL_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 303 | if ($result) { 304 | $json = json_decode($result, true); 305 | if (empty($json) || !empty($json['errcode'])) { 306 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 307 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 308 | return $this->checkRetry(__FUNCTION__, func_get_args()); 309 | } 310 | return true; 311 | } 312 | return false; 313 | } 314 | 315 | /** 316 | * 获取永久素材列表(认证后的订阅号可用) 317 | * @param string $type 素材的类型,图片(image)、视频(video)、语音 (voice)、图文(news) 318 | * @param int $offset 全部素材的偏移位置,0表示从第一个素材 319 | * @param int $count 返回素材的数量,取值在1到20之间 320 | * @return bool|array 321 | * 返回数组格式: 322 | * array( 323 | * 'total_count'=>0, //该类型的素材的总数 324 | * 'item_count'=>0, //本次调用获取的素材的数量 325 | * 'item'=>array() //素材列表数组,内容定义请参考官方文档 326 | * ) 327 | */ 328 | public function getForeverList($type, $offset, $count) 329 | { 330 | if (!$this->access_token && !$this->getAccessToken()) { 331 | return false; 332 | } 333 | $data = array('type' => $type, 'offset' => $offset, 'count' => $count); 334 | $result = Tools::httpPost(self::API_URL_PREFIX . self::MEDIA_FOREVER_BATCHGET_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 335 | if ($result) { 336 | $json = json_decode($result, true); 337 | if (empty($json) || !empty($json['errcode'])) { 338 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 339 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 340 | return $this->checkRetry(__FUNCTION__, func_get_args()); 341 | } 342 | return $json; 343 | } 344 | return false; 345 | } 346 | 347 | /** 348 | * 获取永久素材总数(认证后的订阅号可用) 349 | * @return bool|array 350 | * 返回数组格式: 351 | * array( 352 | * 'voice_count'=>0, //语音总数量 353 | * 'video_count'=>0, //视频总数量 354 | * 'image_count'=>0, //图片总数量 355 | * 'news_count'=>0 //图文总数量 356 | * ) 357 | */ 358 | public function getForeverCount() 359 | { 360 | if (!$this->access_token && !$this->getAccessToken()) { 361 | return false; 362 | } 363 | $result = Tools::httpGet(self::API_URL_PREFIX . self::MEDIA_FOREVER_COUNT_URL . "access_token={$this->access_token}"); 364 | if ($result) { 365 | $json = json_decode($result, true); 366 | if (empty($json) || !empty($json['errcode'])) { 367 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 368 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 369 | return $this->checkRetry(__FUNCTION__, func_get_args()); 370 | } 371 | return $json; 372 | } 373 | return false; 374 | } 375 | 376 | /** 377 | * 上传图文消息素材,用于群发(认证后的订阅号可用) 378 | * @param array $data 消息结构{"articles":[{...}]} 379 | * @return bool|array 380 | */ 381 | public function uploadArticles($data) 382 | { 383 | if (!$this->access_token && !$this->getAccessToken()) { 384 | return false; 385 | } 386 | $result = Tools::httpPost(self::API_URL_PREFIX . self::MEDIA_UPLOADNEWS_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 387 | if ($result) { 388 | $json = json_decode($result, true); 389 | if (empty($json) || !empty($json['errcode'])) { 390 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 391 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 392 | return $this->checkRetry(__FUNCTION__, func_get_args()); 393 | } 394 | return $json; 395 | } 396 | return false; 397 | } 398 | 399 | /** 400 | * 上传视频素材(认证后的订阅号可用) 401 | * @param array $data 消息结构 402 | * { 403 | * "media_id"=>"", //通过上传媒体接口得到的MediaId 404 | * "title"=>"TITLE", //视频标题 405 | * "description"=>"Description" //视频描述 406 | * } 407 | * @return bool|array 408 | * { 409 | * "type":"video", 410 | * "media_id":"mediaid", 411 | * "created_at":1398848981 412 | * } 413 | */ 414 | public function uploadMpVideo($data) 415 | { 416 | if (!$this->access_token && !$this->getAccessToken()) { 417 | return false; 418 | } 419 | $result = Tools::httpPost(self::API_URL_PREFIX . self::MEDIA_VIDEO_UPLOAD . "access_token={$this->access_token}", Tools::json_encode($data)); 420 | if ($result) { 421 | $json = json_decode($result, true); 422 | if (empty($json) || !empty($json['errcode'])) { 423 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 424 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 425 | return $this->checkRetry(__FUNCTION__, func_get_args()); 426 | } 427 | return $json; 428 | } 429 | return false; 430 | } 431 | 432 | } 433 | -------------------------------------------------------------------------------- /Wechat/WechatMenu.php: -------------------------------------------------------------------------------- 1 | 26 | * @date 2016/06/28 11:52 27 | */ 28 | class WechatMenu extends Common 29 | { 30 | 31 | /** 创建自定义菜单 */ 32 | const MENU_ADD_URL = '/menu/create?'; 33 | /* 获取自定义菜单 */ 34 | const MENU_GET_URL = '/menu/get?'; 35 | /* 删除自定义菜单 */ 36 | const MENU_DEL_URL = '/menu/delete?'; 37 | 38 | /** 添加个性菜单 */ 39 | const COND_MENU_ADD_URL = '/menu/addconditional?'; 40 | /* 删除个性菜单 */ 41 | const COND_MENU_DEL_URL = '/menu/delconditional?'; 42 | /* 测试个性菜单 */ 43 | const COND_MENU_TRY_URL = '/menu/trymatch?'; 44 | 45 | /** 46 | * 创建自定义菜单 47 | * @param array $data 菜单数组数据 48 | * @return bool 49 | * @link https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013&token=&lang=zh_CN 文档 50 | */ 51 | public function createMenu($data) 52 | { 53 | if (!$this->access_token && !$this->getAccessToken()) { 54 | return false; 55 | } 56 | $result = Tools::httpPost(self::API_URL_PREFIX . self::MENU_ADD_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 57 | if ($result) { 58 | $json = json_decode($result, true); 59 | if (empty($json) || !empty($json['errcode'])) { 60 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 61 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 62 | return $this->checkRetry(__FUNCTION__, func_get_args()); 63 | } 64 | return true; 65 | } 66 | return false; 67 | } 68 | 69 | /** 70 | * 获取所有菜单 71 | * @return bool|array 72 | */ 73 | public function getMenu() 74 | { 75 | if (!$this->access_token && !$this->getAccessToken()) { 76 | return false; 77 | } 78 | $result = Tools::httpGet(self::API_URL_PREFIX . self::MENU_GET_URL . "access_token={$this->access_token}"); 79 | if ($result) { 80 | $json = json_decode($result, true); 81 | if (empty($json) || !empty($json['errcode'])) { 82 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 83 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 84 | return $this->checkRetry(__FUNCTION__, func_get_args()); 85 | } 86 | return $json; 87 | } 88 | return false; 89 | } 90 | 91 | /** 92 | * 删除所有菜单 93 | * @return bool 94 | */ 95 | public function deleteMenu() 96 | { 97 | if (!$this->access_token && !$this->getAccessToken()) { 98 | return false; 99 | } 100 | $result = Tools::httpGet(self::API_URL_PREFIX . self::MENU_DEL_URL . "access_token={$this->access_token}"); 101 | if ($result) { 102 | $json = json_decode($result, true); 103 | if (empty($json) || !empty($json['errcode'])) { 104 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 105 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 106 | return $this->checkRetry(__FUNCTION__, func_get_args()); 107 | } 108 | return true; 109 | } 110 | return false; 111 | } 112 | 113 | /** 114 | * 创建个性菜单 115 | * @param array $data 菜单数组数据 116 | * @return bool|string 117 | * @link https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN 文档 118 | */ 119 | public function createCondMenu($data) 120 | { 121 | if (!$this->access_token && !$this->getAccessToken()) { 122 | return false; 123 | } 124 | $result = Tools::httpPost(self::API_URL_PREFIX . self::COND_MENU_ADD_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 125 | if ($result) { 126 | $json = json_decode($result, true); 127 | if (empty($json) || !empty($json['errcode'])) { 128 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 129 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 130 | return $this->checkRetry(__FUNCTION__, func_get_args()); 131 | } 132 | return $json['menuid']; 133 | } 134 | return false; 135 | } 136 | 137 | /** 138 | * 删除个性菜单 139 | * @param string $menuid 菜单ID 140 | * @return bool 141 | */ 142 | public function deleteCondMenu($menuid) 143 | { 144 | if (!$this->access_token && !$this->getAccessToken()) { 145 | return false; 146 | } 147 | $data = array('menuid' => $menuid); 148 | $result = Tools::httpPost(self::API_URL_PREFIX . self::COND_MENU_DEL_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 149 | if ($result) { 150 | $json = json_decode($result, true); 151 | if (empty($json) || !empty($json['errcode'])) { 152 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 153 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 154 | return $this->checkRetry(__FUNCTION__, func_get_args()); 155 | } 156 | return true; 157 | } 158 | return false; 159 | } 160 | 161 | /** 162 | * 测试并返回个性化菜单 163 | * @param string $openid 粉丝openid 164 | * @return bool 165 | */ 166 | public function tryCondMenu($openid) 167 | { 168 | if (!$this->access_token && !$this->getAccessToken()) { 169 | return false; 170 | } 171 | $data = array('user_id' => $openid); 172 | $result = Tools::httpPost(self::API_URL_PREFIX . self::COND_MENU_TRY_URL . "access_token={$this->access_token}", Tools::json_encode($data)); 173 | if ($result) { 174 | $json = json_decode($result, true); 175 | if (empty($json) || !empty($json['errcode'])) { 176 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 177 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 178 | return $this->checkRetry(__FUNCTION__, func_get_args()); 179 | } 180 | return $json; 181 | } 182 | return false; 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /Wechat/WechatMessage.php: -------------------------------------------------------------------------------- 1 | 26 | * @date 2016/06/28 11:29 27 | */ 28 | class WechatMessage extends Common 29 | { 30 | 31 | /** 32 | * 获取模板列表 33 | * @return bool|array 34 | */ 35 | public function getAllPrivateTemplate() 36 | { 37 | if (!$this->access_token && !$this->getAccessToken()) { 38 | return false; 39 | } 40 | $result = Tools::httpPost(self::API_URL_PREFIX . "/template/get_all_private_template?access_token={$this->access_token}", []); 41 | if ($result) { 42 | $json = json_decode($result, true); 43 | if (empty($json) || !empty($json['errcode'])) { 44 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 45 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 46 | return $this->checkRetry(__FUNCTION__, func_get_args()); 47 | } 48 | return $json; 49 | } 50 | return false; 51 | } 52 | 53 | /** 54 | * 获取设置的行业信息 55 | * @return bool|array 56 | */ 57 | public function getTMIndustry() 58 | { 59 | if (!$this->access_token && !$this->getAccessToken()) { 60 | return false; 61 | } 62 | $result = Tools::httpPost(self::API_URL_PREFIX . "/template/get_industry?access_token={$this->access_token}", []); 63 | if ($result) { 64 | $json = json_decode($result, true); 65 | if (empty($json) || !empty($json['errcode'])) { 66 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 67 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 68 | return $this->checkRetry(__FUNCTION__, func_get_args()); 69 | } 70 | return $json; 71 | } 72 | return false; 73 | } 74 | 75 | /** 76 | * 删除模板消息 77 | * @param string $tpl_id 78 | * @return bool 79 | */ 80 | public function delPrivateTemplate($tpl_id) 81 | { 82 | if (!$this->access_token && !$this->getAccessToken()) { 83 | return false; 84 | } 85 | $data = array('template_id' => $tpl_id); 86 | $result = Tools::httpPost(self::API_URL_PREFIX . "/template/del_private_template?access_token={$this->access_token}", Tools::json_encode($data)); 87 | if ($result) { 88 | $json = json_decode($result, true); 89 | if (empty($json) || !empty($json['errcode'])) { 90 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 91 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 92 | return $this->checkRetry(__FUNCTION__, func_get_args()); 93 | } 94 | return true; 95 | } 96 | return false; 97 | } 98 | 99 | /** 100 | * 模板消息 设置所属行业 101 | * @param string $id1 公众号模板消息所属行业编号,参看官方开发文档 行业代码 102 | * @param string $id2 同$id1。但如果只有一个行业,此参数可省略 103 | * @return bool|mixed 104 | */ 105 | public function setTMIndustry($id1, $id2 = '') 106 | { 107 | if (!$this->access_token && !$this->getAccessToken()) { 108 | return false; 109 | } 110 | $data = array(); 111 | !empty($id1) && $data['industry_id1'] = $id1; 112 | !empty($id2) && $data['industry_id2'] = $id2; 113 | $json = Tools::json_encode($data); 114 | $result = Tools::httpPost(self::API_URL_PREFIX . "/template/api_set_industry?access_token={$this->access_token}", $json); 115 | if ($result) { 116 | $json = json_decode($result, true); 117 | if (empty($json) || !empty($json['errcode'])) { 118 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 119 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 120 | return $this->checkRetry(__FUNCTION__, func_get_args()); 121 | } 122 | return $json; 123 | } 124 | return false; 125 | } 126 | 127 | /** 128 | * 模板消息 添加消息模板 129 | * 成功返回消息模板的调用id 130 | * @param string $tpl_id 模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式 131 | * @return bool|string 132 | */ 133 | public function addTemplateMessage($tpl_id) 134 | { 135 | if (!$this->access_token && !$this->getAccessToken()) { 136 | return false; 137 | } 138 | $data = Tools::json_encode(array('template_id_short' => $tpl_id)); 139 | $result = Tools::httpPost(self::API_URL_PREFIX . "/template/api_add_template?access_token={$this->access_token}", $data); 140 | if ($result) { 141 | $json = json_decode($result, true); 142 | if (empty($json) || !empty($json['errcode'])) { 143 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 144 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 145 | return $this->checkRetry(__FUNCTION__, func_get_args()); 146 | } 147 | return $json['template_id']; 148 | } 149 | return false; 150 | } 151 | 152 | /** 153 | * 发送模板消息 154 | * @param array $data 消息结构 155 | * { 156 | * "touser":"OPENID", 157 | * "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY", 158 | * "url":"http://weixin.qq.com/download", 159 | * "topcolor":"#FF0000", 160 | * "data":{ 161 | * "参数名1": { 162 | * "value":"参数", 163 | * "color":"#173177" //参数颜色 164 | * }, 165 | * "Date":{ 166 | * "value":"06月07日 19时24分", 167 | * "color":"#173177" 168 | * }, 169 | * "CardNumber":{ 170 | * "value":"0426", 171 | * "color":"#173177" 172 | * }, 173 | * "Type":{ 174 | * "value":"消费", 175 | * "color":"#173177" 176 | * } 177 | * } 178 | * } 179 | * @return bool|array 180 | */ 181 | public function sendTemplateMessage($data) 182 | { 183 | if (!$this->access_token && !$this->getAccessToken()) { 184 | return false; 185 | } 186 | $result = Tools::httpPost(self::API_URL_PREFIX . "/message/template/send?access_token={$this->access_token}", Tools::json_encode($data)); 187 | if ($result) { 188 | $json = json_decode($result, true); 189 | if (empty($json) || !empty($json['errcode'])) { 190 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 191 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 192 | return $this->checkRetry(__FUNCTION__, func_get_args()); 193 | } 194 | return $json; 195 | } 196 | return false; 197 | } 198 | 199 | /** 200 | * 根据标签进行群发 ( 订阅号与服务号认证后均可用 ) 201 | * @param array $data 消息结构 202 | * 注意: 视频需要在调用uploadMedia()方法后,再使用 uploadMpVideo() 方法生成, 203 | * 然后获得的 mediaid 才能用于群发,且消息类型为 mpvideo 类型。 204 | * @return bool|array 205 | * { 206 | * "touser"=>array( 207 | * "OPENID1", 208 | * "OPENID2" 209 | * ), 210 | * "msgtype"=>"mpvideo", 211 | * // 在下面5种类型中选择对应的参数内容 212 | * // mpnews | voice | image | mpvideo => array( "media_id"=>"MediaId") 213 | * // text => array ( "content" => "hello") 214 | * } 215 | */ 216 | public function sendMassMessage($data) 217 | { 218 | if (!$this->access_token && !$this->getAccessToken()) { 219 | return false; 220 | } 221 | $result = Tools::httpPost(self::API_URL_PREFIX . "/message/mass/send?access_token={$this->access_token}", Tools::json_encode($data)); 222 | if ($result) { 223 | $json = json_decode($result, true); 224 | if (empty($json) || !empty($json['errcode'])) { 225 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 226 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 227 | return $this->checkRetry(__FUNCTION__, func_get_args()); 228 | } 229 | return $json; 230 | } 231 | return false; 232 | } 233 | 234 | /** 235 | * 根据标签进行群发 ( 订阅号与服务号认证后均可用 ) 236 | * @param array $data 消息结构 237 | * 注意:视频需要在调用uploadMedia()方法后,再使用 uploadMpVideo() 方法生成, 238 | * 然后获得的 mediaid 才能用于群发,且消息类型为 mpvideo 类型。 239 | * @return bool|array 240 | * { 241 | * "filter"=>array( 242 | * "is_to_all"=>False, //是否群发给所有用户.True不用分组id,False需填写分组id 243 | * "group_id"=>"2" //群发的分组id 244 | * ), 245 | * "msgtype"=>"mpvideo", 246 | * // 在下面5种类型中选择对应的参数内容 247 | * // mpnews | voice | image | mpvideo => array( "media_id"=>"MediaId") 248 | * // text => array ( "content" => "hello") 249 | * } 250 | */ 251 | public function sendGroupMassMessage($data) 252 | { 253 | if (!$this->access_token && !$this->getAccessToken()) { 254 | return false; 255 | } 256 | $result = Tools::httpPost(self::API_URL_PREFIX . "/message/mass/sendall?access_token={$this->access_token}", Tools::json_encode($data)); 257 | if ($result) { 258 | $json = json_decode($result, true); 259 | if (empty($json) || !empty($json['errcode'])) { 260 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 261 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 262 | return $this->checkRetry(__FUNCTION__, func_get_args()); 263 | } 264 | return $json; 265 | } 266 | return false; 267 | } 268 | 269 | /** 270 | * 删除群发图文消息 ( 订阅号与服务号认证后均可用 ) 271 | * @param string $msg_id 消息ID 272 | * @return bool 273 | */ 274 | public function deleteMassMessage($msg_id) 275 | { 276 | if (!$this->access_token && !$this->getAccessToken()) { 277 | return false; 278 | } 279 | $data = Tools::json_encode(array('msg_id' => $msg_id)); 280 | $result = Tools::httpPost(self::API_URL_PREFIX . "/message/mass/delete?access_token={$this->access_token}", $data); 281 | if ($result) { 282 | $json = json_decode($result, true); 283 | if (empty($json) || !empty($json['errcode'])) { 284 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 285 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 286 | return $this->checkRetry(__FUNCTION__, func_get_args()); 287 | } 288 | return true; 289 | } 290 | return false; 291 | } 292 | 293 | /** 294 | * 预览群发消息 ( 订阅号与服务号认证后均可用 ) 295 | * @param array $data 296 | * 注意: 视频需要在调用uploadMedia()方法后,再使用 uploadMpVideo() 方法生成, 297 | * 然后获得的 mediaid 才能用于群发,且消息类型为 mpvideo 类型。 298 | * @消息结构 299 | * { 300 | * "touser"=>"OPENID", 301 | * "msgtype"=>"mpvideo", 302 | * // 在下面5种类型中选择对应的参数内容 303 | * // mpnews | voice | image | mpvideo => array( "media_id"=>"MediaId") 304 | * // text => array ( "content" => "hello") 305 | * } 306 | * @return bool|array 307 | */ 308 | public function previewMassMessage($data) 309 | { 310 | if (!$this->access_token && !$this->getAccessToken()) { 311 | return false; 312 | } 313 | $result = Tools::httpPost(self::API_URL_PREFIX . "/message/mass/preview?access_token={$this->access_token}", Tools::json_encode($data)); 314 | if ($result) { 315 | $json = json_decode($result, true); 316 | if (empty($json) || !empty($json['errcode'])) { 317 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 318 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 319 | return $this->checkRetry(__FUNCTION__, func_get_args()); 320 | } 321 | return $json; 322 | } 323 | return false; 324 | } 325 | 326 | /** 327 | * 查询群发消息发送状态 ( 订阅号与服务号认证后均可用 ) 328 | * @param string $msg_id 消息ID 329 | * @return bool|array 330 | * { 331 | * "msg_id":201053012, //群发消息后返回的消息id 332 | * "msg_status":"SEND_SUCCESS", //消息发送后的状态,SENDING表示正在发送 SEND_SUCCESS表示发送成功 333 | * } 334 | */ 335 | public function queryMassMessage($msg_id) 336 | { 337 | if (!$this->access_token && !$this->getAccessToken()) { 338 | return false; 339 | } 340 | $data = Tools::json_encode(array('msg_id' => $msg_id)); 341 | $result = Tools::httpPost(self::API_URL_PREFIX . "/message/mass/get?access_token={$this->access_token}", $data); 342 | if ($result) { 343 | $json = json_decode($result, true); 344 | if (empty($json) || !empty($json['errcode'])) { 345 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 346 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 347 | return $this->checkRetry(__FUNCTION__, func_get_args()); 348 | } 349 | return $json; 350 | } 351 | return false; 352 | } 353 | 354 | } 355 | -------------------------------------------------------------------------------- /Wechat/WechatOauth.php: -------------------------------------------------------------------------------- 1 | appid}&redirect_uri={$redirect_uri}&response_type=code&scope={$scope}&state={$state}#wechat_redirect"; 46 | } 47 | 48 | /** 49 | * 通过 code 获取 AccessToken 和 openid 50 | * @return bool|array 51 | */ 52 | public function getOauthAccessToken() 53 | { 54 | $code = isset($_GET['code']) ? $_GET['code'] : ''; 55 | if (empty($code)) { 56 | Tools::log("getOauthAccessToken Fail, Because there is no access to the code value in get.", "MSG - {$this->appid}"); 57 | return false; 58 | } 59 | $result = Tools::httpGet(self::API_BASE_URL_PREFIX . self::OAUTH_TOKEN_URL . "appid={$this->appid}&secret={$this->appsecret}&code={$code}&grant_type=authorization_code"); 60 | if ($result) { 61 | $json = json_decode($result, true); 62 | if (empty($json) || !empty($json['errcode'])) { 63 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 64 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 65 | Tools::log("WechatOauth::getOauthAccessToken Fail.{$this->errMsg} [{$this->errCode}]", "ERR - {$this->appid}"); 66 | return false; 67 | } 68 | return $json; 69 | } 70 | return false; 71 | } 72 | 73 | /** 74 | * 刷新access token并续期 75 | * @param string $refresh_token 76 | * @return bool|array 77 | */ 78 | public function getOauthRefreshToken($refresh_token) 79 | { 80 | $result = Tools::httpGet(self::API_BASE_URL_PREFIX . self::OAUTH_REFRESH_URL . "appid={$this->appid}&grant_type=refresh_token&refresh_token={$refresh_token}"); 81 | if ($result) { 82 | $json = json_decode($result, true); 83 | if (empty($json) || !empty($json['errcode'])) { 84 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 85 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 86 | Tools::log("WechatOauth::getOauthRefreshToken Fail.{$this->errMsg} [{$this->errCode}]", "ERR - {$this->appid}"); 87 | return false; 88 | } 89 | return $json; 90 | } 91 | return false; 92 | } 93 | 94 | /** 95 | * 获取授权后的用户资料 96 | * @param string $access_token 97 | * @param string $openid 98 | * @return bool|array {openid,nickname,sex,province,city,country,headimgurl,privilege,[unionid]} 99 | * 注意:unionid字段 只有在用户将公众号绑定到微信开放平台账号后,才会出现。建议调用前用isset()检测一下 100 | */ 101 | public function getOauthUserInfo($access_token, $openid) 102 | { 103 | $result = Tools::httpGet(self::API_BASE_URL_PREFIX . self::OAUTH_USERINFO_URL . "access_token={$access_token}&openid={$openid}"); 104 | if ($result) { 105 | $json = json_decode($result, true); 106 | if (empty($json) || !empty($json['errcode'])) { 107 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 108 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 109 | Tools::log("WechatOauth::getOauthUserInfo Fail.{$this->errMsg} [{$this->errCode}]", "ERR - {$this->appid}"); 110 | return false; 111 | } 112 | return $json; 113 | } 114 | return false; 115 | } 116 | 117 | /** 118 | * 检验授权凭证是否有效 119 | * @param string $access_token 120 | * @param string $openid 121 | * @return bool 是否有效 122 | */ 123 | public function getOauthAuth($access_token, $openid) 124 | { 125 | $result = Tools::httpGet(self::API_BASE_URL_PREFIX . self::OAUTH_AUTH_URL . "access_token={$access_token}&openid={$openid}"); 126 | if ($result) { 127 | $json = json_decode($result, true); 128 | if (empty($json) || !empty($json['errcode'])) { 129 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 130 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 131 | Tools::log("WechatOauth::getOauthAuth Fail.{$this->errMsg} [{$this->errCode}]", "ERR - {$this->appid}"); 132 | return false; 133 | } elseif (intval($json['errcode']) === 0) { 134 | return true; 135 | } 136 | } 137 | return false; 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /Wechat/WechatPay.php: -------------------------------------------------------------------------------- 1 | 24 | * @date 2015/05/13 12:12:00 25 | */ 26 | class WechatPay 27 | { 28 | 29 | /** 支付接口基础地址 */ 30 | const MCH_BASE_URL = 'https://api.mch.weixin.qq.com'; 31 | 32 | /** 公众号appid */ 33 | public $appid; 34 | 35 | /** 公众号配置 */ 36 | public $config; 37 | 38 | /** 商户身份ID */ 39 | public $mch_id; 40 | 41 | /** 商户支付密钥Key */ 42 | public $partnerKey; 43 | 44 | /** 证书路径 */ 45 | public $ssl_cer; 46 | public $ssl_key; 47 | 48 | /** 执行错误消息及代码 */ 49 | public $errMsg; 50 | public $errCode; 51 | 52 | /** 53 | * WechatPay constructor. 54 | * @param array $options 55 | */ 56 | public function __construct($options = array()) 57 | { 58 | $this->config = Loader::config($options); 59 | $this->appid = isset($this->config['appid']) ? $this->config['appid'] : ''; 60 | $this->mch_id = isset($this->config['mch_id']) ? $this->config['mch_id'] : ''; 61 | $this->partnerKey = isset($this->config['partnerkey']) ? $this->config['partnerkey'] : ''; 62 | $this->ssl_cer = isset($this->config['ssl_cer']) ? $this->config['ssl_cer'] : ''; 63 | $this->ssl_key = isset($this->config['ssl_key']) ? $this->config['ssl_key'] : ''; 64 | } 65 | 66 | /** 67 | * 获取当前错误内容 68 | * @return string 69 | */ 70 | public function getError() 71 | { 72 | return $this->errMsg; 73 | } 74 | 75 | /** 76 | * 当前当前错误代码 77 | * @return int 78 | */ 79 | public function getErrorCode() 80 | { 81 | return $this->errCode; 82 | } 83 | 84 | /** 85 | * 获取当前操作公众号APPID 86 | * @return string 87 | */ 88 | public function getAppid() 89 | { 90 | return $this->appid; 91 | } 92 | 93 | /** 94 | * 获取SDK配置参数 95 | * @return array 96 | */ 97 | public function getConfig() 98 | { 99 | return $this->config; 100 | } 101 | 102 | /** 103 | * 设置标配的请求参数,生成签名,生成接口参数xml 104 | * @param array $data 105 | * @return string 106 | */ 107 | protected function createXml($data) 108 | { 109 | if (!isset($data['wxappid']) && !isset($data['mch_appid']) && !isset($data['appid'])) { 110 | $data['appid'] = $this->appid; 111 | } 112 | if (!isset($data['mchid']) && !isset($data['mch_id'])) { 113 | $data['mch_id'] = $this->mch_id; 114 | } 115 | isset($data['nonce_str']) || $data['nonce_str'] = Tools::createNoncestr(); 116 | $data["sign"] = Tools::getPaySign($data, $this->partnerKey); 117 | return Tools::arr2xml($data); 118 | } 119 | 120 | /** 121 | * POST提交XML 122 | * @param array $data 123 | * @param string $url 124 | * @return mixed 125 | */ 126 | public function postXml($data, $url) 127 | { 128 | return Tools::httpPost($url, $this->createXml($data)); 129 | } 130 | 131 | /** 132 | * 使用证书post请求XML 133 | * @param array $data 134 | * @param string $url 135 | * @return mixed 136 | */ 137 | function postXmlSSL($data, $url) 138 | { 139 | return Tools::httpsPost($url, $this->createXml($data), $this->ssl_cer, $this->ssl_key); 140 | } 141 | 142 | /** 143 | * POST提交获取Array结果 144 | * @param array $data 需要提交的数据 145 | * @param string $url 146 | * @param string $method 147 | * @return array 148 | */ 149 | public function getArrayResult($data, $url, $method = 'postXml') 150 | { 151 | return Tools::xml2arr($this->$method($data, $url)); 152 | } 153 | 154 | /** 155 | * 解析返回的结果 156 | * @param array $result 157 | * @return bool|array 158 | */ 159 | protected function _parseResult($result) 160 | { 161 | if (empty($result)) { 162 | $this->errCode = 'result error'; 163 | $this->errMsg = '解析返回结果失败'; 164 | return false; 165 | } 166 | if ($result['return_code'] !== 'SUCCESS') { 167 | $this->errCode = $result['return_code']; 168 | $this->errMsg = $result['return_msg']; 169 | return false; 170 | } 171 | if (isset($result['err_code']) && $result['err_code'] !== 'SUCCESS') { 172 | $this->errMsg = $result['err_code_des']; 173 | $this->errCode = $result['err_code']; 174 | return false; 175 | } 176 | return $result; 177 | } 178 | 179 | /** 180 | * 创建刷卡支付参数包 181 | * @param string $auth_code 授权Code号 182 | * @param string $out_trade_no 商户订单号 183 | * @param int $total_fee 支付费用 184 | * @param string $body 订单标识 185 | * @param null $goods_tag 商品标签 186 | * @return array|bool 187 | */ 188 | public function createMicroPay($auth_code, $out_trade_no, $total_fee, $body, $goods_tag = null) 189 | { 190 | $data = array( 191 | "appid" => $this->appid, 192 | "mch_id" => $this->mch_id, 193 | "body" => $body, 194 | "out_trade_no" => $out_trade_no, 195 | "total_fee" => $total_fee, 196 | "auth_code" => $auth_code, 197 | "spbill_create_ip" => Tools::getAddress(), 198 | ); 199 | empty($goods_tag) || $data['goods_tag'] = $goods_tag; 200 | $json = Tools::xml2arr($this->postXml($data, self::MCH_BASE_URL . '/pay/micropay')); 201 | if (!empty($json) && false === $this->_parseResult($json)) { 202 | return false; 203 | } 204 | return $json; 205 | } 206 | 207 | /** 208 | * 支付通知验证处理 209 | * @return bool|array 210 | */ 211 | public function getNotify() 212 | { 213 | $disableEntities = libxml_disable_entity_loader(true); 214 | $notifyInfo = (array)simplexml_load_string(file_get_contents("php://input"), 'SimpleXMLElement', LIBXML_NOCDATA); 215 | libxml_disable_entity_loader($disableEntities); 216 | if (empty($notifyInfo)) { 217 | Tools::log('Payment notification forbidden access.', "ERR - {$this->appid}"); 218 | $this->errCode = '404'; 219 | $this->errMsg = 'Payment notification forbidden access.'; 220 | return false; 221 | } 222 | if (empty($notifyInfo['sign'])) { 223 | Tools::log('Payment notification signature is missing.' . var_export($notifyInfo, true), "ERR - {$this->appid}"); 224 | $this->errCode = '403'; 225 | $this->errMsg = 'Payment notification signature is missing.'; 226 | return false; 227 | } 228 | $data = $notifyInfo; 229 | unset($data['sign']); 230 | if ($notifyInfo['sign'] !== Tools::getPaySign($data, $this->partnerKey)) { 231 | Tools::log('Payment notification signature verification failed.' . var_export($notifyInfo, true), "ERR - {$this->appid}"); 232 | $this->errCode = '403'; 233 | $this->errMsg = 'Payment signature verification failed.'; 234 | return false; 235 | } 236 | Tools::log('Payment notification signature verification success.' . var_export($notifyInfo, true), "MSG - {$this->appid}"); 237 | $this->errCode = '0'; 238 | $this->errMsg = ''; 239 | return $notifyInfo; 240 | } 241 | 242 | 243 | /** 244 | * 支付XML统一回复 245 | * @param array $data 需要回复的XML内容数组 246 | * @param bool $isReturn 是否返回XML内容,默认不返回 247 | * @return string 248 | */ 249 | public function replyXml(array $data, $isReturn = false) 250 | { 251 | $xml = Tools::arr2xml($data); 252 | if ($isReturn) { 253 | return $xml; 254 | } 255 | ob_clean(); 256 | exit($xml); 257 | } 258 | 259 | /** 260 | * 获取预支付ID 261 | * @param string $openid 用户openid,JSAPI必填 262 | * @param string $body 商品标题 263 | * @param string $out_trade_no 第三方订单号 264 | * @param int $total_fee 订单总价 265 | * @param string $notify_url 支付成功回调地址 266 | * @param string $trade_type 支付类型JSAPI|NATIVE|APP 267 | * @param string $goods_tag 商品标记,代金券或立减优惠功能的参数 268 | * @param string $fee_type 交易币种 269 | * @param null $no_credit 是否禁止信用 270 | * @return bool|string 271 | */ 272 | public function getPrepayId($openid, $body, $out_trade_no, $total_fee, $notify_url, $trade_type = "JSAPI", $goods_tag = null, $fee_type = 'CNY', $no_credit = null) 273 | { 274 | $postdata = array( 275 | "body" => $body, 276 | "out_trade_no" => $out_trade_no, 277 | "fee_type" => $fee_type, 278 | "total_fee" => $total_fee, 279 | "notify_url" => $notify_url, 280 | "trade_type" => $trade_type, 281 | "spbill_create_ip" => Tools::getAddress(), 282 | ); 283 | empty($openid) || $postdata['openid'] = $openid; 284 | empty($goods_tag) || $postdata['goods_tag'] = $goods_tag; 285 | is_null($no_credit) || $postdata['no_credit'] = $no_credit; 286 | $result = $this->getArrayResult($postdata, self::MCH_BASE_URL . '/pay/unifiedorder'); 287 | if (false === $this->_parseResult($result)) { 288 | return false; 289 | } 290 | return in_array($trade_type, array('JSAPI', 'APP')) ? $result['prepay_id'] : ($trade_type === 'MWEB' ? $result['mweb_url'] : $result['code_url']); 291 | } 292 | 293 | /** 294 | * 获取二维码预支付ID 295 | * @param string $openid 用户openid,JSAPI必填 296 | * @param string $body 商品标题 297 | * @param string $out_trade_no 第三方订单号 298 | * @param int $total_fee 订单总价 299 | * @param string $notify_url 支付成功回调地址 300 | * @param string $goods_tag 商品标记,代金券或立减优惠功能的参数 301 | * @param string $fee_type 交易币种 302 | * @return bool|string 303 | */ 304 | public function getQrcPrepayId($openid, $body, $out_trade_no, $total_fee, $notify_url, $goods_tag = null, $fee_type = 'CNY') 305 | { 306 | $postdata = array( 307 | "body" => $body, 308 | "out_trade_no" => $out_trade_no, 309 | "fee_type" => $fee_type, 310 | "total_fee" => $total_fee, 311 | "notify_url" => $notify_url, 312 | "trade_type" => 'NATIVE', 313 | "spbill_create_ip" => Tools::getAddress(), 314 | ); 315 | empty($goods_tag) || $postdata['goods_tag'] = $goods_tag; 316 | empty($openid) || $postdata['openid'] = $openid; 317 | $result = $this->getArrayResult($postdata, self::MCH_BASE_URL . '/pay/unifiedorder'); 318 | if (false === $this->_parseResult($result) || empty($result['prepay_id'])) { 319 | return false; 320 | } 321 | return $result['prepay_id']; 322 | } 323 | 324 | /** 325 | * 获取支付规二维码 326 | * @param string $product_id 商户定义的商品id 或者订单号 327 | * @return string 328 | */ 329 | public function getQrcPayUrl($product_id) 330 | { 331 | $data = array( 332 | 'appid' => $this->appid, 333 | 'mch_id' => $this->mch_id, 334 | 'time_stamp' => (string)time(), 335 | 'nonce_str' => Tools::createNoncestr(), 336 | 'product_id' => (string)$product_id, 337 | ); 338 | $data['sign'] = Tools::getPaySign($data, $this->partnerKey); 339 | return "weixin://wxpay/bizpayurl?" . http_build_query($data); 340 | } 341 | 342 | 343 | /** 344 | * 创建JSAPI支付参数包 345 | * @param string $prepay_id 346 | * @return array 347 | */ 348 | public function createMchPay($prepay_id) 349 | { 350 | $option = array(); 351 | $option["appId"] = $this->appid; 352 | $option["timeStamp"] = (string)time(); 353 | $option["nonceStr"] = Tools::createNoncestr(); 354 | $option["package"] = "prepay_id={$prepay_id}"; 355 | $option["signType"] = "MD5"; 356 | $option["paySign"] = Tools::getPaySign($option, $this->partnerKey); 357 | $option['timestamp'] = $option['timeStamp']; 358 | return $option; 359 | } 360 | 361 | /** 362 | * 关闭订单 363 | * @param string $out_trade_no 364 | * @return bool 365 | */ 366 | public function closeOrder($out_trade_no) 367 | { 368 | $data = array('out_trade_no' => $out_trade_no); 369 | $result = $this->getArrayResult($data, self::MCH_BASE_URL . '/pay/closeorder'); 370 | if (false === $this->_parseResult($result)) { 371 | return false; 372 | } 373 | return ($result['return_code'] === 'SUCCESS'); 374 | } 375 | 376 | /** 377 | * 查询订单详情 378 | * @param $out_trade_no 379 | * @return bool|array 380 | */ 381 | public function queryOrder($out_trade_no) 382 | { 383 | $data = array('out_trade_no' => $out_trade_no); 384 | $result = $this->getArrayResult($data, self::MCH_BASE_URL . '/pay/orderquery'); 385 | if (false === $this->_parseResult($result)) { 386 | return false; 387 | } 388 | return $result; 389 | } 390 | 391 | /** 392 | * 订单退款接口 393 | * @param string $out_trade_no 商户订单号 394 | * @param string $transaction_id 微信订单号,与 out_refund_no 二选一(不选时传0或false) 395 | * @param string $out_refund_no 商户退款订单号,与 transaction_id 二选一(不选时传0或false) 396 | * @param int $total_fee 商户订单总金额 397 | * @param int $refund_fee 退款金额,不可大于订单总金额 398 | * @param int|null $op_user_id 操作员ID,默认商户ID 399 | * @param string $refund_account 退款资金来源 400 | * 仅针对老资金流商户使用 401 | * REFUND_SOURCE_UNSETTLED_FUNDS --- 未结算资金退款(默认使用未结算资金退款) 402 | * REFUND_SOURCE_RECHARGE_FUNDS --- 可用余额退款 403 | * @param string $refund_desc 退款原因 404 | * @param string $refund_fee_type 退款货币种类 405 | * @return bool 406 | */ 407 | public function refund($out_trade_no, $transaction_id, $out_refund_no, $total_fee, $refund_fee, $op_user_id = null, $refund_account = '', $refund_desc = '', $refund_fee_type = 'CNY') 408 | { 409 | $data = array(); 410 | $data['out_trade_no'] = $out_trade_no; 411 | $data['total_fee'] = $total_fee; 412 | $data['refund_fee'] = $refund_fee; 413 | $data['refund_fee_type'] = $refund_fee_type; 414 | $data['op_user_id'] = empty($op_user_id) ? $this->mch_id : $op_user_id; 415 | !empty($out_refund_no) && $data['out_refund_no'] = $out_refund_no; 416 | !empty($transaction_id) && $data['transaction_id'] = $transaction_id; 417 | !empty($refund_account) && $data['refund_account'] = $refund_account; 418 | !empty($refund_desc) && $data['refund_desc'] = $refund_desc; 419 | $result = $this->getArrayResult($data, self::MCH_BASE_URL . '/secapi/pay/refund', 'postXmlSSL'); 420 | if (false === $this->_parseResult($result)) { 421 | return false; 422 | } 423 | return ($result['return_code'] === 'SUCCESS'); 424 | } 425 | 426 | /** 427 | * 退款查询接口 428 | * @param string $out_trade_no 429 | * @return bool|array 430 | */ 431 | public function refundQuery($out_trade_no) 432 | { 433 | $data = array(); 434 | $data['out_trade_no'] = $out_trade_no; 435 | $result = $this->getArrayResult($data, self::MCH_BASE_URL . '/pay/refundquery'); 436 | if (false === $this->_parseResult($result)) { 437 | return false; 438 | } 439 | return $result; 440 | } 441 | 442 | /** 443 | * 获取对账单 444 | * @param string $bill_date 账单日期,如 20141110 445 | * @param string $bill_type ALL|SUCCESS|REFUND|REVOKED 446 | * @return bool|array 447 | */ 448 | public function getBill($bill_date, $bill_type = 'ALL') 449 | { 450 | $data = array(); 451 | $data['bill_date'] = $bill_date; 452 | $data['bill_type'] = $bill_type; 453 | $result = $this->postXml($data, self::MCH_BASE_URL . '/pay/downloadbill'); 454 | $json = Tools::xml2arr($result); 455 | if (!empty($json) && false === $this->_parseResult($json)) { 456 | return false; 457 | } 458 | return $json; 459 | } 460 | 461 | /** 462 | * 发送现金红包 463 | * @param string $openid 红包接收者OPENID 464 | * @param int $total_amount 红包总金额 465 | * @param string $mch_billno 商户订单号 466 | * @param string $sendname 商户名称 467 | * @param string $wishing 红包祝福语 468 | * @param string $act_name 活动名称 469 | * @param string $remark 备注信息 470 | * @param null|int $total_num 红包发放总人数(大于1为裂变红包) 471 | * @param null|string $scene_id 场景id 472 | * @param string $risk_info 活动信息 473 | * @param null|string $consume_mch_id 资金授权商户号 474 | * @return array|bool 475 | * @link https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_5 476 | */ 477 | public function sendRedPack($openid, $total_amount, $mch_billno, $sendname, $wishing, $act_name, $remark, $total_num = 1, $scene_id = null, $risk_info = '', $consume_mch_id = null) 478 | { 479 | $data = array(); 480 | $data['mch_billno'] = $mch_billno; // 商户订单号 mch_id+yyyymmdd+10位一天内不能重复的数字 481 | $data['wxappid'] = $this->appid; 482 | $data['send_name'] = $sendname; //商户名称 483 | $data['re_openid'] = $openid; //红包接收者 484 | $data['total_amount'] = $total_amount; //红包总金额 485 | $data['total_num'] = '1'; //发放人数据 486 | $data['wishing'] = $wishing; //红包祝福语 487 | $data['client_ip'] = Tools::getAddress(); //调用接口的机器Ip地址 488 | $data['act_name'] = $act_name; //活动名称 489 | $data['remark'] = $remark; //备注信息 490 | $data['total_num'] = $total_num; 491 | !empty($scene_id) && $data['scene_id'] = $scene_id; 492 | !empty($risk_info) && $data['risk_info'] = $risk_info; 493 | !empty($consume_mch_id) && $data['consume_mch_id'] = $consume_mch_id; 494 | if ($total_num > 1) { 495 | $data['amt_type'] = 'ALL_RAND'; 496 | $api = self::MCH_BASE_URL . '/mmpaymkttransfers/sendgroupredpack'; 497 | } else { 498 | $api = self::MCH_BASE_URL . '/mmpaymkttransfers/sendredpack'; 499 | } 500 | $result = $this->postXmlSSL($data, $api); 501 | $json = Tools::xml2arr($result); 502 | if (!empty($json) && false === $this->_parseResult($json)) { 503 | return false; 504 | } 505 | return $json; 506 | } 507 | 508 | 509 | /** 510 | * 现金红包状态查询 511 | * @param string $billno 512 | * @return bool|array 513 | * @link https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_7&index=6 514 | */ 515 | public function queryRedPack($billno) 516 | { 517 | $data['mch_billno'] = $billno; 518 | $data['bill_type'] = 'MCHT'; 519 | $result = $this->postXmlSSL($data, self::MCH_BASE_URL . '/mmpaymkttransfers/gethbinfo'); 520 | $json = Tools::xml2arr($result); 521 | if (!empty($json) && false === $this->_parseResult($json)) { 522 | return false; 523 | } 524 | return $json; 525 | } 526 | 527 | /** 528 | * 企业付款 529 | * @param string $openid 红包接收者OPENID 530 | * @param int $amount 红包总金额 531 | * @param string $billno 商户订单号 532 | * @param string $desc 备注信息 533 | * @return bool|array 534 | * @link https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2 535 | */ 536 | public function transfers($openid, $amount, $billno, $desc) 537 | { 538 | $data = array(); 539 | $data['mchid'] = $this->mch_id; 540 | $data['mch_appid'] = $this->appid; 541 | $data['partner_trade_no'] = $billno; 542 | $data['openid'] = $openid; 543 | $data['amount'] = $amount; 544 | $data['check_name'] = 'NO_CHECK'; #不验证姓名 545 | $data['spbill_create_ip'] = Tools::getAddress(); //调用接口的机器Ip地址 546 | $data['desc'] = $desc; //备注信息 547 | $result = $this->postXmlSSL($data, self::MCH_BASE_URL . '/mmpaymkttransfers/promotion/transfers'); 548 | $json = Tools::xml2arr($result); 549 | if (!empty($json) && false === $this->_parseResult($json)) { 550 | return false; 551 | } 552 | return $json; 553 | } 554 | 555 | /** 556 | * 企业付款查询 557 | * @param string $billno 558 | * @return bool|array 559 | * @link https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_3 560 | */ 561 | public function queryTransfers($billno) 562 | { 563 | $data['appid'] = $this->appid; 564 | $data['mch_id'] = $this->mch_id; 565 | $data['partner_trade_no'] = $billno; 566 | $result = $this->postXmlSSL($data, self::MCH_BASE_URL . '/mmpaymkttransfers/gettransferinfo'); 567 | $json = Tools::xml2arr($result); 568 | if (!empty($json) && false === $this->_parseResult($json)) { 569 | return false; 570 | } 571 | return $json; 572 | } 573 | 574 | /** 575 | * 二维码链接转成短链接 576 | * @param string $url 需要处理的长链接 577 | * @return bool|string 578 | */ 579 | public function shortUrl($url) 580 | { 581 | $data = array(); 582 | $data['long_url'] = $url; 583 | $result = $this->getArrayResult($data, self::MCH_BASE_URL . '/tools/shorturl'); 584 | if (!$result || $result['return_code'] !== 'SUCCESS') { 585 | $this->errCode = $result['return_code']; 586 | $this->errMsg = $result['return_msg']; 587 | return false; 588 | } 589 | if (isset($result['err_code']) && $result['err_code'] !== 'SUCCESS') { 590 | $this->errMsg = $result['err_code_des']; 591 | $this->errCode = $result['err_code']; 592 | return false; 593 | } 594 | return $result['short_url']; 595 | } 596 | 597 | /** 598 | * 发放代金券 599 | * @param int $coupon_stock_id 代金券批次id 600 | * @param string $partner_trade_no 商户此次发放凭据号(格式:商户id+日期+流水号),商户侧需保持唯一性 601 | * @param string $openid Openid信息 602 | * @param string $op_user_id 操作员帐号, 默认为商户号 可在商户平台配置操作员对应的api权限 603 | * @return bool|array 604 | * @link https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_3 605 | */ 606 | public function sendCoupon($coupon_stock_id, $partner_trade_no, $openid, $op_user_id = null) 607 | { 608 | $data = array(); 609 | $data['appid'] = $this->appid; 610 | $data['coupon_stock_id'] = $coupon_stock_id; 611 | $data['openid_count'] = 1; 612 | $data['partner_trade_no'] = $partner_trade_no; 613 | $data['openid'] = $openid; 614 | $data['op_user_id'] = empty($op_user_id) ? $this->mch_id : $op_user_id; 615 | $result = $this->postXmlSSL($data, self::MCH_BASE_URL . '/mmpaymkttransfers/send_coupon'); 616 | $json = Tools::xml2arr($result); 617 | if (!empty($json) && false === $this->_parseResult($json)) { 618 | return false; 619 | } 620 | return $json; 621 | } 622 | } 623 | -------------------------------------------------------------------------------- /Wechat/WechatPoi.php: -------------------------------------------------------------------------------- 1 | 25 | * @date 2016/10/26 15:43 26 | */ 27 | class WechatPoi extends Common 28 | { 29 | 30 | /** 创建门店 */ 31 | const POI_ADD = '/cgi-bin/poi/addpoi?'; 32 | 33 | /** 查询门店信息 */ 34 | const POI_GET = '/cgi-bin/poi/getpoi?'; 35 | 36 | /** 获取门店列表 */ 37 | const POI_GET_LIST = '/cgi-bin/poi/getpoilist?'; 38 | 39 | /** 修改门店信息 */ 40 | const POI_UPDATE = '/cgi-bin/poi/updatepoi?'; 41 | 42 | /** 删除门店 */ 43 | const POI_DELETE = '/cgi-bin/poi/delpoi?'; 44 | 45 | /** 获取门店类目表 */ 46 | const POI_CATEGORY = '/cgi-bin/poi/getwxcategory?'; 47 | 48 | /** 49 | * 创建门店 50 | * @link https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444378120&token=&lang=zh_CN 51 | * @param array $data 52 | * @return bool 53 | */ 54 | public function addPoi($data) 55 | { 56 | if (!$this->access_token && !$this->getAccessToken()) { 57 | return false; 58 | } 59 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::POI_ADD . "access_token={$this->access_token}", Tools::json_encode($data)); 60 | if ($result) { 61 | $json = json_decode($result, true); 62 | if (empty($json) || !empty($json['errcode'])) { 63 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 64 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 65 | return $this->checkRetry(__FUNCTION__, func_get_args()); 66 | } 67 | return $json; 68 | } 69 | return false; 70 | } 71 | 72 | /** 73 | * 删除门店 74 | * @link https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444378120&token=&lang=zh_CN 75 | * @param string $poi_id JSON数据格式 76 | * @return bool|array 77 | */ 78 | public function delPoi($poi_id) 79 | { 80 | if (!$this->access_token && !$this->getAccessToken()) { 81 | return false; 82 | } 83 | $data = array('poi_id' => $poi_id); 84 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::POI_DELETE . "access_token={$this->access_token}", Tools::json_encode($data)); 85 | if ($result) { 86 | $json = json_decode($result, true); 87 | if (empty($json) || !empty($json['errcode'])) { 88 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 89 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 90 | return $this->checkRetry(__FUNCTION__, func_get_args()); 91 | } 92 | return $json; 93 | } 94 | return false; 95 | } 96 | 97 | /** 98 | * 修改门店服务信息 99 | * @link https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444378120&token=&lang=zh_CN 100 | * @param array $data 101 | * @return bool 102 | */ 103 | public function updatePoi($data) 104 | { 105 | if (!$this->access_token && !$this->getAccessToken()) { 106 | return false; 107 | } 108 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::POI_UPDATE . "access_token={$this->access_token}", Tools::json_encode($data)); 109 | if ($result) { 110 | $json = json_decode($result, true); 111 | if (empty($json) || !empty($json['errcode'])) { 112 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 113 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 114 | return $this->checkRetry(__FUNCTION__, func_get_args()); 115 | } 116 | return true; 117 | } 118 | return false; 119 | } 120 | 121 | /** 122 | * 查询门店信息 123 | * @link https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444378120&token=&lang=zh_CN 124 | * @param string $poi_id 125 | * @return bool 126 | */ 127 | public function getPoi($poi_id) 128 | { 129 | if (!$this->access_token && !$this->getAccessToken()) { 130 | return false; 131 | } 132 | $data = array('poi_id' => $poi_id); 133 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::POI_GET . "access_token={$this->access_token}", Tools::json_encode($data)); 134 | if ($result) { 135 | $json = json_decode($result, true); 136 | if (empty($json) || !empty($json['errcode'])) { 137 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 138 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 139 | return $this->checkRetry(__FUNCTION__, func_get_args()); 140 | } 141 | return $json; 142 | } 143 | return false; 144 | } 145 | 146 | /** 147 | * 查询门店列表 148 | * @link https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444378120&token=&lang=zh_CN 149 | * @param int $begin 开始位置,0 即为从第一条开始查询 150 | * @param int $limit 返回数据条数,最大允许50,默认为20 151 | * @return bool|array 152 | */ 153 | public function getPoiList($begin = 0, $limit = 50) 154 | { 155 | if (!$this->access_token && !$this->getAccessToken()) { 156 | return false; 157 | } 158 | $limit > 50 && $limit = 50; 159 | $data = array('begin' => $begin, 'limit' => $limit); 160 | $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::POI_GET_LIST . "access_token={$this->access_token}", Tools::json_encode($data)); 161 | if ($result) { 162 | $json = json_decode($result, true); 163 | if (empty($json) || !empty($json['errcode'])) { 164 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 165 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 166 | return $this->checkRetry(__FUNCTION__, func_get_args()); 167 | } 168 | return $json; 169 | } 170 | return false; 171 | } 172 | 173 | /** 174 | * 获取商家门店类目表 175 | * @return bool|string 176 | */ 177 | public function getCategory() 178 | { 179 | if (!$this->access_token && !$this->getAccessToken()) { 180 | return false; 181 | } 182 | $result = Tools::httpGet(self::API_URL_PREFIX . self::POI_CATEGORY . "access_token={$this->access_token}"); 183 | if ($result) { 184 | $json = json_decode($result, true); 185 | if (empty($json) || !empty($json['errcode'])) { 186 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 187 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 188 | return $this->checkRetry(__FUNCTION__, func_get_args()); 189 | } 190 | return $json; 191 | } 192 | return false; 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /Wechat/WechatReceive.php: -------------------------------------------------------------------------------- 1 | 26 | * @date 2016/06/28 11:29 27 | */ 28 | class WechatReceive extends WechatMessage 29 | { 30 | 31 | /** 消息回复类型 */ 32 | const MSGTYPE_TEXT = 'text'; 33 | const MSGTYPE_LINK = 'link'; 34 | const MSGTYPE_NEWS = 'news'; 35 | const MSGTYPE_IMAGE = 'image'; 36 | const MSGTYPE_VOICE = 'voice'; 37 | const MSGTYPE_EVENT = 'event'; 38 | const MSGTYPE_MUSIC = 'music'; 39 | const MSGTYPE_VIDEO = 'video'; 40 | const MSGTYPE_LOCATION = 'location'; 41 | 42 | /** 文本过滤 */ 43 | protected $_text_filter = true; 44 | 45 | /** 消息对象 */ 46 | private $_receive; 47 | 48 | /** 49 | * 获取微信服务器发来的内容 50 | * @return $this 51 | */ 52 | public function getRev() 53 | { 54 | if ($this->_receive) { 55 | return $this; 56 | } 57 | $postStr = !empty($this->postxml) ? $this->postxml : file_get_contents("php://input"); 58 | if (!empty($postStr)) { 59 | $disableEntities = libxml_disable_entity_loader(true); 60 | $this->_receive = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA); 61 | libxml_disable_entity_loader($disableEntities); 62 | } 63 | return $this; 64 | } 65 | 66 | /** 67 | * 获取微信服务器发来的信息数据 68 | * @return array 69 | */ 70 | public function getRevData() 71 | { 72 | return $this->_receive; 73 | } 74 | 75 | /** 76 | * 获取消息发送者 77 | * @return bool|string 78 | */ 79 | public function getRevFrom() 80 | { 81 | if (isset($this->_receive['FromUserName'])) { 82 | return $this->_receive['FromUserName']; 83 | } 84 | return false; 85 | } 86 | 87 | /** 88 | * 获取消息接受者 89 | * @return bool|string 90 | */ 91 | public function getRevTo() 92 | { 93 | if (isset($this->_receive['ToUserName'])) { 94 | return $this->_receive['ToUserName']; 95 | } 96 | return false; 97 | } 98 | 99 | /** 100 | * 获取接收消息的类型 101 | * @return bool|string 102 | */ 103 | public function getRevType() 104 | { 105 | if (isset($this->_receive['MsgType'])) { 106 | return $this->_receive['MsgType']; 107 | } 108 | return false; 109 | } 110 | 111 | /** 112 | * 获取消息ID 113 | * @return bool|string 114 | */ 115 | public function getRevID() 116 | { 117 | if (isset($this->_receive['MsgId'])) { 118 | return $this->_receive['MsgId']; 119 | } 120 | return false; 121 | } 122 | 123 | /** 124 | * 获取消息发送时间 125 | * @return bool|string 126 | */ 127 | public function getRevCtime() 128 | { 129 | if (isset($this->_receive['CreateTime'])) { 130 | return $this->_receive['CreateTime']; 131 | } 132 | return false; 133 | } 134 | 135 | /** 136 | * 获取卡券事件推送 - 卡卷审核是否通过 137 | * 当Event为 card_pass_check(审核通过) 或 card_not_pass_check(未通过) 138 | * @return bool|string 返回卡券ID 139 | */ 140 | public function getRevCardPass() 141 | { 142 | if (isset($this->_receive['CardId'])) { 143 | return $this->_receive['CardId']; 144 | } 145 | return false; 146 | } 147 | 148 | /** 149 | * 获取卡券事件推送 - 领取卡券 150 | * 当Event为 user_get_card(用户领取卡券) 151 | * @return bool|array 152 | */ 153 | public function getRevCardGet() 154 | { 155 | $array = array(); 156 | if (isset($this->_receive['CardId'])) { 157 | $array['CardId'] = $this->_receive['CardId']; 158 | } 159 | if (isset($this->_receive['IsGiveByFriend'])) { 160 | $array['IsGiveByFriend'] = $this->_receive['IsGiveByFriend']; 161 | } 162 | $array['OldUserCardCode'] = $this->_receive['OldUserCardCode']; 163 | if (isset($this->_receive['UserCardCode']) && !empty($this->_receive['UserCardCode'])) { 164 | $array['UserCardCode'] = $this->_receive['UserCardCode']; 165 | } 166 | return (isset($array) && count($array) > 0) ? $array : false; 167 | } 168 | 169 | /** 170 | * 获取卡券事件推送 - 删除卡券 171 | * 当Event为 user_del_card (用户删除卡券) 172 | * @return bool|array 173 | */ 174 | public function getRevCardDel() 175 | { 176 | if (isset($this->_receive['CardId'])) { //卡券 ID 177 | $array['CardId'] = $this->_receive['CardId']; 178 | } 179 | if (isset($this->_receive['UserCardCode']) && !empty($this->_receive['UserCardCode'])) { 180 | $array['UserCardCode'] = $this->_receive['UserCardCode']; 181 | } 182 | return (isset($array) && count($array) > 0) ? $array : false; 183 | } 184 | 185 | /** 186 | * 获取接收消息内容正文 187 | * @return bool 188 | */ 189 | public function getRevContent() 190 | { 191 | if (isset($this->_receive['Content'])) { 192 | return $this->_receive['Content']; 193 | } elseif (isset($this->_receive['Recognition'])) { 194 | return $this->_receive['Recognition']; 195 | } 196 | return false; 197 | } 198 | 199 | /** 200 | * 获取接收消息图片 201 | * @return array|bool 202 | */ 203 | public function getRevPic() 204 | { 205 | if (isset($this->_receive['PicUrl'])) { 206 | return array( 207 | 'mediaid' => $this->_receive['MediaId'], 208 | 'picurl' => (string)$this->_receive['PicUrl'], 209 | ); 210 | } 211 | return false; 212 | } 213 | 214 | /** 215 | * 获取接收消息链接 216 | * @return bool|array 217 | */ 218 | public function getRevLink() 219 | { 220 | if (isset($this->_receive['Url'])) { 221 | return array( 222 | 'url' => $this->_receive['Url'], 223 | 'title' => $this->_receive['Title'], 224 | 'description' => $this->_receive['Description'], 225 | ); 226 | } 227 | return false; 228 | } 229 | 230 | /** 231 | * 获取接收地理位置 232 | * @return bool|array 233 | */ 234 | public function getRevGeo() 235 | { 236 | if (isset($this->_receive['Location_X'])) { 237 | return array( 238 | 'x' => $this->_receive['Location_X'], 239 | 'y' => $this->_receive['Location_Y'], 240 | 'scale' => $this->_receive['Scale'], 241 | 'label' => $this->_receive['Label'], 242 | ); 243 | } 244 | return false; 245 | } 246 | 247 | /** 248 | * 获取上报地理位置事件 249 | * @return bool|array 250 | */ 251 | public function getRevEventGeo() 252 | { 253 | if (isset($this->_receive['Latitude'])) { 254 | return array( 255 | 'x' => $this->_receive['Latitude'], 256 | 'y' => $this->_receive['Longitude'], 257 | 'precision' => $this->_receive['Precision'], 258 | ); 259 | } 260 | return false; 261 | } 262 | 263 | /** 264 | * 获取接收事件推送 265 | * @return bool|array 266 | */ 267 | public function getRevEvent() 268 | { 269 | if (isset($this->_receive['Event'])) { 270 | $array['event'] = $this->_receive['Event']; 271 | } 272 | if (isset($this->_receive['EventKey'])) { 273 | $array['key'] = $this->_receive['EventKey']; 274 | } 275 | return (isset($array) && count($array) > 0) ? $array : false; 276 | } 277 | 278 | /** 279 | * 获取自定义菜单的扫码推事件信息 280 | * 281 | * 事件类型为以下两种时则调用此方法有效 282 | * Event 事件类型, scancode_push 283 | * Event 事件类型, scancode_waitmsg 284 | * @return bool|array 285 | */ 286 | public function getRevScanInfo() 287 | { 288 | if (isset($this->_receive['ScanCodeInfo'])) { 289 | if (!is_array($this->_receive['ScanCodeInfo'])) { 290 | $array = (array)$this->_receive['ScanCodeInfo']; 291 | $this->_receive['ScanCodeInfo'] = $array; 292 | } else { 293 | $array = $this->_receive['ScanCodeInfo']; 294 | } 295 | } 296 | return (isset($array) && count($array) > 0) ? $array : false; 297 | } 298 | 299 | /** 300 | * 获取自定义菜单的图片发送事件信息 301 | * 302 | * 事件类型为以下三种时则调用此方法有效 303 | * Event 事件类型,pic_sysphoto 弹出系统拍照发图的事件推送 304 | * Event 事件类型,pic_photo_or_album 弹出拍照或者相册发图的事件推送 305 | * Event 事件类型,pic_weixin 弹出微信相册发图器的事件推送 306 | * 307 | * @return bool|array 308 | * array ( 309 | * 'Count' => '2', 310 | * 'PicList' =>array ( 311 | * 'item' =>array ( 312 | * 0 =>array ('PicMd5Sum' => 'aaae42617cf2a14342d96005af53624c'), 313 | * 1 =>array ('PicMd5Sum' => '149bd39e296860a2adc2f1bb81616ff8'), 314 | * ), 315 | * ), 316 | * ) 317 | * 318 | */ 319 | public function getRevSendPicsInfo() 320 | { 321 | if (isset($this->_receive['SendPicsInfo'])) { 322 | if (!is_array($this->_receive['SendPicsInfo'])) { 323 | $array = (array)$this->_receive['SendPicsInfo']; 324 | if (isset($array['PicList'])) { 325 | $array['PicList'] = (array)$array['PicList']; 326 | $item = $array['PicList']['item']; 327 | $array['PicList']['item'] = array(); 328 | foreach ($item as $key => $value) { 329 | $array['PicList']['item'][$key] = (array)$value; 330 | } 331 | } 332 | $this->_receive['SendPicsInfo'] = $array; 333 | } else { 334 | $array = $this->_receive['SendPicsInfo']; 335 | } 336 | } 337 | return (isset($array) && count($array) > 0) ? $array : false; 338 | } 339 | 340 | /** 341 | * 获取自定义菜单的地理位置选择器事件推送 342 | * 343 | * 事件类型为以下时则可以调用此方法有效 344 | * Event 事件类型,location_select 弹出地理位置选择器的事件推送 345 | * 346 | * @return bool|array 347 | * array ( 348 | * 'Location_X' => '33.731655000061', 349 | * 'Location_Y' => '113.29955200008047', 350 | * 'Scale' => '16', 351 | * 'Label' => '某某市某某区某某路', 352 | * 'Poiname' => '', 353 | * ) 354 | * 355 | */ 356 | public function getRevSendGeoInfo() 357 | { 358 | if (isset($this->_receive['SendLocationInfo'])) { 359 | if (!is_array($this->_receive['SendLocationInfo'])) { 360 | $array = (array)$this->_receive['SendLocationInfo']; 361 | if (empty($array['Poiname'])) { 362 | $array['Poiname'] = ""; 363 | } 364 | if (empty($array['Label'])) { 365 | $array['Label'] = ""; 366 | } 367 | $this->_receive['SendLocationInfo'] = $array; 368 | } else { 369 | $array = $this->_receive['SendLocationInfo']; 370 | } 371 | } 372 | return (isset($array) && count($array) > 0) ? $array : false; 373 | } 374 | 375 | /** 376 | * 获取接收语音推送 377 | * @return bool|array 378 | */ 379 | public function getRevVoice() 380 | { 381 | if (isset($this->_receive['MediaId'])) { 382 | return array( 383 | 'mediaid' => $this->_receive['MediaId'], 384 | 'format' => $this->_receive['Format'], 385 | ); 386 | } 387 | return false; 388 | } 389 | 390 | /** 391 | * 获取接收视频推送 392 | * @return array|bool 393 | */ 394 | public function getRevVideo() 395 | { 396 | if (isset($this->_receive['MediaId'])) { 397 | return array( 398 | 'mediaid' => $this->_receive['MediaId'], 399 | 'thumbmediaid' => $this->_receive['ThumbMediaId'], 400 | ); 401 | } 402 | return false; 403 | } 404 | 405 | /** 406 | * 获取接收TICKET 407 | * @return bool|string 408 | */ 409 | public function getRevTicket() 410 | { 411 | if (isset($this->_receive['Ticket'])) { 412 | return $this->_receive['Ticket']; 413 | } 414 | return false; 415 | } 416 | 417 | /** 418 | * 获取二维码的场景值 419 | * @return bool|string 420 | */ 421 | public function getRevSceneId() 422 | { 423 | if (isset($this->_receive['EventKey'])) { 424 | return str_replace('qrscene_', '', $this->_receive['EventKey']); 425 | } 426 | return false; 427 | } 428 | 429 | /** 430 | * 获取主动推送的消息ID 431 | * 经过验证,这个和普通的消息MsgId不一样 432 | * 当Event为 MASSSENDJOBFINISH 或 TEMPLATESENDJOBFINISH 433 | * @return bool|string 434 | */ 435 | public function getRevTplMsgID() 436 | { 437 | if (isset($this->_receive['MsgID'])) { 438 | return $this->_receive['MsgID']; 439 | } 440 | return false; 441 | } 442 | 443 | /** 444 | * 获取模板消息发送状态 445 | * @return bool|string 446 | */ 447 | public function getRevStatus() 448 | { 449 | if (isset($this->_receive['Status'])) { 450 | return $this->_receive['Status']; 451 | } 452 | return false; 453 | } 454 | 455 | /** 456 | * 获取群发或模板消息发送结果 457 | * 当Event为 MASSSENDJOBFINISH 或 TEMPLATESENDJOBFINISH,即高级群发/模板消息 458 | * @return bool|array 459 | */ 460 | public function getRevResult() 461 | { 462 | if (isset($this->_receive['Status'])) { //发送是否成功,具体的返回值请参考 高级群发/模板消息 的事件推送说明 463 | $array['Status'] = $this->_receive['Status']; 464 | } 465 | if (isset($this->_receive['MsgID'])) { //发送的消息id 466 | $array['MsgID'] = $this->_receive['MsgID']; 467 | } 468 | //以下仅当群发消息时才会有的事件内容 469 | if (isset($this->_receive['TotalCount'])) { //分组或openid列表内粉丝数量 470 | $array['TotalCount'] = $this->_receive['TotalCount']; 471 | } 472 | if (isset($this->_receive['FilterCount'])) { //过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数 473 | $array['FilterCount'] = $this->_receive['FilterCount']; 474 | } 475 | if (isset($this->_receive['SentCount'])) { //发送成功的粉丝数 476 | $array['SentCount'] = $this->_receive['SentCount']; 477 | } 478 | if (isset($this->_receive['ErrorCount'])) { //发送失败的粉丝数 479 | $array['ErrorCount'] = $this->_receive['ErrorCount']; 480 | } 481 | if (isset($array) && count($array) > 0) { 482 | return $array; 483 | } 484 | return false; 485 | } 486 | 487 | /** 488 | * 获取多客服会话状态推送事件 - 接入会话 489 | * 当Event为 kfcreatesession 即接入会话 490 | * @return bool|string 491 | */ 492 | public function getRevKFCreate() 493 | { 494 | if (isset($this->_receive['KfAccount'])) { 495 | return $this->_receive['KfAccount']; 496 | } 497 | return false; 498 | } 499 | 500 | /** 501 | * 获取多客服会话状态推送事件 - 关闭会话 502 | * 当Event为 kfclosesession 即关闭会话 503 | * @return bool|string 504 | */ 505 | public function getRevKFClose() 506 | { 507 | if (isset($this->_receive['KfAccount'])) { 508 | return $this->_receive['KfAccount']; 509 | } 510 | return false; 511 | } 512 | 513 | /** 514 | * 获取多客服会话状态推送事件 - 转接会话 515 | * 当Event为 kfswitchsession 即转接会话 516 | * @return bool|array 517 | */ 518 | public function getRevKFSwitch() 519 | { 520 | if (isset($this->_receive['FromKfAccount'])) { //原接入客服 521 | $array['FromKfAccount'] = $this->_receive['FromKfAccount']; 522 | } 523 | if (isset($this->_receive['ToKfAccount'])) { //转接到客服 524 | $array['ToKfAccount'] = $this->_receive['ToKfAccount']; 525 | } 526 | return (isset($array) && count($array) > 0) ? $array : false; 527 | } 528 | 529 | /** 530 | * 发送客服消息 531 | * @param array $data 消息结构{"touser":"OPENID","msgtype":"news","news":{...}} 532 | * @return bool|array 533 | */ 534 | public function sendCustomMessage($data) 535 | { 536 | if (!$this->access_token && !$this->getAccessToken()) { 537 | return false; 538 | } 539 | $result = Tools::httpPost(self::API_URL_PREFIX . "/message/custom/send?access_token={$this->access_token}", Tools::json_encode($data)); 540 | if ($result) { 541 | $json = json_decode($result, true); 542 | if (empty($json) || !empty($json['errcode'])) { 543 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 544 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 545 | return $this->checkRetry(__FUNCTION__, func_get_args()); 546 | } 547 | return $json; 548 | } 549 | return false; 550 | } 551 | 552 | /** 553 | * 转发多客服消息 554 | * @param string $customer_account 555 | * @return $this 556 | */ 557 | public function transfer_customer_service($customer_account = '') 558 | { 559 | $msg = array( 560 | 'ToUserName' => $this->getRevFrom(), 561 | 'FromUserName' => $this->getRevTo(), 562 | 'CreateTime' => time(), 563 | 'MsgType' => 'transfer_customer_service', 564 | ); 565 | if ($customer_account) { 566 | $msg['TransInfo'] = array('KfAccount' => $customer_account); 567 | } 568 | $this->Message($msg); 569 | return $this; 570 | } 571 | 572 | /** 573 | * 设置发送消息 574 | * @param string|array $msg 消息数组 575 | * @param bool $append 是否在原消息数组追加 576 | * @return array 577 | */ 578 | public function Message($msg = '', $append = false) 579 | { 580 | if (is_null($msg)) { 581 | $this->_msg = array(); 582 | } elseif (is_array($msg)) { 583 | if ($append) { 584 | $this->_msg = array_merge($this->_msg, $msg); 585 | } else { 586 | $this->_msg = $msg; 587 | } 588 | return $this->_msg; 589 | } 590 | return $this->_msg; 591 | } 592 | 593 | /** 594 | * 设置文本消息 595 | * @param string $text 文本内容 596 | * @return $this 597 | */ 598 | public function text($text = '') 599 | { 600 | $msg = array( 601 | 'ToUserName' => $this->getRevFrom(), 602 | 'FromUserName' => $this->getRevTo(), 603 | 'MsgType' => self::MSGTYPE_TEXT, 604 | 'Content' => $this->_auto_text_filter($text), 605 | 'CreateTime' => time(), 606 | ); 607 | $this->Message($msg); 608 | return $this; 609 | } 610 | 611 | /** 612 | * 设置图片消息 613 | * @param string $mediaid 图片媒体ID 614 | * @return $this 615 | */ 616 | public function image($mediaid = '') 617 | { 618 | $msg = array( 619 | 'ToUserName' => $this->getRevFrom(), 620 | 'FromUserName' => $this->getRevTo(), 621 | 'MsgType' => self::MSGTYPE_IMAGE, 622 | 'Image' => array('MediaId' => $mediaid), 623 | 'CreateTime' => time(), 624 | ); 625 | $this->Message($msg); 626 | return $this; 627 | } 628 | 629 | /** 630 | * 设置语音回复消息 631 | * @param string $mediaid 语音媒体ID 632 | * @return $this 633 | */ 634 | public function voice($mediaid = '') 635 | { 636 | $msg = array( 637 | 'ToUserName' => $this->getRevFrom(), 638 | 'FromUserName' => $this->getRevTo(), 639 | 'MsgType' => self::MSGTYPE_VOICE, 640 | 'Voice' => array('MediaId' => $mediaid), 641 | 'CreateTime' => time(), 642 | ); 643 | $this->Message($msg); 644 | return $this; 645 | } 646 | 647 | /** 648 | * 设置视频回复消息 649 | * @param string $mediaid 视频媒体ID 650 | * @param string $title 视频标题 651 | * @param string $description 视频描述 652 | * @return $this 653 | */ 654 | public function video($mediaid = '', $title = '', $description = '') 655 | { 656 | $msg = array( 657 | 'ToUserName' => $this->getRevFrom(), 658 | 'FromUserName' => $this->getRevTo(), 659 | 'MsgType' => self::MSGTYPE_VIDEO, 660 | 'Video' => array( 661 | 'MediaId' => $mediaid, 662 | 'Title' => $title, 663 | 'Description' => $description, 664 | ), 665 | 'CreateTime' => time(), 666 | ); 667 | $this->Message($msg); 668 | return $this; 669 | } 670 | 671 | /** 672 | * 设置音乐回复消息 673 | * @param string $title 音乐标题 674 | * @param string $desc 音乐描述 675 | * @param string $musicurl 音乐地址 676 | * @param string $hgmusicurl 高清音乐地址 677 | * @param string $thumbmediaid 音乐图片缩略图的媒体id(可选) 678 | * @return $this 679 | */ 680 | public function music($title, $desc, $musicurl, $hgmusicurl = '', $thumbmediaid = '') 681 | { 682 | $msg = array( 683 | 'ToUserName' => $this->getRevFrom(), 684 | 'FromUserName' => $this->getRevTo(), 685 | 'CreateTime' => time(), 686 | 'MsgType' => self::MSGTYPE_MUSIC, 687 | 'Music' => array( 688 | 'Title' => $title, 689 | 'Description' => $desc, 690 | 'MusicUrl' => $musicurl, 691 | 'HQMusicUrl' => $hgmusicurl, 692 | ), 693 | ); 694 | if ($thumbmediaid) { 695 | $msg['Music']['ThumbMediaId'] = $thumbmediaid; 696 | } 697 | $this->Message($msg); 698 | return $this; 699 | } 700 | 701 | /** 702 | * 设置回复图文 703 | * @param array $newsData 704 | * @return $this 705 | */ 706 | public function news($newsData = array()) 707 | { 708 | $msg = array( 709 | 'ToUserName' => $this->getRevFrom(), 710 | 'FromUserName' => $this->getRevTo(), 711 | 'CreateTime' => time(), 712 | 'MsgType' => self::MSGTYPE_NEWS, 713 | 'ArticleCount' => count($newsData), 714 | 'Articles' => $newsData, 715 | ); 716 | $this->Message($msg); 717 | return $this; 718 | } 719 | 720 | /** 721 | * 回复微信服务器 722 | * @param array $msg 要发送的信息(默认取$this->_msg) 723 | * @param bool $return 是否返回信息而不抛出到浏览器(默认:否) 724 | * @return bool|string 725 | */ 726 | public function reply($msg = array(), $return = false) 727 | { 728 | if (empty($msg)) { 729 | if (empty($this->_msg)) { //防止不先设置回复内容,直接调用reply方法导致异常 730 | return false; 731 | } 732 | $msg = $this->_msg; 733 | } 734 | $xmldata = Tools::arr2xml($msg); 735 | if ($this->encrypt_type == 'aes') { //如果来源消息为加密方式 736 | !class_exists('Prpcrypt', false) && require __DIR__ . '/Lib/Prpcrypt.php'; 737 | $pc = new Prpcrypt($this->encodingAesKey); 738 | // 如果是第三方平台,加密得使用 component_appid 739 | $array = $pc->encrypt($xmldata, empty($this->config['component_appid']) ? $this->appid : $this->config['component_appid']); 740 | $ret = $array[0]; 741 | if ($ret != 0) { 742 | Tools::log('Encrypt Error!', "ERR - {$this->appid}"); 743 | return false; 744 | } 745 | $timestamp = time(); 746 | $nonce = rand(77, 999) * rand(605, 888) * rand(11, 99); 747 | $encrypt = $array[1]; 748 | $tmpArr = array($this->token, $timestamp, $nonce, $encrypt); 749 | sort($tmpArr, SORT_STRING); 750 | $signature = sha1(implode($tmpArr)); 751 | $format = "%s"; 752 | $xmldata = sprintf($format, $encrypt, $signature, $timestamp, $nonce); 753 | } 754 | if ($return) { 755 | return $xmldata; 756 | } 757 | echo $xmldata; 758 | } 759 | 760 | /** 761 | * 过滤文字回复\r\n换行符 762 | * @param string $text 763 | * @return string 764 | */ 765 | private function _auto_text_filter($text) 766 | { 767 | if (!$this->_text_filter) { 768 | return $text; 769 | } 770 | return str_replace("\r\n", "\n", $text); 771 | } 772 | 773 | } 774 | -------------------------------------------------------------------------------- /Wechat/WechatScript.php: -------------------------------------------------------------------------------- 1 | 26 | * @date 2016/06/28 11:24 27 | */ 28 | class WechatScript extends Common 29 | { 30 | 31 | /** 32 | * JSAPI授权TICKET 33 | * @var string 34 | */ 35 | public $jsapi_ticket; 36 | 37 | /** 38 | * 删除JSAPI授权TICKET 39 | * @param string $appid 40 | * @return bool 41 | */ 42 | public function resetJsTicket($appid = '') 43 | { 44 | $this->jsapi_ticket = ''; 45 | $authname = 'wechat_jsapi_ticket_' . empty($appid) ? $this->appid : $appid; 46 | Tools::removeCache($authname); 47 | return true; 48 | } 49 | 50 | /** 51 | * 获取JSAPI授权TICKET 52 | * @param string $appid 用于多个appid时使用,可空 53 | * @param string $jsapi_ticket 手动指定jsapi_ticket,非必要情况不建议用 54 | * @param string $access_token 获取 jsapi_ticket 指定 access_token 55 | * @return bool|string 56 | */ 57 | public function getJsTicket($appid = '', $jsapi_ticket = '', $access_token = '') 58 | { 59 | if (empty($access_token)) { 60 | if (!$this->access_token && !$this->getAccessToken()) { 61 | return false; 62 | } 63 | $access_token = $this->access_token; 64 | } 65 | if (empty($appid)) { 66 | $appid = $this->appid; 67 | } 68 | # 手动指定token,优先使用 69 | if ($jsapi_ticket) { 70 | $this->jsapi_ticket = $jsapi_ticket; 71 | return $this->jsapi_ticket; 72 | } 73 | # 尝试从缓存中读取 74 | $cache = 'wechat_jsapi_ticket_' . $appid; 75 | $jt = Tools::getCache($cache); 76 | if ($jt) { 77 | return $this->jsapi_ticket = $jt; 78 | } 79 | # 检测事件注册 80 | if (isset(Loader::$callback[__FUNCTION__])) { 81 | return $this->jsapi_ticket = call_user_func_array(Loader::$callback[__FUNCTION__], array(&$this, &$cache)); 82 | } 83 | # 调接口获取 84 | $result = Tools::httpGet(self::API_URL_PREFIX . self::GET_TICKET_URL . "access_token={$access_token}" . '&type=jsapi'); 85 | if ($result) { 86 | $json = json_decode($result, true); 87 | if (empty($json) || !empty($json['errcode'])) { 88 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 89 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 90 | return $this->checkRetry(__FUNCTION__, func_get_args()); 91 | } 92 | $this->jsapi_ticket = $json['ticket']; 93 | Tools::setCache($cache, $this->jsapi_ticket, $json['expires_in'] ? intval($json['expires_in']) - 100 : 3600); 94 | return $this->jsapi_ticket; 95 | } 96 | return false; 97 | } 98 | 99 | /** 100 | * 获取JsApi使用签名 101 | * @param string $url 网页的URL,自动处理#及其后面部分 102 | * @param int $timestamp 当前时间戳 (为空则自动生成) 103 | * @param string $noncestr 随机串 (为空则自动生成) 104 | * @param string $appid 用于多个appid时使用,可空 105 | * @param string $access_token 获取 jsapi_ticket 指定 access_token 106 | * @return array|bool 返回签名字串 107 | */ 108 | public function getJsSign($url, $timestamp = 0, $noncestr = '', $appid = '', $access_token = '') 109 | { 110 | if (!$this->jsapi_ticket && !$this->getJsTicket($appid, '', $access_token) || empty($url)) { 111 | return false; 112 | } 113 | $data = array( 114 | "jsapi_ticket" => $this->jsapi_ticket, 115 | "timestamp" => empty($timestamp) ? time() : $timestamp, 116 | "noncestr" => '' . empty($noncestr) ? Tools::createNoncestr(16) : $noncestr, 117 | "url" => trim($url), 118 | ); 119 | return array( 120 | "url" => $url, 121 | 'debug' => false, 122 | "appId" => empty($appid) ? $this->appid : $appid, 123 | "nonceStr" => $data['noncestr'], 124 | "timestamp" => $data['timestamp'], 125 | "signature" => Tools::getSignature($data, 'sha1'), 126 | 'jsApiList' => array( 127 | 'updateTimelineShareData', 'updateAppMessageShareData', 'onMenuShareQQ', 'onMenuShareWeibo', 'onMenuShareQZone', 128 | 'hideOptionMenu', 'showOptionMenu', 'hideMenuItems', 'showMenuItems', 'hideAllNonBaseMenuItem', 'showAllNonBaseMenuItem', 129 | 'chooseImage', 'previewImage', 'uploadImage', 'downloadImage', 'closeWindow', 'scanQRCode', 'chooseWXPay', 130 | 'translateVoice', 'getNetworkType', 'openLocation', 'getLocation', 131 | 'openProductSpecificView', 'addCard', 'chooseCard', 'openCard', 132 | 'startRecord', 'stopRecord', 'onVoiceRecordEnd', 'playVoice', 'pauseVoice', 'stopVoice', 'onVoicePlayEnd', 'uploadVoice', 'downloadVoice', 133 | 'openWXDeviceLib', 'closeWXDeviceLib', 'getWXDeviceInfos', 'sendDataToWXDevice', 'disconnectWXDevice', 'getWXDeviceTicket', 'connectWXDevice', 134 | 'startScanWXDevice', 'stopScanWXDevice', 'onWXDeviceBindStateChange', 'onScanWXDeviceResult', 'onReceiveDataFromWXDevice', 135 | 'onWXDeviceBluetoothStateChange', 'onWXDeviceStateChange' 136 | ) 137 | ); 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /Wechat/WechatService.php: -------------------------------------------------------------------------------- 1 | 27 | * @date 2016/10/18 00:35:55 28 | */ 29 | class WechatService 30 | { 31 | 32 | const URL_PREFIX = 'https://api.weixin.qq.com/cgi-bin/component'; 33 | // 获取服务access_token 34 | const COMPONENT_TOKEN_URL = '/api_component_token'; 35 | // 获取(刷新)授权公众号的令牌 36 | const REFRESH_ACCESS_TOKEN = '/api_authorizer_token'; 37 | // 获取预授权码 38 | const PREAUTH_CODE_URL = '/api_create_preauthcode'; 39 | // 获取公众号的授权信息 40 | const QUERY_AUTH_URL = '/api_query_auth'; 41 | // 获取授权方的账户信息 42 | const GET_AUTHORIZER_INFO_URL = '/api_get_authorizer_info'; 43 | // 获取授权方的选项设置信息 44 | const GET_AUTHORIZER_OPTION_URL = '/api_get_authorizer_option'; 45 | // 设置授权方的选项信息 46 | const SET_AUTHORIZER_OPTION_URL = '/api_set_authorizer_option'; 47 | 48 | // 微信后台推送的ticket 每十分钟更新一次 49 | public $errCode; 50 | // 服务appid 51 | public $errMsg; 52 | // 服务appsecret 53 | protected $component_verify_ticket; 54 | // 公众号消息校验Token 55 | protected $component_appid; 56 | // 公众号消息加解密Key 57 | protected $component_appsecret; 58 | // 服务令牌 59 | protected $component_token; 60 | // 授权方appid 61 | protected $component_encodingaeskey; 62 | // 授权方令牌 63 | protected $component_access_token; 64 | // 刷新令牌 65 | protected $authorizer_appid; 66 | // JSON数据 67 | protected $pre_auth_code; 68 | // 错误消息 69 | protected $data; 70 | 71 | /** 72 | * WechatService constructor. 73 | * @param array $options 74 | */ 75 | public function __construct($options = array()) 76 | { 77 | $options = Loader::config($options); 78 | $this->component_encodingaeskey = !empty($options['component_encodingaeskey']) ? $options['component_encodingaeskey'] : ''; 79 | $this->component_verify_ticket = !empty($options['component_verify_ticket']) ? $options['component_verify_ticket'] : ''; 80 | $this->component_appsecret = !empty($options['component_appsecret']) ? $options['component_appsecret'] : ''; 81 | $this->component_token = !empty($options['component_token']) ? $options['component_token'] : ''; 82 | $this->component_appid = !empty($options['component_appid']) ? $options['component_appid'] : ''; 83 | } 84 | 85 | /** 86 | * 接收公众平台推送的 Ticket 87 | * @return bool|array 88 | */ 89 | public function getComonentTicket() 90 | { 91 | $receive = new WechatReceive(array( 92 | 'appid' => $this->component_appid, 93 | 'appsecret' => $this->component_appsecret, 94 | 'encodingaeskey' => $this->component_encodingaeskey, 95 | 'token' => $this->component_token, 96 | 'cachepath' => Cache::$cachepath 97 | )); 98 | # 会话内容解密状态判断 99 | if (false === $receive->valid()) { 100 | $this->errCode = $receive->errCode; 101 | $this->errMsg = $receive->errMsg; 102 | Tools::log("Get Wechat Push ComponentVerifyTicket Faild. {$this->errMsg} [$this->errCode]", "ERR - {$this->authorizer_appid}"); 103 | return false; 104 | } 105 | $data = $receive->getRev()->getRevData(); 106 | if ($data['InfoType'] === 'component_verify_ticket' && !empty($data['ComponentVerifyTicket'])) { 107 | # 记录推送日志到微信SDK 108 | Tools::log("Wechat Push ComponentVerifyTicket Success. "); 109 | Tools::setCache('component_verify_ticket', $data['ComponentVerifyTicket']); 110 | } 111 | return $data; 112 | } 113 | 114 | /** 115 | * 获取(刷新)授权公众号的令牌 116 | * @注意1. 授权公众号访问access token2小时有效 117 | * @注意2. 一定保存好新的刷新令牌 118 | * @param string $authorizer_appid 授权方APPID 119 | * @param string $authorizer_refresh_token 授权方刷新令牌 120 | * @return bool|string 121 | */ 122 | public function refreshAccessToken($authorizer_appid, $authorizer_refresh_token) 123 | { 124 | empty($this->component_access_token) && $this->getComponentAccessToken(); 125 | if (empty($this->component_access_token)) { 126 | return false; 127 | } 128 | $data = array(); 129 | $data['component_appid'] = $this->component_appid; 130 | $data['authorizer_appid'] = $authorizer_appid; 131 | $data['authorizer_refresh_token'] = $authorizer_refresh_token; 132 | $url = self::URL_PREFIX . self::REFRESH_ACCESS_TOKEN . "?component_access_token={$this->component_access_token}"; 133 | $result = Tools::httpPost($url, Tools::json_encode($data)); 134 | if (($result = $this->_decode($result)) === false) { 135 | Tools::log("Get getAuthorizerOption Faild. {$this->errMsg} [$this->errCode]", "ERR - {$this->authorizer_appid}"); 136 | } 137 | return $result; 138 | } 139 | 140 | /** 141 | * 获取或刷新服务 AccessToken 142 | * @return bool|string 143 | */ 144 | public function getComponentAccessToken() 145 | { 146 | $cacheKey = 'wechat_component_access_token'; 147 | $this->component_access_token = Tools::getCache($cacheKey); 148 | if (empty($this->component_access_token)) { 149 | $data = array(); 150 | $data['component_appid'] = $this->component_appid; 151 | $data['component_appsecret'] = $this->component_appsecret; 152 | $data['component_verify_ticket'] = $this->component_verify_ticket; 153 | $url = self::URL_PREFIX . self::COMPONENT_TOKEN_URL; 154 | $result = Tools::httpPost($url, Tools::json_encode($data)); 155 | if (($this->component_access_token = $this->_decode($result, 'component_access_token')) === false) { 156 | Tools::log("Get getComponentAccessToken Faild. {$this->errMsg} [$this->errCode]", "ERR - {$this->authorizer_appid}"); 157 | return false; 158 | } 159 | Tools::setCache($cacheKey, $this->component_access_token, 7200); 160 | } 161 | return $this->component_access_token; 162 | } 163 | 164 | /** 165 | * 解析JSON数据 166 | * @param string $result 167 | * @param string|null $field 168 | * @return bool|array 169 | */ 170 | private function _decode($result, $field = null) 171 | { 172 | $this->data = json_decode($result, true); 173 | if (!empty($this->data['errcode'])) { 174 | $this->errCode = $this->data['errcode']; 175 | $this->errMsg = $this->data['errmsg']; 176 | return false; 177 | } 178 | if ($this->data && !is_null($field)) { 179 | if (isset($this->data[$field])) { 180 | return $this->data[$field]; 181 | } else { 182 | return false; 183 | } 184 | } 185 | return $this->data; 186 | } 187 | 188 | /** 189 | * 获取公众号的授权信息 190 | * 191 | * @param string $authorization_code 192 | * @return bool|array 193 | */ 194 | public function getAuthorizationInfo($authorization_code) 195 | { 196 | empty($this->component_access_token) && $this->getComponentAccessToken(); 197 | if (empty($this->component_access_token)) { 198 | return false; 199 | } 200 | $data = array(); 201 | $data['component_appid'] = $this->component_appid; 202 | $data['authorization_code'] = $authorization_code; 203 | $url = self::URL_PREFIX . self::QUERY_AUTH_URL . "?component_access_token={$this->component_access_token}"; 204 | $result = Tools::httpPost($url, Tools::json_encode($data)); 205 | $authorization_info = $this->_decode($result, 'authorization_info'); 206 | if (empty($authorization_info)) { 207 | Tools::log("Get getAuthorizationInfo Faild. {$this->errMsg} [$this->errCode]", "ERR - {$this->authorizer_appid}"); 208 | return false; 209 | } 210 | $authorization_info['func_info'] = $this->_parseFuncInfo($authorization_info['func_info']); 211 | return $authorization_info; 212 | } 213 | 214 | /** 215 | * 解析授权信息,返回以逗号分割的数据 216 | * @param array $func_info 217 | * @return string 218 | */ 219 | private function _parseFuncInfo($func_info) 220 | { 221 | $authorization_list = array(); 222 | foreach ($func_info as $func) { 223 | foreach ($func as $f) { 224 | isset($f['id']) && $authorization_list[] = $f['id']; 225 | } 226 | } 227 | return join($authorization_list, ','); 228 | } 229 | 230 | /** 231 | * 获取授权方的账户信息 232 | * @param string $authorizer_appid 233 | * @return bool 234 | */ 235 | public function getWechatInfo($authorizer_appid) 236 | { 237 | empty($this->component_access_token) && $this->getComponentAccessToken(); 238 | $data = array(); 239 | $data['component_access_token'] = $this->component_access_token; 240 | $data['component_appid'] = $this->component_appid; 241 | $data['authorizer_appid'] = $authorizer_appid; 242 | $url = self::URL_PREFIX . self::GET_AUTHORIZER_INFO_URL . "?component_access_token={$this->component_access_token}"; 243 | $result = Tools::httpPost($url, Tools::json_encode($data)); 244 | $authorizer_info = $this->_decode($result, 'authorizer_info'); 245 | if (empty($authorizer_info)) { 246 | Tools::log("Get WechatInfo Faild. {$this->errMsg} [$this->errCode]", "ERR - {$this->authorizer_appid}"); 247 | return false; 248 | } 249 | $author_data = array_merge($authorizer_info, $this->data['authorization_info']); 250 | $author_data['service_type_info'] = $author_data['service_type_info']['id']; 251 | $author_data['verify_type_info'] = $author_data['verify_type_info']['id']; 252 | $author_data['func_info'] = $this->_parseFuncInfo($author_data['func_info']); 253 | $author_data['business_info'] = json_encode($author_data['business_info']); 254 | return $author_data; 255 | } 256 | 257 | /** 258 | * 获取授权方的选项设置信息 259 | * @param string $authorizer_appid 260 | * @param string $option_name 261 | * @return bool 262 | */ 263 | public function getAuthorizerOption($authorizer_appid, $option_name) 264 | { 265 | empty($this->component_access_token) && $this->getComponentAccessToken(); 266 | if (empty($this->authorizer_appid)) { 267 | return false; 268 | } 269 | $data = array(); 270 | $data['component_appid'] = $this->component_appid; 271 | $data['authorizer_appid'] = $authorizer_appid; 272 | $data['option_name'] = $option_name; 273 | $url = self::URL_PREFIX . self::GET_AUTHORIZER_OPTION_URL . "?component_access_token={$this->component_access_token}"; 274 | $result = Tools::httpPost($url, Tools::json_encode($data)); 275 | if (($result = $this->_decode($result)) === false) { 276 | Tools::log("Get getAuthorizerOption Faild. {$this->errMsg} [$this->errCode]", "ERR - {$this->authorizer_appid}"); 277 | } 278 | return $result; 279 | } 280 | 281 | /** 282 | * 设置授权方的选项信息 283 | * @param string $authorizer_appid 284 | * @param string $option_name 285 | * @param string $option_value 286 | * @return bool 287 | */ 288 | public function setAuthorizerOption($authorizer_appid, $option_name, $option_value) 289 | { 290 | empty($this->component_access_token) && $this->getComponentAccessToken(); 291 | if (empty($this->authorizer_appid)) { 292 | return false; 293 | } 294 | $data = array(); 295 | $data['component_appid'] = $this->component_appid; 296 | $data['authorizer_appid'] = $authorizer_appid; 297 | $data['option_name'] = $option_name; 298 | $data['option_value'] = $option_value; 299 | $url = self::URL_PREFIX . self::SET_AUTHORIZER_OPTION_URL . "?component_access_token={$this->component_access_token}"; 300 | $result = Tools::httpPost($url, Tools::json_encode($data)); 301 | if (($result = $this->_decode($result)) === false) { 302 | Tools::log("Get setAuthorizerOption Faild. {$this->errMsg} [$this->errCode]", "ERR - {$this->authorizer_appid}"); 303 | } 304 | return $result; 305 | } 306 | 307 | /** 308 | * 获取授权回跳地址 309 | * @param string $redirect_uri 310 | * @return bool 311 | */ 312 | public function getAuthRedirect($redirect_uri) 313 | { 314 | empty($this->pre_auth_code) && $this->getPreauthCode(); 315 | if (empty($this->pre_auth_code)) { 316 | return false; 317 | } 318 | return "https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid={$this->component_appid}&pre_auth_code={$this->pre_auth_code}&redirect_uri={$redirect_uri}"; 319 | } 320 | 321 | /** 322 | * 获取预授权码 323 | * 324 | * @return bool|string 325 | */ 326 | public function getPreauthCode() 327 | { 328 | empty($this->component_access_token) && $this->getComponentAccessToken(); 329 | if (empty($this->component_access_token)) { 330 | return false; 331 | } 332 | $data = array(); 333 | $data['component_appid'] = $this->component_appid; 334 | $url = self::URL_PREFIX . self::PREAUTH_CODE_URL . "?component_access_token={$this->component_access_token}"; 335 | $result = Tools::httpPost($url, Tools::json_encode($data)); 336 | $this->pre_auth_code = $this->_decode($result, 'pre_auth_code'); 337 | if (empty($this->pre_auth_code)) { 338 | Tools::log("Get getPreauthCode Faild. {$this->errMsg} [$this->errCode]", "ERR - {$this->authorizer_appid}"); 339 | } 340 | return $this->pre_auth_code; 341 | } 342 | 343 | /** 344 | * oauth 授权跳转接口 345 | * @param string $appid 346 | * @param string $redirect_uri 347 | * @param string $scope snsapi_userinfo|snsapi_base 348 | * @return string 349 | */ 350 | public function getOauthRedirect($appid, $redirect_uri, $scope = 'snsapi_userinfo') 351 | { 352 | return "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$appid}&redirect_uri=" . urlencode($redirect_uri) 353 | . "&response_type=code&scope={$scope}&state={$appid}&component_appid={$this->component_appid}#wechat_redirect"; 354 | } 355 | 356 | /** 357 | * 通过code获取Access Token 358 | * @param string $appid 359 | * @return bool|array 360 | */ 361 | public function getOauthAccessToken($appid) 362 | { 363 | $code = isset($_GET['code']) ? $_GET['code'] : ''; 364 | if (empty($code)) { 365 | return false; 366 | } 367 | empty($this->component_access_token) && $this->getComponentAccessToken(); 368 | if (empty($this->component_access_token)) { 369 | return false; 370 | } 371 | $url = "https://api.weixin.qq.com/sns/oauth2/component/access_token?appid={$appid}&code={$code}&grant_type=authorization_code&" 372 | . "component_appid={$this->component_appid}&component_access_token={$this->component_access_token}"; 373 | $json = $this->parseJson(Tools::httpGet($url)); 374 | if ($json !== false) { 375 | return $json; 376 | } 377 | return false; 378 | } 379 | 380 | /** 381 | * 解析JSON数据 382 | * @param string $result 383 | * @return bool 384 | */ 385 | private function parseJson($result) 386 | { 387 | $json = json_decode($result, true); 388 | if (empty($json) || !empty($json['errcode'])) { 389 | $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505'; 390 | $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容!'; 391 | return false; 392 | } 393 | return $json; 394 | } 395 | 396 | /** 397 | * 获取关注者详细信息 398 | * @param string $openid 399 | * @param string $oauthAccessToken 400 | * @return bool|array {subscribe,openid,nickname,sex,city,province,country,language,headimgurl,subscribe_time,[unionid]} 401 | * 注意:unionid字段 只有在用户将公众号绑定到公众号第三方平台账号后,才会出现。建议调用前用isset()检测一下 402 | */ 403 | public function getOauthUserInfo($openid, $oauthAccessToken) 404 | { 405 | $url = "https://api.weixin.qq.com/sns/userinfo?access_token={$oauthAccessToken}&openid={$openid}&lang=zh_CN"; 406 | return $this->parseJson(Tools::httpGet($url)); 407 | } 408 | 409 | 410 | } 411 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "project", 3 | "name": "zoujingli/wechat-php-sdk", 4 | "homepage": "https://www.kancloud.cn/zoujingli/wechat-php-sdk", 5 | "description": "WeChat Development of SDK", 6 | "license": "MIT", 7 | "keywords": [ 8 | "wechat-php-sdk" 9 | ], 10 | "require": { 11 | "php": ">=5.4", 12 | "ext-curl": "*", 13 | "ext-json": "*", 14 | "ext-libxml": "*", 15 | "ext-simplexml": "*" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "Wechat\\": "./Wechat" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /include.php: -------------------------------------------------------------------------------- 1 | '', 23 | 'appid' => '', 24 | 'appsecret' => '', 25 | 'encodingaeskey' => '', 26 | ); 27 | 28 | # 加载对应操作接口 29 | $wechat = \Wechat\Loader::get('User', $config); 30 | $userlist = $wechat->getUserList(); 31 | 32 | var_dump($userlist); 33 | var_dump($wechat->errMsg); 34 | var_dump($wechat->errCode); 35 | 36 | exit; 37 | 38 | // 第三方平台 JSSDK 签名包 39 | 40 | $wechat = Db::name('WechatConfig')->where('authorizer_appid', 'wx60a43dd8161666d4')->find(); 41 | // 第三方授权获取到的 Access_token 42 | $access_token = $wechat['authorizer_access_token']; 43 | // 参与授权的公众号 APPID 44 | $authorizer_appid = $wechat['authorizer_appid']; 45 | // 当前微信页面URL地址(完整) 46 | $current_url = url('', '', true, true); 47 | // 实例SDK脚本 48 | $script = load_wechat('Script', $authorizer_appid); 49 | // 获取JS签名包 50 | $result = $script->getJsSign($current_url, 0, '', $authorizer_appid, $access_token); 51 | 52 | $json = json_encode($result, JSON_PRETTY_PRINT); 53 | echo ''; 54 | echo " 55 | 68 | "; --------------------------------------------------------------------------------