├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Atan │ └── Wechat │ │ ├── Facades │ │ └── Wechat.php │ │ ├── Wechat.php │ │ └── WechatServiceProvider.php └── config │ ├── .gitkeep │ └── wechat.php └── tests └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | 9 | before_script: 10 | - travis_retry composer self-update 11 | - travis_retry composer install --prefer-source --no-interaction --dev 12 | 13 | script: phpunit 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 atan 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 all 13 | 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 THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 本项目已停止开发 2 | 3 | 4 | #微信公众平台 Laravel SDK 5 | 6 | ##使用帮助 7 | 8 | ###0、说明 9 | 10 | 用 Laravel Facade 封装了 [dodgepudding/wechat-php-sdk](https://github.com/dodgepudding/wechat-php-sdk),目前只添加了 wechat.class 官方API类库,尽量做到及时更新,还未做全部测试,欢迎提交issue。 11 | 12 | ###1、安装 13 | 14 | 请先确认已经安装Composer. 编辑 `composer.json` 文件,然后加入 `atan/wechat`. 15 | 16 | ``` 17 | "require": { 18 | "atan/wechat": "dev-master" 19 | } 20 | ``` 21 | 22 | 更新包 `composer update` 。 23 | 24 | 需要添加以下服务到系统。 25 | 26 | 打开 `app/config/app.php` , 在`'providers'` 下添加: 27 | 28 | ``` 29 | 'Atan\Wechat\WechatServiceProvider', 30 | ``` 31 | 32 | 在`'aliases'` 下添加: 33 | 34 | ``` 35 | 'Wechat' => 'Atan\Wechat\Facades\Wechat', 36 | ``` 37 | 38 | 执行 `php artisan config:publish atan/wechat` ,然后修改 `app/config/packages/atan/wechat` 中的配置文件 `wechat.php` 。 39 | 40 | 把微信公众号的 'token', 'encodingaeskey', 'appid', 'appsecret' 改为对应的。 41 | 42 | ##2、使用 43 | 44 | 45 | ```php 46 | // 获取用户列表 47 | Route::get('/users', function(){ 48 | return Wechat::getUserList(); 49 | }); 50 | 51 | // 回复文本消息 52 | Route::post('/', function(){ 53 | $weObj = new Atan\Wechat\Wechat(); 54 | $type = $weObj->getRev()->getRevType(); 55 | $weObj->text("hello, it's wechat")->reply(); 56 | }); 57 | ``` 58 | 59 | 60 | ##3、主要功能 61 | 62 | - 接入验证 **(初级权限)** 63 | - 自动回复(文本、图片、语音、视频、音乐、图文) **(初级权限)** 64 | - 菜单操作(查询、创建、删除) **(菜单权限)** 65 | - 客服消息(文本、图片、语音、视频、音乐、图文) **(认证权限)** 66 | - 二维码(创建临时、永久二维码,获取二维码URL) **(服务号、认证权限)** 67 | - 长链接转短链接接口 **(服务号、认证权限)** 68 | - 分组操作(查询、创建、修改、移动用户到分组) **(认证权限)** 69 | - 网页授权(基本授权,用户信息授权) **(服务号、认证权限)** 70 | - 用户信息(查询用户基本信息、获取关注者列表) **(认证权限)** 71 | - 多客服功能(客服管理、获取客服记录、客服会话管理) **(认证权限)** 72 | - 媒体文件(上传、获取) **(认证权限)** 73 | - 调用地址组件 **(支付权限)** 74 | - 生成订单签名数据 **(支付权限)** 75 | - 订单成功回调 **(支付权限)** 76 | - 发货通知 **(支付权限)** 77 | - 支付订单查询 **(支付权限)** 78 | - 高级群发 **(认证权限)** 79 | - 模板消息(设置所属行业、添加模板、发送模板消息) **(服务号、认证权限)** 80 | - 卡券管理(创建、修改、删除、发放、门店管理等) **(认证权限)** 81 | - 语义理解 **(服务号、认证权限)** 82 | - 获取微信服务器IP列表 **(初级权限)** 83 | 84 | ##4、被动接口方法 85 | 86 | * valid() 验证连接,被动接口处于加密模式时必须调用 87 | * getRev() 获取微信服务器发来信息(不返回结果),被动接口必须调用 88 | * getRevData() 返回微信服务器发来的信息(数组) 89 | * getRevFrom() 返回消息发送者的userid 90 | * getRevTo() 返回消息接收者的id(即公众号id) 91 | * getRevType() 返回接收消息的类型 92 | * getRevID() 返回消息id 93 | * getRevCtime() 返回消息发送事件 94 | * getRevContent() 返回消息内容正文或语音识别结果(文本型) 95 | * getRevPic() 返回图片信息(图片型信息) 返回数组{'mediaid'=>'','picurl'=>''} 96 | * getRevLink() 接收消息链接(链接型信息) 返回数组{'url'=>'','title'=>'','description'=>''} 97 | * getRevGeo() 返回地理位置(位置型信息) 返回数组{'x'=>'','y'=>'','scale'=>'','label'=>''} 98 | * getRevEventGeo() 返回事件地理位置(事件型信息) 返回数组{'x'=>'','y'=>'','precision'=>''} 99 | * getRevEvent() 返回事件类型(事件型信息) 返回数组{'event'=>'','key'=>''} 100 | * getRevScanInfo() 获取自定义菜单的扫码推事件信息,事件类型为`scancode_push`或`scancode_waitmsg` 返回数组array ('ScanType'=>'qrcode','ScanResult'=>'123123') 101 | * getRevSendPicsInfo() 获取自定义菜单的图片发送事件信息,事件类型为`pic_sysphoto`或`pic_photo_or_album`或`pic_weixin` 数组结构见php文件内方法说明 102 | * getRevSendGeoInfo() 获取自定义菜单的地理位置选择器事件推送,事件类型为`location_select` 数组结构见php文件内方法说明 103 | * getRevVoice() 返回语音信息(语音型信息) 返回数组{'mediaid'=>'','format'=>''} 104 | * getRevVideo() 返回视频信息(视频型信息) 返回数组{'mediaid'=>'','thumbmediaid'=>''} 105 | * getRevTicket() 返回接收TICKET(扫描带参数二维码,关注或SCAN事件) 返回二维码的ticket值 106 | * getRevSceneId() 返回二维码的场景值(扫描带参数二维码的关注事件) 返回二维码的参数值 107 | * getRevTplMsgID() 返回主动推送的消息ID(群发或模板消息事件) 返回MsgID值 108 | * getRevStatus() 返回模板消息发送状态(模板消息事件) 返回文本:success(成功)|failed:user block(用户拒绝接收)|failed: system failed(发送失败(非用户拒绝)) 109 | * getRevResult() 返回群发或模板消息发送结果(群发或模板消息事件) 返回数组,内容依事件类型而不同,参考开发文档中群发、模板消息推送事件 110 | * getRevKFCreate() 返回多客服-接入会话的客服账号(多客服-接入会话事件) 返回文本型 111 | * getRevKFClose() 返回多客服-处理会话的客服账号(多客服-接入会话事件) 返回文本型 112 | * getRevKFSwitch() 返回多客服-转接会话信息(多客服-转接会话事件) 返回数组 {'FromKfAccount' => '','ToKfAccount' => ''} 113 | * getRevCardPass() 返回卡券-审核通过的卡券ID(卡券-卡券审核事件) 返回文本型 114 | * getRevCardGet() 返回卡券-用户领取卡券的相关信息(卡券-领取卡券事件) 返回数组{'CardId' => '','IsGiveByFriend' => '','UserCardCode' => ''} 115 | * getRevCardDel() 返回卡券-用户删除卡券的相关信息(卡券-删除卡券事件) 返回数组{'CardId' => '','UserCardCode' => ''} 116 | * 117 | * text($text) 设置文本型消息,参数:文本内容 118 | * image($mediaid) 设置图片型消息,参数:图片的media_id 119 | * voice($mediaid) 设置语音型消息,参数:语音的media_id 120 | * video($mediaid='',$title,$description) 设置视频型消息,参数:视频的media_id、标题、摘要 121 | * music($title,$desc,$musicurl,$hgmusicurl='',$thumbmediaid='') 设置回复音乐,参数:音乐标题、音乐描述、音乐链接、高音质链接、缩略图的媒体id 122 | * news($newsData) 设置图文型消息,参数:数组。数组结构见php文件内方法说明 123 | * image($mediaid) 设置图片型消息,参数:图片的media_id 124 | * Message($msg = '',$append = false) 设置发送的消息(一般不需要调用这个方法) 125 | * transfer_customer_service($customer_account = '') 转接多客服,如不指定客服可不提供参数,参数:指定客服的账号 126 | * reply() 将以上已经设置好的消息,回复给微信服务器 127 | 128 | ##5、主动接口方法: 129 | 130 | * checkAuth($appid,$appsecret,$token) 此处传入公众后台高级接口提供的appid和appsecret, 或者手动指定$token为access_token。函数将返回access_token操作令牌 131 | * resetAuth($appid='') 删除验证数据 132 | * resetJsTicket($appid='') 删除JSAPI授权TICKET 133 | * getJsTicket($appid='',$jsapi_ticket='') 获取JSAPI授权TICKET 134 | * getJsSign($url, $timeStamp, $nonceStr, $appid='') 获取JsApi使用签名 135 | * createMenu($data) 创建菜单 $data菜单结构详见 **[自定义菜单创建接口](http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口)** 136 | * getServerIp() 获取微信服务器IP地址列表 返回数组array('127.0.0.1','127.0.0.1') 137 | * getMenu() 获取菜单 138 | * deleteMenu() 删除菜单 139 | * uploadMedia($data, $type) 上传多媒体文件(注意上传大文件时可能需要先调用 set_time_limit(0) 避免超时) 140 | * getMedia() 获取接收到的音频、视频媒体文件 141 | * uploadMpVideo($data) 上传视频素材,当需要群发视频时,必须使用此方法得到的MediaID,否则无法显示 142 | * uploadArticles($data) 上传图文消息素材 143 | * sendMassMessage($data) 高级群发消息 144 | * sendGroupMassMessage($data) 高级群发消息(全体或分组群发) 145 | * deleteMassMessage($msg_id) 删除群发图文消息 146 | * previewMassMessage($data) 预览群发消息 147 | * queryMassMessage($msg_id) 查询群发消息发送状态 148 | * getQRCode($scene_id,$type=0,$expire=1800) 获取推广二维码ticket字串 149 | * getQRUrl($ticket) 获取二维码图片地址 150 | * getShortUrl($long_url) 长链接转短链接接口 151 | * getUserList($next_openid) 批量获取关注用户列表 152 | * getUserInfo($openid) 获取关注者详细信息 153 | * updateUserRemark($openid,$remark) 设置用户备注名 154 | * getGroup() 获取用户分组列表 155 | * getUserGroup($openid) 获取用户所在分组 156 | * createGroup($name) 新增自定分组 157 | * updateGroup($groupid,$name) 更改分组名称 158 | * updateGroupMembers($groupid,$openid) 移动用户分组 159 | * sendCustomMessage($data) 发送客服消息 160 | * getOauthRedirect($callback,$state,$scope) 获取网页授权oAuth跳转地址 161 | * getOauthAccessToken() 通过回调的code获取网页授权access_token 162 | * getOauthRefreshToken($refresh_token) 通过refresh_token对access_token续期 163 | * getOauthUserinfo($access_token,$openid) 通过网页授权的access_token获取用户资料 164 | * getOauthAuth($access_token,$openid) 检验授权凭证access_token是否有效 165 | * getSignature($arrdata,'sha1') 生成签名字串 166 | * generateNonceStr($length) 获取随机字串 167 | * createPackage($out_trade_no,$body,$total_fee,$notify_url,$spbill_create_ip,$fee_type=1,$bank_type="WX",$input_charset="UTF-8",$time_start="",$time_expire="",$transport_fee="",$product_fee="",$goods_tag="",$attach="") 生成订单package字符串 168 | * getPaySign($package, $timeStamp, $nonceStr) 支付签名(paySign)生成方法 169 | * checkOrderSignature($orderxml='') 回调通知签名验证 170 | * sendPayDeliverNotify($openid,$transid,$out_trade_no,$status=1,$msg='ok') 发货通知 171 | * getPayOrder($out_trade_no) 查询订单信息 172 | * getAddrSign($url, $timeStamp, $nonceStr, $user_token='') 获取收货地址JS的签名 173 | * setTMIndustry($id1,$id2='') 模板消息,设置所属行业 174 | * addTemplateMessage($tpl_id) 模板消息,添加消息模板 175 | * sendTemplateMessage($data) 发送模板消息 176 | * getCustomServiceMessage($data) 获取多客服会话记录 177 | * transfer_customer_service($customer_account) 转发多客服消息 178 | * getCustomServiceKFlist() 获取多客服客服基本信息 179 | * getCustomServiceOnlineKFlist() 获取多客服在线客服接待信息 180 | * createKFSession($openid,$kf_account,$text='') 创建指定多客服会话 181 | * closeKFSession($openid,$kf_account,$text='') 关闭指定多客服会话 182 | * getKFSession($openid) 获取用户会话状态 183 | * getKFSessionlist($kf_account) 获取指定客服的会话列表 184 | * getKFSessionWait() 获取未接入会话列表 185 | * addKFAccount($account,$nickname,$password) 添加客服账号 186 | * updateKFAccount($account,$nickname,$password) 修改客服账号信息 187 | * deleteKFAccount($account) 删除客服账号 188 | * setKFHeadImg($account,$imgfile) 上传客服头像 189 | * querySemantic($uid,$query,$category,$latitude=0,$longitude=0,$city="",$region="") 语义理解接口 参数含义及返回的json内容请查看 **[微信语义理解接口](http://mp.weixin.qq.com/wiki/index.php?title=语义理解)** 190 | * createCard($data) 创建卡券 191 | * updateCard($data) 修改卡券 192 | * delCard($card_id) 删除卡券 193 | * getCardInfo($card_id) 查询卡券详情 194 | * getCardColors() 获取颜色列表 195 | * getCardLocations() 拉取门店列表 196 | * addCardLocations($data) 批量导入门店信息 197 | * createCardQrcode($card_id) 生成卡券二维码 198 | * consumeCardCode($code) 消耗 code 199 | * decryptCardCode($encrypt_code) code 解码 200 | * checkCardCode($code) 获取 code 的有效性 201 | * getCardIdList($data) 批量查询卡列表 202 | * updateCardCode($code,$code_id,$new_code) 更改 code 203 | * unavailableCardCode($code) 设置卡券失效**(不可逆)** 204 | * modifyCardStock($data) 库存修改 205 | * activateMemberCard($data) 激活/绑定会员卡,参数结构请参看卡券开发文档(6.1.1 激活/绑定会员卡)章节 206 | * updateMemberCard($data) 会员卡交易,参数结构请参看卡券开发文档(6.1.2 会员卡交易)章节 207 | * updateLuckyMoney($code,$balance,$card_id='') 更新红包金额 208 | * setCardTestWhiteList($openid=array(),$user=array()) 设置卡券测试白名单 209 | 210 | 211 | ##6、环境 212 | PHP >= 5.4 213 | Laravel >= 4.2 214 | 215 | ##7、License 216 | This is free software distributed under the terms of the MIT license 217 | 感谢 [dodgepudding/wechat-php-sdk](https://github.com/dodgepudding/wechat-php-sdk) 和 [huanghua581/laravel-wechat-sdk](https://github.com/huanghua581/laravel-wechat-sdk) 218 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atan/wechat", 3 | "keywords": ["laravel", "weixin", "wechat"], 4 | "license": "MIT", 5 | "description": "Wechat SDK for Laravel 4", 6 | "authors": [ 7 | { 8 | "name": "atan", 9 | "email": "tanshiqi@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.4.0", 14 | "illuminate/support": "4.2.*" 15 | }, 16 | "autoload": { 17 | "psr-0": { 18 | "Atan\\Wechat\\": "src/" 19 | } 20 | }, 21 | "minimum-stability": "stable" 22 | } 23 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Atan/Wechat/Facades/Wechat.php: -------------------------------------------------------------------------------- 1 | array( 119 | 'summary' => '/datacube/getusersummary?', //获取用户增减数据(getusersummary) 120 | 'cumulate' => '/datacube/getusercumulate?', //获取累计用户数据(getusercumulate) 121 | ), 122 | 'article' => array( //图文分析 123 | 'summary' => '/datacube/getarticlesummary?', //获取图文群发每日数据(getarticlesummary) 124 | 'total' => '/datacube/getarticletotal?', //获取图文群发总数据(getarticletotal) 125 | 'read' => '/datacube/getuserread?', //获取图文统计数据(getuserread) 126 | 'readhour' => '/datacube/getuserreadhour?', //获取图文统计分时数据(getuserreadhour) 127 | 'share' => '/datacube/getusershare?', //获取图文分享转发数据(getusershare) 128 | 'sharehour' => '/datacube/getusersharehour?', //获取图文分享转发分时数据(getusersharehour) 129 | ), 130 | 'upstreammsg' => array( //消息分析 131 | 'summary' => '/datacube/getupstreammsg?', //获取消息发送概况数据(getupstreammsg) 132 | 'hour' => '/datacube/getupstreammsghour?', //获取消息分送分时数据(getupstreammsghour) 133 | 'week' => '/datacube/getupstreammsgweek?', //获取消息发送周数据(getupstreammsgweek) 134 | 'month' => '/datacube/getupstreammsgmonth?', //获取消息发送月数据(getupstreammsgmonth) 135 | 'dist' => '/datacube/getupstreammsgdist?', //获取消息发送分布数据(getupstreammsgdist) 136 | 'distweek' => '/datacube/getupstreammsgdistweek?', //获取消息发送分布周数据(getupstreammsgdistweek) 137 | 'distmonth' => '/datacube/getupstreammsgdistmonth?', //获取消息发送分布月数据(getupstreammsgdistmonth) 138 | ), 139 | 'interface' => array( //接口分析 140 | 'summary' => '/datacube/getinterfacesummary?', //获取接口分析数据(getinterfacesummary) 141 | 'summaryhour' => '/datacube/getinterfacesummaryhour?', //获取接口分析分时数据(getinterfacesummaryhour) 142 | ) 143 | ); 144 | 145 | 146 | private $token; 147 | private $encodingAesKey; 148 | private $encrypt_type; 149 | private $appid; 150 | private $appsecret; 151 | private $access_token; 152 | private $jsapi_ticket; 153 | private $user_token; 154 | private $partnerid; 155 | private $partnerkey; 156 | private $paysignkey; 157 | private $postxml; 158 | private $_msg; 159 | private $_funcflag = false; 160 | private $_receive; 161 | private $_text_filter = true; 162 | public $debug = false; 163 | public $errCode = 40001; 164 | public $errMsg = "no access"; 165 | public $logcallback; 166 | 167 | public function __construct($options = 'default') 168 | { 169 | $opt = is_array($options)?$options:Config::get('wechat::wechat.'.$options); 170 | $this->token = isset($opt['token'])?$opt['token']:''; 171 | $this->encodingAesKey = isset($opt['encodingaeskey'])?$opt['encodingaeskey']:''; 172 | $this->appid = isset($opt['appid'])?$opt['appid']:''; 173 | $this->appsecret = isset($opt['appsecret'])?$opt['appsecret']:''; 174 | $this->debug = isset($opt['debug'])?$opt['debug']:false; 175 | $this->logcallback = isset($opt['logcallback'])?$opt['logcallback']:false; 176 | } 177 | 178 | /** 179 | * For weixin server validation 180 | */ 181 | private function checkSignature($str='') 182 | { 183 | $signature = isset($_GET["signature"])?$_GET["signature"]:''; 184 | $signature = isset($_GET["msg_signature"])?$_GET["msg_signature"]:$signature; //如果存在加密验证则用加密验证段 185 | $timestamp = isset($_GET["timestamp"])?$_GET["timestamp"]:''; 186 | $nonce = isset($_GET["nonce"])?$_GET["nonce"]:''; 187 | 188 | $token = $this->token; 189 | $tmpArr = array($token, $timestamp, $nonce,$str); 190 | sort($tmpArr, SORT_STRING); 191 | $tmpStr = implode( $tmpArr ); 192 | $tmpStr = sha1( $tmpStr ); 193 | 194 | if( $tmpStr == $signature ){ 195 | return true; 196 | }else{ 197 | return false; 198 | } 199 | } 200 | 201 | /** 202 | * For weixin server validation 203 | * @param bool $return 是否返回 204 | */ 205 | public function valid($return=false) 206 | { 207 | $encryptStr=""; 208 | if ($_SERVER['REQUEST_METHOD'] == "POST") { 209 | $postStr = file_get_contents("php://input"); 210 | $array = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA); 211 | $this->encrypt_type = isset($_GET["encrypt_type"]) ? $_GET["encrypt_type"]: ''; 212 | if ($this->encrypt_type == 'aes') { //aes加密 213 | $this->log($postStr); 214 | $encryptStr = $array['Encrypt']; 215 | $pc = new Prpcrypt($this->encodingAesKey); 216 | $array = $pc->decrypt($encryptStr,$this->appid); 217 | if (!isset($array[0]) || ($array[0] != 0)) { 218 | if (!$return) { 219 | die('decrypt error!'); 220 | } else { 221 | return false; 222 | } 223 | } 224 | $this->postxml = $array[1]; 225 | if (!$this->appid) 226 | $this->appid = $array[2];//为了没有appid的订阅号。 227 | } else { 228 | $this->postxml = $postStr; 229 | } 230 | } elseif (isset($_GET["echostr"])) { 231 | $echoStr = $_GET["echostr"]; 232 | if ($return) { 233 | if ($this->checkSignature()) 234 | return $echoStr; 235 | else 236 | return false; 237 | } else { 238 | if ($this->checkSignature()) 239 | die($echoStr); 240 | else 241 | die('no access'); 242 | } 243 | } 244 | 245 | if (!$this->checkSignature($encryptStr)) { 246 | if ($return) 247 | return false; 248 | else 249 | die('no access'); 250 | } 251 | return true; 252 | } 253 | 254 | /** 255 | * 设置发送消息 256 | * @param array $msg 消息数组 257 | * @param bool $append 是否在原消息数组追加 258 | */ 259 | public function Message($msg = '',$append = false){ 260 | if (is_null($msg)) { 261 | $this->_msg =array(); 262 | }elseif (is_array($msg)) { 263 | if ($append) 264 | $this->_msg = array_merge($this->_msg,$msg); 265 | else 266 | $this->_msg = $msg; 267 | return $this->_msg; 268 | } else { 269 | return $this->_msg; 270 | } 271 | } 272 | 273 | /** 274 | * 设置消息的星标标志,官方已取消对此功能的支持 275 | */ 276 | public function setFuncFlag($flag) { 277 | $this->_funcflag = $flag; 278 | return $this; 279 | } 280 | 281 | /** 282 | * 日志记录,可被重载。 283 | * @param mixed $log 输入日志 284 | * @return mixed 285 | */ 286 | protected function log($log){ 287 | if ($this->debug && function_exists($this->logcallback)) { 288 | if (is_array($log)) $log = print_r($log,true); 289 | return call_user_func($this->logcallback,$log); 290 | } 291 | } 292 | 293 | /** 294 | * 获取微信服务器发来的信息 295 | */ 296 | public function getRev() 297 | { 298 | if ($this->_receive) return $this; 299 | $postStr = !empty($this->postxml)?$this->postxml:file_get_contents("php://input"); 300 | //兼顾使用明文又不想调用valid()方法的情况 301 | $this->log($postStr); 302 | if (!empty($postStr)) { 303 | $this->_receive = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA); 304 | } 305 | return $this; 306 | } 307 | 308 | /** 309 | * 获取微信服务器发来的信息 310 | */ 311 | public function getRevData() 312 | { 313 | return $this->_receive; 314 | } 315 | 316 | /** 317 | * 获取消息发送者 318 | */ 319 | public function getRevFrom() { 320 | if (isset($this->_receive['FromUserName'])) 321 | return $this->_receive['FromUserName']; 322 | else 323 | return false; 324 | } 325 | 326 | /** 327 | * 获取消息接受者 328 | */ 329 | public function getRevTo() { 330 | if (isset($this->_receive['ToUserName'])) 331 | return $this->_receive['ToUserName']; 332 | else 333 | return false; 334 | } 335 | 336 | /** 337 | * 获取接收消息的类型 338 | */ 339 | public function getRevType() { 340 | if (isset($this->_receive['MsgType'])) 341 | return $this->_receive['MsgType']; 342 | else 343 | return false; 344 | } 345 | 346 | /** 347 | * 获取消息ID 348 | */ 349 | public function getRevID() { 350 | if (isset($this->_receive['MsgId'])) 351 | return $this->_receive['MsgId']; 352 | else 353 | return false; 354 | } 355 | 356 | /** 357 | * 获取消息发送时间 358 | */ 359 | public function getRevCtime() { 360 | if (isset($this->_receive['CreateTime'])) 361 | return $this->_receive['CreateTime']; 362 | else 363 | return false; 364 | } 365 | 366 | /** 367 | * 获取接收消息内容正文 368 | */ 369 | public function getRevContent(){ 370 | if (isset($this->_receive['Content'])) 371 | return $this->_receive['Content']; 372 | else if (isset($this->_receive['Recognition'])) //获取语音识别文字内容,需申请开通 373 | return $this->_receive['Recognition']; 374 | else 375 | return false; 376 | } 377 | 378 | /** 379 | * 获取接收消息图片 380 | */ 381 | public function getRevPic(){ 382 | if (isset($this->_receive['PicUrl'])) 383 | return array( 384 | 'mediaid'=>$this->_receive['MediaId'], 385 | 'picurl'=>(string)$this->_receive['PicUrl'], //防止picurl为空导致解析出错 386 | ); 387 | else 388 | return false; 389 | } 390 | 391 | /** 392 | * 获取接收消息链接 393 | */ 394 | public function getRevLink(){ 395 | if (isset($this->_receive['Url'])){ 396 | return array( 397 | 'url'=>$this->_receive['Url'], 398 | 'title'=>$this->_receive['Title'], 399 | 'description'=>$this->_receive['Description'] 400 | ); 401 | } else 402 | return false; 403 | } 404 | 405 | /** 406 | * 获取接收地理位置 407 | */ 408 | public function getRevGeo(){ 409 | if (isset($this->_receive['Location_X'])){ 410 | return array( 411 | 'x'=>$this->_receive['Location_X'], 412 | 'y'=>$this->_receive['Location_Y'], 413 | 'scale'=>$this->_receive['Scale'], 414 | 'label'=>$this->_receive['Label'] 415 | ); 416 | } else 417 | return false; 418 | } 419 | 420 | /** 421 | * 获取上报地理位置事件 422 | */ 423 | public function getRevEventGeo(){ 424 | if (isset($this->_receive['Latitude'])){ 425 | return array( 426 | 'x'=>$this->_receive['Latitude'], 427 | 'y'=>$this->_receive['Longitude'], 428 | 'precision'=>$this->_receive['Precision'], 429 | ); 430 | } else 431 | return false; 432 | } 433 | 434 | /** 435 | * 获取接收事件推送 436 | */ 437 | public function getRevEvent(){ 438 | if (isset($this->_receive['Event'])){ 439 | $array['event'] = $this->_receive['Event']; 440 | } 441 | if (isset($this->_receive['EventKey'])){ 442 | $array['key'] = $this->_receive['EventKey']; 443 | } 444 | if (isset($array) && count($array) > 0) { 445 | return $array; 446 | } else { 447 | return false; 448 | } 449 | } 450 | 451 | /** 452 | * 获取自定义菜单的扫码推事件信息 453 | * 454 | * 事件类型为以下两种时则调用此方法有效 455 | * Event 事件类型,scancode_push 456 | * Event 事件类型,scancode_waitmsg 457 | * 458 | * @return: array | false 459 | * array ( 460 | * 'ScanType'=>'qrcode', 461 | * 'ScanResult'=>'123123' 462 | * ) 463 | */ 464 | public function getRevScanInfo(){ 465 | if (isset($this->_receive['ScanCodeInfo'])){ 466 | if (!is_array($this->_receive['SendPicsInfo'])) { 467 | $array=(array)$this->_receive['ScanCodeInfo']; 468 | $this->_receive['ScanCodeInfo']=$array; 469 | }else { 470 | $array=$this->_receive['ScanCodeInfo']; 471 | } 472 | } 473 | if (isset($array) && count($array) > 0) { 474 | return $array; 475 | } else { 476 | return false; 477 | } 478 | } 479 | 480 | /** 481 | * 获取自定义菜单的图片发送事件信息 482 | * 483 | * 事件类型为以下三种时则调用此方法有效 484 | * Event 事件类型,pic_sysphoto 弹出系统拍照发图的事件推送 485 | * Event 事件类型,pic_photo_or_album 弹出拍照或者相册发图的事件推送 486 | * Event 事件类型,pic_weixin 弹出微信相册发图器的事件推送 487 | * 488 | * @return: array | false 489 | * array ( 490 | * 'Count' => '2', 491 | * 'PicList' =>array ( 492 | * 'item' =>array ( 493 | * 0 =>array ('PicMd5Sum' => 'aaae42617cf2a14342d96005af53624c'), 494 | * 1 =>array ('PicMd5Sum' => '149bd39e296860a2adc2f1bb81616ff8'), 495 | * ), 496 | * ), 497 | * ) 498 | * 499 | */ 500 | public function getRevSendPicsInfo(){ 501 | if (isset($this->_receive['SendPicsInfo'])){ 502 | if (!is_array($this->_receive['SendPicsInfo'])) { 503 | $array=(array)$this->_receive['SendPicsInfo']; 504 | if (isset($array['PicList'])){ 505 | $array['PicList']=(array)$array['PicList']; 506 | $item=$array['PicList']['item']; 507 | $array['PicList']['item']=array(); 508 | foreach ( $item as $key => $value ){ 509 | $array['PicList']['item'][$key]=(array)$value; 510 | } 511 | } 512 | $this->_receive['SendPicsInfo']=$array; 513 | } else { 514 | $array=$this->_receive['SendPicsInfo']; 515 | } 516 | } 517 | if (isset($array) && count($array) > 0) { 518 | return $array; 519 | } else { 520 | return false; 521 | } 522 | } 523 | 524 | /** 525 | * 获取自定义菜单的地理位置选择器事件推送 526 | * 527 | * 事件类型为以下时则可以调用此方法有效 528 | * Event 事件类型,location_select 弹出地理位置选择器的事件推送 529 | * 530 | * @return: array | false 531 | * array ( 532 | * 'Location_X' => '33.731655000061', 533 | * 'Location_Y' => '113.29955200008047', 534 | * 'Scale' => '16', 535 | * 'Label' => '某某市某某区某某路', 536 | * 'Poiname' => '', 537 | * ) 538 | * 539 | */ 540 | public function getRevSendGeoInfo(){ 541 | if (isset($this->_receive['SendLocationInfo'])){ 542 | if (!is_array($this->_receive['SendLocationInfo'])) { 543 | $array=(array)$this->_receive['SendLocationInfo']; 544 | if (empty($array['Poiname'])) { 545 | $array['Poiname']=""; 546 | } 547 | if (empty($array['Label'])) { 548 | $array['Label']=""; 549 | } 550 | $this->_receive['SendLocationInfo']=$array; 551 | } else { 552 | $array=$this->_receive['SendLocationInfo']; 553 | } 554 | } 555 | if (isset($array) && count($array) > 0) { 556 | return $array; 557 | } else { 558 | return false; 559 | } 560 | } 561 | 562 | /** 563 | * 获取接收语音推送 564 | */ 565 | public function getRevVoice(){ 566 | if (isset($this->_receive['MediaId'])){ 567 | return array( 568 | 'mediaid'=>$this->_receive['MediaId'], 569 | 'format'=>$this->_receive['Format'], 570 | ); 571 | } else 572 | return false; 573 | } 574 | 575 | /** 576 | * 获取接收视频推送 577 | */ 578 | public function getRevVideo(){ 579 | if (isset($this->_receive['MediaId'])){ 580 | return array( 581 | 'mediaid'=>$this->_receive['MediaId'], 582 | 'thumbmediaid'=>$this->_receive['ThumbMediaId'] 583 | ); 584 | } else 585 | return false; 586 | } 587 | 588 | /** 589 | * 获取接收TICKET 590 | */ 591 | public function getRevTicket(){ 592 | if (isset($this->_receive['Ticket'])){ 593 | return $this->_receive['Ticket']; 594 | } else 595 | return false; 596 | } 597 | 598 | /** 599 | * 获取二维码的场景值 600 | */ 601 | public function getRevSceneId (){ 602 | if (isset($this->_receive['EventKey'])){ 603 | return str_replace('qrscene_','',$this->_receive['EventKey']); 604 | } else{ 605 | return false; 606 | } 607 | } 608 | 609 | /** 610 | * 获取主动推送的消息ID 611 | * 经过验证,这个和普通的消息MsgId不一样 612 | * 当Event为 MASSSENDJOBFINISH 或 TEMPLATESENDJOBFINISH 613 | */ 614 | public function getRevTplMsgID(){ 615 | if (isset($this->_receive['MsgID'])){ 616 | return $this->_receive['MsgID']; 617 | } else 618 | return false; 619 | } 620 | 621 | /** 622 | * 获取模板消息发送状态 623 | */ 624 | public function getRevStatus(){ 625 | if (isset($this->_receive['Status'])){ 626 | return $this->_receive['Status']; 627 | } else 628 | return false; 629 | } 630 | 631 | /** 632 | * 获取群发或模板消息发送结果 633 | * 当Event为 MASSSENDJOBFINISH 或 TEMPLATESENDJOBFINISH,即高级群发/模板消息 634 | */ 635 | public function getRevResult(){ 636 | if (isset($this->_receive['Status'])) //发送是否成功,具体的返回值请参考 高级群发/模板消息 的事件推送说明 637 | $array['Status'] = $this->_receive['Status']; 638 | if (isset($this->_receive['MsgID'])) //发送的消息id 639 | $array['MsgID'] = $this->_receive['MsgID']; 640 | 641 | //以下仅当群发消息时才会有的事件内容 642 | if (isset($this->_receive['TotalCount'])) //分组或openid列表内粉丝数量 643 | $array['TotalCount'] = $this->_receive['TotalCount']; 644 | if (isset($this->_receive['FilterCount'])) //过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数 645 | $array['FilterCount'] = $this->_receive['FilterCount']; 646 | if (isset($this->_receive['SentCount'])) //发送成功的粉丝数 647 | $array['SentCount'] = $this->_receive['SentCount']; 648 | if (isset($this->_receive['ErrorCount'])) //发送失败的粉丝数 649 | $array['ErrorCount'] = $this->_receive['ErrorCount']; 650 | if (isset($array) && count($array) > 0) { 651 | return $array; 652 | } else { 653 | return false; 654 | } 655 | } 656 | 657 | /** 658 | * 获取多客服会话状态推送事件 - 接入会话 659 | * 当Event为 kfcreatesession 即接入会话 660 | * @return string | boolean 返回分配到的客服 661 | */ 662 | public function getRevKFCreate(){ 663 | if (isset($this->_receive['KfAccount'])){ 664 | return $this->_receive['KfAccount']; 665 | } else 666 | return false; 667 | } 668 | 669 | /** 670 | * 获取多客服会话状态推送事件 - 关闭会话 671 | * 当Event为 kfclosesession 即关闭会话 672 | * @return string | boolean 返回分配到的客服 673 | */ 674 | public function getRevKFClose(){ 675 | if (isset($this->_receive['KfAccount'])){ 676 | return $this->_receive['KfAccount']; 677 | } else 678 | return false; 679 | } 680 | 681 | /** 682 | * 获取多客服会话状态推送事件 - 转接会话 683 | * 当Event为 kfswitchsession 即转接会话 684 | * @return array | boolean 返回分配到的客服 685 | * { 686 | * 'FromKfAccount' => '', //原接入客服 687 | * 'ToKfAccount' => '' //转接到客服 688 | * } 689 | */ 690 | public function getRevKFSwitch(){ 691 | if (isset($this->_receive['FromKfAccount'])) //原接入客服 692 | $array['FromKfAccount'] = $this->_receive['FromKfAccount']; 693 | if (isset($this->_receive['ToKfAccount'])) //转接到客服 694 | $array['ToKfAccount'] = $this->_receive['ToKfAccount']; 695 | if (isset($array) && count($array) > 0) { 696 | return $array; 697 | } else { 698 | return false; 699 | } 700 | } 701 | 702 | /** 703 | * 获取卡券事件推送 - 卡卷审核是否通过 704 | * 当Event为 card_pass_check(审核通过) 或 card_not_pass_check(未通过) 705 | * @return string|boolean 返回卡券ID 706 | */ 707 | public function getRevCardPass(){ 708 | if (isset($this->_receive['CardId'])) 709 | return $this->_receive['CardId']; 710 | else 711 | return false; 712 | } 713 | 714 | /** 715 | * 获取卡券事件推送 - 领取卡券 716 | * 当Event为 user_get_card(用户领取卡券) 717 | * @return array|boolean 718 | */ 719 | public function getRevCardGet(){ 720 | if (isset($this->_receive['CardId'])) //卡券 ID 721 | $array['CardId'] = $this->_receive['CardId']; 722 | if (isset($this->_receive['IsGiveByFriend'])) //是否为转赠,1 代表是,0 代表否。 723 | $array['IsGiveByFriend'] = $this->_receive['IsGiveByFriend']; 724 | if (isset($this->_receive['UserCardCode']) && !empty($this->_receive['UserCardCode'])) //code 序列号。自定义 code 及非自定义 code的卡券被领取后都支持事件推送。 725 | $array['UserCardCode'] = $this->_receive['UserCardCode']; 726 | if (isset($array) && count($array) > 0) { 727 | return $array; 728 | } else { 729 | return false; 730 | } 731 | } 732 | 733 | /** 734 | * 获取卡券事件推送 - 删除卡券 735 | * 当Event为 user_del_card(用户删除卡券) 736 | * @return array|boolean 737 | */ 738 | public function getRevCardDel(){ 739 | if (isset($this->_receive['CardId'])) //卡券 ID 740 | $array['CardId'] = $this->_receive['CardId']; 741 | if (isset($this->_receive['UserCardCode']) && !empty($this->_receive['UserCardCode'])) //code 序列号。自定义 code 及非自定义 code的卡券被领取后都支持事件推送。 742 | $array['UserCardCode'] = $this->_receive['UserCardCode']; 743 | if (isset($array) && count($array) > 0) { 744 | return $array; 745 | } else { 746 | return false; 747 | } 748 | } 749 | 750 | public static function xmlSafeStr($str) 751 | { 752 | return ''; 753 | } 754 | 755 | /** 756 | * 数据XML编码 757 | * @param mixed $data 数据 758 | * @return string 759 | */ 760 | public static function data_to_xml($data) { 761 | $xml = ''; 762 | foreach ($data as $key => $val) { 763 | is_numeric($key) && $key = "item id=\"$key\""; 764 | $xml .= "<$key>"; 765 | $xml .= ( is_array($val) || is_object($val)) ? self::data_to_xml($val) : self::xmlSafeStr($val); 766 | list($key, ) = explode(' ', $key); 767 | $xml .= ""; 768 | } 769 | return $xml; 770 | } 771 | 772 | /** 773 | * XML编码 774 | * @param mixed $data 数据 775 | * @param string $root 根节点名 776 | * @param string $item 数字索引的子节点名 777 | * @param string $attr 根节点属性 778 | * @param string $id 数字索引子节点key转换的属性名 779 | * @param string $encoding 数据编码 780 | * @return string 781 | */ 782 | public function xml_encode($data, $root='xml', $item='item', $attr='', $id='id', $encoding='utf-8') { 783 | if(is_array($attr)){ 784 | $_attr = array(); 785 | foreach ($attr as $key => $value) { 786 | $_attr[] = "{$key}=\"{$value}\""; 787 | } 788 | $attr = implode(' ', $_attr); 789 | } 790 | $attr = trim($attr); 791 | $attr = empty($attr) ? '' : " {$attr}"; 792 | $xml = "<{$root}{$attr}>"; 793 | $xml .= self::data_to_xml($data, $item, $id); 794 | $xml .= ""; 795 | return $xml; 796 | } 797 | 798 | /** 799 | * 过滤文字回复\r\n换行符 800 | * @param string $text 801 | * @return string|mixed 802 | */ 803 | private function _auto_text_filter($text) { 804 | if (!$this->_text_filter) return $text; 805 | return str_replace("\r\n", "\n", $text); 806 | } 807 | 808 | /** 809 | * 设置回复消息 810 | * Example: $obj->text('hello')->reply(); 811 | * @param string $text 812 | */ 813 | public function text($text='') 814 | { 815 | $FuncFlag = $this->_funcflag ? 1 : 0; 816 | $msg = array( 817 | 'ToUserName' => $this->getRevFrom(), 818 | 'FromUserName'=>$this->getRevTo(), 819 | 'MsgType'=>self::MSGTYPE_TEXT, 820 | 'Content'=>$this->_auto_text_filter($text), 821 | 'CreateTime'=>time(), 822 | 'FuncFlag'=>$FuncFlag 823 | ); 824 | $this->Message($msg); 825 | return $this; 826 | } 827 | /** 828 | * 设置回复消息 829 | * Example: $obj->image('media_id')->reply(); 830 | * @param string $mediaid 831 | */ 832 | public function image($mediaid='') 833 | { 834 | $FuncFlag = $this->_funcflag ? 1 : 0; 835 | $msg = array( 836 | 'ToUserName' => $this->getRevFrom(), 837 | 'FromUserName'=>$this->getRevTo(), 838 | 'MsgType'=>self::MSGTYPE_IMAGE, 839 | 'Image'=>array('MediaId'=>$mediaid), 840 | 'CreateTime'=>time(), 841 | 'FuncFlag'=>$FuncFlag 842 | ); 843 | $this->Message($msg); 844 | return $this; 845 | } 846 | 847 | /** 848 | * 设置回复消息 849 | * Example: $obj->voice('media_id')->reply(); 850 | * @param string $mediaid 851 | */ 852 | public function voice($mediaid='') 853 | { 854 | $FuncFlag = $this->_funcflag ? 1 : 0; 855 | $msg = array( 856 | 'ToUserName' => $this->getRevFrom(), 857 | 'FromUserName'=>$this->getRevTo(), 858 | 'MsgType'=>self::MSGTYPE_VOICE, 859 | 'Voice'=>array('MediaId'=>$mediaid), 860 | 'CreateTime'=>time(), 861 | 'FuncFlag'=>$FuncFlag 862 | ); 863 | $this->Message($msg); 864 | return $this; 865 | } 866 | 867 | /** 868 | * 设置回复消息 869 | * Example: $obj->video('media_id','title','description')->reply(); 870 | * @param string $mediaid 871 | */ 872 | public function video($mediaid='',$title='',$description='') 873 | { 874 | $FuncFlag = $this->_funcflag ? 1 : 0; 875 | $msg = array( 876 | 'ToUserName' => $this->getRevFrom(), 877 | 'FromUserName'=>$this->getRevTo(), 878 | 'MsgType'=>self::MSGTYPE_VIDEO, 879 | 'Video'=>array( 880 | 'MediaId'=>$mediaid, 881 | 'Title'=>$title, 882 | 'Description'=>$description 883 | ), 884 | 'CreateTime'=>time(), 885 | 'FuncFlag'=>$FuncFlag 886 | ); 887 | $this->Message($msg); 888 | return $this; 889 | } 890 | 891 | /** 892 | * 设置回复音乐 893 | * @param string $title 894 | * @param string $desc 895 | * @param string $musicurl 896 | * @param string $hgmusicurl 897 | * @param string $thumbmediaid 音乐图片缩略图的媒体id,非必须 898 | */ 899 | public function music($title,$desc,$musicurl,$hgmusicurl='',$thumbmediaid='') { 900 | $FuncFlag = $this->_funcflag ? 1 : 0; 901 | $msg = array( 902 | 'ToUserName' => $this->getRevFrom(), 903 | 'FromUserName'=>$this->getRevTo(), 904 | 'CreateTime'=>time(), 905 | 'MsgType'=>self::MSGTYPE_MUSIC, 906 | 'Music'=>array( 907 | 'Title'=>$title, 908 | 'Description'=>$desc, 909 | 'MusicUrl'=>$musicurl, 910 | 'HQMusicUrl'=>$hgmusicurl 911 | ), 912 | 'FuncFlag'=>$FuncFlag 913 | ); 914 | if ($thumbmediaid) { 915 | $msg['Music']['ThumbMediaId'] = $thumbmediaid; 916 | } 917 | $this->Message($msg); 918 | return $this; 919 | } 920 | 921 | /** 922 | * 设置回复图文 923 | * @param array $newsData 924 | * 数组结构: 925 | * array( 926 | * "0"=>array( 927 | * 'Title'=>'msg title', 928 | * 'Description'=>'summary text', 929 | * 'PicUrl'=>'http://www.domain.com/1.jpg', 930 | * 'Url'=>'http://www.domain.com/1.html' 931 | * ), 932 | * "1"=>.... 933 | * ) 934 | */ 935 | public function news($newsData=array()) 936 | { 937 | $FuncFlag = $this->_funcflag ? 1 : 0; 938 | $count = count($newsData); 939 | 940 | $msg = array( 941 | 'ToUserName' => $this->getRevFrom(), 942 | 'FromUserName'=>$this->getRevTo(), 943 | 'MsgType'=>self::MSGTYPE_NEWS, 944 | 'CreateTime'=>time(), 945 | 'ArticleCount'=>$count, 946 | 'Articles'=>$newsData, 947 | 'FuncFlag'=>$FuncFlag 948 | ); 949 | $this->Message($msg); 950 | return $this; 951 | } 952 | 953 | /** 954 | * 955 | * 回复微信服务器, 此函数支持链式操作 956 | * Example: $this->text('msg tips')->reply(); 957 | * @param string $msg 要发送的信息, 默认取$this->_msg 958 | * @param bool $return 是否返回信息而不抛出到浏览器 默认:否 959 | */ 960 | public function reply($msg=array(),$return = false) 961 | { 962 | if (empty($msg)) { 963 | if (empty($this->_msg)) //防止不先设置回复内容,直接调用reply方法导致异常 964 | return false; 965 | $msg = $this->_msg; 966 | } 967 | $xmldata= $this->xml_encode($msg); 968 | $this->log($xmldata); 969 | if ($this->encrypt_type == 'aes') { //如果来源消息为加密方式 970 | $pc = new Prpcrypt($this->encodingAesKey); 971 | $array = $pc->encrypt($xmldata, $this->appid); 972 | $ret = $array[0]; 973 | if ($ret != 0) { 974 | $this->log('encrypt err!'); 975 | return false; 976 | } 977 | $timestamp = time(); 978 | $nonce = rand(77,999)*rand(605,888)*rand(11,99); 979 | $encrypt = $array[1]; 980 | $tmpArr = array($this->token, $timestamp, $nonce,$encrypt);//比普通公众平台多了一个加密的密文 981 | sort($tmpArr, SORT_STRING); 982 | $signature = implode($tmpArr); 983 | $signature = sha1($signature); 984 | $xmldata = $this->generate($encrypt, $signature, $timestamp, $nonce); 985 | $this->log($xmldata); 986 | } 987 | if ($return) 988 | return $xmldata; 989 | else 990 | echo $xmldata; 991 | } 992 | 993 | /** 994 | * xml格式加密,仅请求为加密方式时再用 995 | */ 996 | private function generate($encrypt, $signature, $timestamp, $nonce) 997 | { 998 | //格式化加密信息 999 | $format = " 1000 | 1001 | 1002 | %s 1003 | 1004 | "; 1005 | return sprintf($format, $encrypt, $signature, $timestamp, $nonce); 1006 | } 1007 | 1008 | /** 1009 | * GET 请求 1010 | * @param string $url 1011 | */ 1012 | private function http_get($url){ 1013 | $oCurl = curl_init(); 1014 | if(stripos($url,"https://")!==FALSE){ 1015 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE); 1016 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE); 1017 | curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1 1018 | } 1019 | curl_setopt($oCurl, CURLOPT_URL, $url); 1020 | curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 ); 1021 | $sContent = curl_exec($oCurl); 1022 | $aStatus = curl_getinfo($oCurl); 1023 | curl_close($oCurl); 1024 | if(intval($aStatus["http_code"])==200){ 1025 | return $sContent; 1026 | }else{ 1027 | return false; 1028 | } 1029 | } 1030 | 1031 | /** 1032 | * POST 请求 1033 | * @param string $url 1034 | * @param array $param 1035 | * @param boolean $post_file 是否文件上传 1036 | * @return string content 1037 | */ 1038 | private function http_post($url,$param,$post_file=false){ 1039 | $oCurl = curl_init(); 1040 | if(stripos($url,"https://")!==FALSE){ 1041 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE); 1042 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false); 1043 | curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1 1044 | } 1045 | if (is_string($param) || $post_file) { 1046 | $strPOST = $param; 1047 | } else { 1048 | $aPOST = array(); 1049 | foreach($param as $key=>$val){ 1050 | $aPOST[] = $key."=".urlencode($val); 1051 | } 1052 | $strPOST = join("&", $aPOST); 1053 | } 1054 | curl_setopt($oCurl, CURLOPT_URL, $url); 1055 | curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 ); 1056 | curl_setopt($oCurl, CURLOPT_POST,true); 1057 | curl_setopt($oCurl, CURLOPT_POSTFIELDS,$strPOST); 1058 | $sContent = curl_exec($oCurl); 1059 | $aStatus = curl_getinfo($oCurl); 1060 | curl_close($oCurl); 1061 | if(intval($aStatus["http_code"])==200){ 1062 | return $sContent; 1063 | }else{ 1064 | return false; 1065 | } 1066 | } 1067 | 1068 | /** 1069 | * 设置缓存,按需重载 1070 | * @param string $cachename 1071 | * @param mixed $value 1072 | * @param int $expired 1073 | * @return boolean 1074 | */ 1075 | protected function setCache($cachename,$value,$expired){ 1076 | Cache::put($cachename,$value,$expired/60); 1077 | return false; 1078 | } 1079 | 1080 | /** 1081 | * 获取缓存,按需重载 1082 | * @param string $cachename 1083 | * @return mixed 1084 | */ 1085 | protected function getCache($cachename){ 1086 | return Cache::get($cachename); 1087 | // return false; 1088 | } 1089 | 1090 | /** 1091 | * 清除缓存,按需重载 1092 | * @param string $cachename 1093 | * @return boolean 1094 | */ 1095 | protected function removeCache($cachename){ 1096 | Cache::forget($cachename); 1097 | return false; 1098 | } 1099 | 1100 | /** 1101 | * 获取access_token 1102 | * @param string $appid 如在类初始化时已提供,则可为空 1103 | * @param string $appsecret 如在类初始化时已提供,则可为空 1104 | * @param string $token 手动指定access_token,非必要情况不建议用 1105 | */ 1106 | public function checkAuth($appid='',$appsecret='',$token=''){ 1107 | if (!$appid || !$appsecret) { 1108 | $appid = $this->appid; 1109 | $appsecret = $this->appsecret; 1110 | } 1111 | if ($token) { //手动指定token,优先使用 1112 | $this->access_token=$token; 1113 | return $this->access_token; 1114 | } 1115 | 1116 | $authname = 'wechat_access_token'.$appid; 1117 | if ($rs = $this->getCache($authname)) { 1118 | $this->access_token = $rs; 1119 | return $rs; 1120 | } 1121 | 1122 | $result = $this->http_get(self::API_URL_PREFIX.self::AUTH_URL.'appid='.$appid.'&secret='.$appsecret); 1123 | if ($result) 1124 | { 1125 | $json = json_decode($result,true); 1126 | if (!$json || isset($json['errcode'])) { 1127 | $this->errCode = $json['errcode']; 1128 | $this->errMsg = $json['errmsg']; 1129 | return false; 1130 | } 1131 | $this->access_token = $json['access_token']; 1132 | $expire = $json['expires_in'] ? intval($json['expires_in'])-100 : 3600; 1133 | $this->setCache($authname,$this->access_token,$expire); 1134 | return $this->access_token; 1135 | } 1136 | return false; 1137 | } 1138 | 1139 | /** 1140 | * 删除验证数据 1141 | * @param string $appid 1142 | */ 1143 | public function resetAuth($appid=''){ 1144 | if (!$appid) $appid = $this->appid; 1145 | $this->access_token = ''; 1146 | $authname = 'wechat_access_token'.$appid; 1147 | $this->removeCache($authname); 1148 | return true; 1149 | } 1150 | 1151 | /** 1152 | * 删除JSAPI授权TICKET 1153 | * @param string $appid 用于多个appid时使用 1154 | */ 1155 | public function resetJsTicket($appid=''){ 1156 | if (!$appid) $appid = $this->appid; 1157 | $this->jsapi_ticket = ''; 1158 | $authname = 'wechat_jsapi_ticket'.$appid; 1159 | $this->removeCache($authname); 1160 | return true; 1161 | } 1162 | 1163 | /** 1164 | * 获取JSAPI授权TICKET 1165 | * @param string $appid 用于多个appid时使用,可空 1166 | * @param string $jsapi_ticket 手动指定jsapi_ticket,非必要情况不建议用 1167 | */ 1168 | public function getJsTicket($appid='',$jsapi_ticket=''){ 1169 | if (!$this->access_token && !$this->checkAuth()) return false; 1170 | if (!$appid) $appid = $this->appid; 1171 | if ($jsapi_ticket) { //手动指定token,优先使用 1172 | $this->jsapi_ticket = $jsapi_ticket; 1173 | return $this->access_token; 1174 | } 1175 | $authname = 'wechat_jsapi_ticket'.$appid; 1176 | if ($rs = $this->getCache($authname)) { 1177 | $this->jsapi_ticket = $rs; 1178 | return $rs; 1179 | } 1180 | $result = $this->http_get(self::API_URL_PREFIX.self::GET_TICKET_URL.'access_token='.$this->access_token.'&type=jsapi'); 1181 | if ($result) 1182 | { 1183 | $json = json_decode($result,true); 1184 | if (!$json || !empty($json['errcode'])) { 1185 | $this->errCode = $json['errcode']; 1186 | $this->errMsg = $json['errmsg']; 1187 | return false; 1188 | } 1189 | $this->jsapi_ticket = $json['ticket']; 1190 | $expire = $json['expires_in'] ? intval($json['expires_in'])-100 : 3600; 1191 | $this->setCache($authname,$this->jsapi_ticket,$expire); 1192 | return $this->jsapi_ticket; 1193 | } 1194 | return false; 1195 | } 1196 | 1197 | 1198 | /** 1199 | * 获取JsApi使用签名 1200 | * @param string $url 网页的URL,自动处理#及其后面部分 1201 | * @param string $timestamp 当前时间戳 (为空则自动生成) 1202 | * @param string $noncestr 随机串 (为空则自动生成) 1203 | * @param string $appid 用于多个appid时使用,可空 1204 | * @return array|bool 返回签名字串 1205 | */ 1206 | public function getJsSign($url, $timestamp=0, $noncestr='', $appid=''){ 1207 | if (!$this->jsapi_ticket && !$this->getJsTicket($appid) || !$url) return false; 1208 | if (!$timestamp) 1209 | $timestamp = time(); 1210 | if (!$noncestr) 1211 | $noncestr = $this->generateNonceStr(); 1212 | $ret = strpos($url,'#'); 1213 | if ($ret) 1214 | $url = substr($url,0,$ret); 1215 | $url = trim($url); 1216 | if (empty($url)) 1217 | return false; 1218 | $arrdata = array("timestamp" => $timestamp, "noncestr" => $noncestr, "url" => $url, "jsapi_ticket" => $this->jsapi_ticket); 1219 | $sign = $this->getSignature($arrdata); 1220 | if (!$sign) 1221 | return false; 1222 | $signPackage = array( 1223 | "appid" => $this->appid, 1224 | "noncestr" => $noncestr, 1225 | "timestamp" => $timestamp, 1226 | "url" => $url, 1227 | "signature" => $sign 1228 | ); 1229 | return $signPackage; 1230 | } 1231 | 1232 | /** 1233 | * 微信api不支持中文转义的json结构 1234 | * @param array $arr 1235 | */ 1236 | static function json_encode($arr) { 1237 | $parts = array (); 1238 | $is_list = false; 1239 | //Find out if the given array is a numerical array 1240 | $keys = array_keys ( $arr ); 1241 | $max_length = count ( $arr ) - 1; 1242 | if (($keys [0] === 0) && ($keys [$max_length] === $max_length )) { //See if the first key is 0 and last key is length - 1 1243 | $is_list = true; 1244 | for($i = 0; $i < count ( $keys ); $i ++) { //See if each key correspondes to its position 1245 | if ($i != $keys [$i]) { //A key fails at position check. 1246 | $is_list = false; //It is an associative array. 1247 | break; 1248 | } 1249 | } 1250 | } 1251 | foreach ( $arr as $key => $value ) { 1252 | if (is_array ( $value )) { //Custom handling for arrays 1253 | if ($is_list) 1254 | $parts [] = self::json_encode ( $value ); /* :RECURSION: */ 1255 | else 1256 | $parts [] = '"' . $key . '":' . self::json_encode ( $value ); /* :RECURSION: */ 1257 | } else { 1258 | $str = ''; 1259 | if (! $is_list) 1260 | $str = '"' . $key . '":'; 1261 | //Custom handling for multiple data types 1262 | if (!is_string ( $value ) && is_numeric ( $value ) && $value<2000000000) 1263 | $str .= $value; //Numbers 1264 | elseif ($value === false) 1265 | $str .= 'false'; //The booleans 1266 | elseif ($value === true) 1267 | $str .= 'true'; 1268 | else 1269 | $str .= '"' . addslashes ( $value ) . '"'; //All other things 1270 | // :TODO: Is there any more datatype we should be in the lookout for? (Object?) 1271 | $parts [] = $str; 1272 | } 1273 | } 1274 | $json = implode ( ',', $parts ); 1275 | if ($is_list) 1276 | return '[' . $json . ']'; //Return numerical JSON 1277 | return '{' . $json . '}'; //Return associative JSON 1278 | } 1279 | 1280 | /** 1281 | * 获取签名 1282 | * @param array $arrdata 签名数组 1283 | * @param string $method 签名方法 1284 | * @return boolean|string 签名值 1285 | */ 1286 | public function getSignature($arrdata,$method="sha1") { 1287 | if (!function_exists($method)) return false; 1288 | ksort($arrdata); 1289 | $paramstring = ""; 1290 | foreach($arrdata as $key => $value) 1291 | { 1292 | if(strlen($paramstring) == 0) 1293 | $paramstring .= $key . "=" . $value; 1294 | else 1295 | $paramstring .= "&" . $key . "=" . $value; 1296 | } 1297 | $Sign = $method($paramstring); 1298 | return $Sign; 1299 | } 1300 | 1301 | /** 1302 | * 生成随机字串 1303 | * @param number $length 长度,默认为16,最长为32字节 1304 | * @return string 1305 | */ 1306 | public function generateNonceStr($length=16){ 1307 | // 密码字符集,可任意添加你需要的字符 1308 | $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 1309 | $str = ""; 1310 | for($i = 0; $i < $length; $i++) 1311 | { 1312 | $str .= $chars[mt_rand(0, strlen($chars) - 1)]; 1313 | } 1314 | return $str; 1315 | } 1316 | 1317 | /** 1318 | * 获取微信服务器IP地址列表 1319 | * @return array('127.0.0.1','127.0.0.1') 1320 | */ 1321 | public function getServerIp(){ 1322 | if (!$this->access_token && !$this->checkAuth()) return false; 1323 | $result = $this->http_get(self::API_URL_PREFIX.self::CALLBACKSERVER_GET_URL.'access_token='.$this->access_token); 1324 | if ($result) 1325 | { 1326 | $json = json_decode($result,true); 1327 | if (!$json || isset($json['errcode'])) { 1328 | $this->errCode = $json['errcode']; 1329 | $this->errMsg = $json['errmsg']; 1330 | return false; 1331 | } 1332 | return $json['ip_list']; 1333 | } 1334 | return false; 1335 | } 1336 | 1337 | /** 1338 | * 创建菜单(认证后的订阅号可用) 1339 | * @param array $data 菜单数组数据 1340 | * example: 1341 | * array ( 1342 | * 'button' => array ( 1343 | * 0 => array ( 1344 | * 'name' => '扫码', 1345 | * 'sub_button' => array ( 1346 | * 0 => array ( 1347 | * 'type' => 'scancode_waitmsg', 1348 | * 'name' => '扫码带提示', 1349 | * 'key' => 'rselfmenu_0_0', 1350 | * ), 1351 | * 1 => array ( 1352 | * 'type' => 'scancode_push', 1353 | * 'name' => '扫码推事件', 1354 | * 'key' => 'rselfmenu_0_1', 1355 | * ), 1356 | * ), 1357 | * ), 1358 | * 1 => array ( 1359 | * 'name' => '发图', 1360 | * 'sub_button' => array ( 1361 | * 0 => array ( 1362 | * 'type' => 'pic_sysphoto', 1363 | * 'name' => '系统拍照发图', 1364 | * 'key' => 'rselfmenu_1_0', 1365 | * ), 1366 | * 1 => array ( 1367 | * 'type' => 'pic_photo_or_album', 1368 | * 'name' => '拍照或者相册发图', 1369 | * 'key' => 'rselfmenu_1_1', 1370 | * ) 1371 | * ), 1372 | * ), 1373 | * 2 => array ( 1374 | * 'type' => 'location_select', 1375 | * 'name' => '发送位置', 1376 | * 'key' => 'rselfmenu_2_0' 1377 | * ), 1378 | * ), 1379 | * ) 1380 | * type可以选择为以下几种,其中5-8除了收到菜单事件以外,还会单独收到对应类型的信息。 1381 | * 1、click:点击推事件 1382 | * 2、view:跳转URL 1383 | * 3、scancode_push:扫码推事件 1384 | * 4、scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框 1385 | * 5、pic_sysphoto:弹出系统拍照发图 1386 | * 6、pic_photo_or_album:弹出拍照或者相册发图 1387 | * 7、pic_weixin:弹出微信相册发图器 1388 | * 8、location_select:弹出地理位置选择器 1389 | */ 1390 | public function createMenu($data){ 1391 | if (!$this->access_token && !$this->checkAuth()) return false; 1392 | $result = $this->http_post(self::API_URL_PREFIX.self::MENU_CREATE_URL.'access_token='.$this->access_token,self::json_encode($data)); 1393 | if ($result) 1394 | { 1395 | $json = json_decode($result,true); 1396 | if (!$json || !empty($json['errcode'])) { 1397 | $this->errCode = $json['errcode']; 1398 | $this->errMsg = $json['errmsg']; 1399 | return false; 1400 | } 1401 | return true; 1402 | } 1403 | return false; 1404 | } 1405 | 1406 | /** 1407 | * 获取菜单(认证后的订阅号可用) 1408 | * @return array('menu'=>array(....s)) 1409 | */ 1410 | public function getMenu(){ 1411 | if (!$this->access_token && !$this->checkAuth()) return false; 1412 | $result = $this->http_get(self::API_URL_PREFIX.self::MENU_GET_URL.'access_token='.$this->access_token); 1413 | if ($result) 1414 | { 1415 | $json = json_decode($result,true); 1416 | if (!$json || isset($json['errcode'])) { 1417 | $this->errCode = $json['errcode']; 1418 | $this->errMsg = $json['errmsg']; 1419 | return false; 1420 | } 1421 | return $json; 1422 | } 1423 | return false; 1424 | } 1425 | 1426 | /** 1427 | * 删除菜单(认证后的订阅号可用) 1428 | * @return boolean 1429 | */ 1430 | public function deleteMenu(){ 1431 | if (!$this->access_token && !$this->checkAuth()) return false; 1432 | $result = $this->http_get(self::API_URL_PREFIX.self::MENU_DELETE_URL.'access_token='.$this->access_token); 1433 | if ($result) 1434 | { 1435 | $json = json_decode($result,true); 1436 | if (!$json || !empty($json['errcode'])) { 1437 | $this->errCode = $json['errcode']; 1438 | $this->errMsg = $json['errmsg']; 1439 | return false; 1440 | } 1441 | return true; 1442 | } 1443 | return false; 1444 | } 1445 | 1446 | /** 1447 | * 上传多媒体文件(认证后的订阅号可用) 1448 | * 注意:上传大文件时可能需要先调用 set_time_limit(0) 避免超时 1449 | * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义 1450 | * @param array $data {"media":'@Path\filename.jpg'} 1451 | * @param type 类型:图片:image 语音:voice 视频:video 缩略图:thumb 1452 | * @return boolean|array 1453 | */ 1454 | public function uploadMedia($data, $type){ 1455 | if (!$this->access_token && !$this->checkAuth()) return false; 1456 | $result = $this->http_post(self::UPLOAD_MEDIA_URL.self::MEDIA_UPLOAD_URL.'access_token='.$this->access_token.'&type='.$type,$data,true); 1457 | if ($result) 1458 | { 1459 | $json = json_decode($result,true); 1460 | if (!$json || !empty($json['errcode'])) { 1461 | $this->errCode = $json['errcode']; 1462 | $this->errMsg = $json['errmsg']; 1463 | return false; 1464 | } 1465 | return $json; 1466 | } 1467 | return false; 1468 | } 1469 | 1470 | /** 1471 | * 根据媒体文件ID获取媒体文件(认证后的订阅号可用) 1472 | * @param string $media_id 媒体文件id 1473 | * @return raw data 1474 | */ 1475 | public function getMedia($media_id){ 1476 | if (!$this->access_token && !$this->checkAuth()) return false; 1477 | $result = $this->http_get(self::UPLOAD_MEDIA_URL.self::MEDIA_GET_URL.'access_token='.$this->access_token.'&media_id='.$media_id); 1478 | if ($result) 1479 | { 1480 | $json = json_decode($result,true); 1481 | if (isset($json['errcode'])) { 1482 | $this->errCode = $json['errcode']; 1483 | $this->errMsg = $json['errmsg']; 1484 | return false; 1485 | } 1486 | return $result; 1487 | } 1488 | return false; 1489 | } 1490 | 1491 | /** 1492 | * 上传图文消息素材(认证后的订阅号可用) 1493 | * @param array $data 消息结构{"articles":[{...}]} 1494 | * @return boolean|array 1495 | */ 1496 | public function uploadArticles($data){ 1497 | if (!$this->access_token && !$this->checkAuth()) return false; 1498 | $result = $this->http_post(self::API_URL_PREFIX.self::MEDIA_UPLOADNEWS_URL.'access_token='.$this->access_token,self::json_encode($data)); 1499 | if ($result) 1500 | { 1501 | $json = json_decode($result,true); 1502 | if (!$json || !empty($json['errcode'])) { 1503 | $this->errCode = $json['errcode']; 1504 | $this->errMsg = $json['errmsg']; 1505 | return false; 1506 | } 1507 | return $json; 1508 | } 1509 | return false; 1510 | } 1511 | 1512 | /** 1513 | * 上传视频素材(认证后的订阅号可用) 1514 | * @param array $data 消息结构 1515 | * { 1516 | * "media_id"=>"", //通过上传媒体接口得到的MediaId 1517 | * "title"=>"TITLE", //视频标题 1518 | * "description"=>"Description" //视频描述 1519 | * } 1520 | * @return boolean|array 1521 | * { 1522 | * "type":"video", 1523 | * "media_id":"mediaid", 1524 | * "created_at":1398848981 1525 | * } 1526 | */ 1527 | public function uploadMpVideo($data){ 1528 | if (!$this->access_token && !$this->checkAuth()) return false; 1529 | $result = $this->http_post(self::UPLOAD_MEDIA_URL.self::MEDIA_VIDEO_UPLOAD.'access_token='.$this->access_token,self::json_encode($data)); 1530 | if ($result) 1531 | { 1532 | $json = json_decode($result,true); 1533 | if (!$json || !empty($json['errcode'])) { 1534 | $this->errCode = $json['errcode']; 1535 | $this->errMsg = $json['errmsg']; 1536 | return false; 1537 | } 1538 | return $json; 1539 | } 1540 | return false; 1541 | } 1542 | 1543 | /** 1544 | * 高级群发消息, 根据OpenID列表群发图文消息(订阅号不可用) 1545 | * 注意:视频需要在调用uploadMedia()方法后,再使用 uploadMpVideo() 方法生成, 1546 | * 然后获得的 mediaid 才能用于群发,且消息类型为 mpvideo 类型。 1547 | * @param array $data 消息结构 1548 | * { 1549 | * "touser"=>array( 1550 | * "OPENID1", 1551 | * "OPENID2" 1552 | * ), 1553 | * "msgtype"=>"mpvideo", 1554 | * // 在下面5种类型中选择对应的参数内容 1555 | * // mpnews | voice | image | mpvideo => array( "media_id"=>"MediaId") 1556 | * // text => array ( "content" => "hello") 1557 | * } 1558 | * @return boolean|array 1559 | */ 1560 | public function sendMassMessage($data){ 1561 | if (!$this->access_token && !$this->checkAuth()) return false; 1562 | $result = $this->http_post(self::API_URL_PREFIX.self::MASS_SEND_URL.'access_token='.$this->access_token,self::json_encode($data)); 1563 | if ($result) 1564 | { 1565 | $json = json_decode($result,true); 1566 | if (!$json || !empty($json['errcode'])) { 1567 | $this->errCode = $json['errcode']; 1568 | $this->errMsg = $json['errmsg']; 1569 | return false; 1570 | } 1571 | return $json; 1572 | } 1573 | return false; 1574 | } 1575 | 1576 | /** 1577 | * 高级群发消息, 根据群组id群发图文消息(认证后的订阅号可用) 1578 | * 注意:视频需要在调用uploadMedia()方法后,再使用 uploadMpVideo() 方法生成, 1579 | * 然后获得的 mediaid 才能用于群发,且消息类型为 mpvideo 类型。 1580 | * @param array $data 消息结构 1581 | * { 1582 | * "filter"=>array( 1583 | * "is_to_all"=>False, //是否群发给所有用户.True不用分组id,False需填写分组id 1584 | * "group_id"=>"2" //群发的分组id 1585 | * ), 1586 | * "msgtype"=>"mpvideo", 1587 | * // 在下面5种类型中选择对应的参数内容 1588 | * // mpnews | voice | image | mpvideo => array( "media_id"=>"MediaId") 1589 | * // text => array ( "content" => "hello") 1590 | * } 1591 | * @return boolean|array 1592 | */ 1593 | public function sendGroupMassMessage($data){ 1594 | if (!$this->access_token && !$this->checkAuth()) return false; 1595 | $result = $this->http_post(self::API_URL_PREFIX.self::MASS_SEND_GROUP_URL.'access_token='.$this->access_token,self::json_encode($data)); 1596 | if ($result) 1597 | { 1598 | $json = json_decode($result,true); 1599 | if (!$json || !empty($json['errcode'])) { 1600 | $this->errCode = $json['errcode']; 1601 | $this->errMsg = $json['errmsg']; 1602 | return false; 1603 | } 1604 | return $json; 1605 | } 1606 | return false; 1607 | } 1608 | 1609 | /** 1610 | * 高级群发消息, 删除群发图文消息(认证后的订阅号可用) 1611 | * @param int $msg_id 消息id 1612 | * @return boolean|array 1613 | */ 1614 | public function deleteMassMessage($msg_id){ 1615 | if (!$this->access_token && !$this->checkAuth()) return false; 1616 | $result = $this->http_post(self::API_URL_PREFIX.self::MASS_DELETE_URL.'access_token='.$this->access_token,self::json_encode(array('msg_id'=>$msg_id))); 1617 | if ($result) 1618 | { 1619 | $json = json_decode($result,true); 1620 | if (!$json || !empty($json['errcode'])) { 1621 | $this->errCode = $json['errcode']; 1622 | $this->errMsg = $json['errmsg']; 1623 | return false; 1624 | } 1625 | return true; 1626 | } 1627 | return false; 1628 | } 1629 | 1630 | /** 1631 | * 高级群发消息, 预览群发消息(认证后的订阅号可用) 1632 | * 注意:视频需要在调用uploadMedia()方法后,再使用 uploadMpVideo() 方法生成, 1633 | * 然后获得的 mediaid 才能用于群发,且消息类型为 mpvideo 类型。 1634 | * @param array $data 消息结构 1635 | * { 1636 | * "touser"=>"OPENID", 1637 | * "msgtype"=>"mpvideo", 1638 | * // 在下面5种类型中选择对应的参数内容 1639 | * // mpnews | voice | image | mpvideo => array( "media_id"=>"MediaId") 1640 | * // text => array ( "content" => "hello") 1641 | * } 1642 | * @return boolean|array 1643 | */ 1644 | public function previewMassMessage($data){ 1645 | if (!$this->access_token && !$this->checkAuth()) return false; 1646 | $result = $this->http_post(self::API_URL_PREFIX.self::MASS_PREVIEW_URL.'access_token='.$this->access_token,self::json_encode($data)); 1647 | if ($result) 1648 | { 1649 | $json = json_decode($result,true); 1650 | if (!$json || !empty($json['errcode'])) { 1651 | $this->errCode = $json['errcode']; 1652 | $this->errMsg = $json['errmsg']; 1653 | return false; 1654 | } 1655 | return $json; 1656 | } 1657 | return false; 1658 | } 1659 | 1660 | /** 1661 | * 高级群发消息, 查询群发消息发送状态(认证后的订阅号可用) 1662 | * @param int $msg_id 消息id 1663 | * @return boolean|array 1664 | * { 1665 | * "msg_id":201053012, //群发消息后返回的消息id 1666 | * "msg_status":"SEND_SUCCESS" //消息发送后的状态,SENDING表示正在发送 SEND_SUCCESS表示发送成功 1667 | * } 1668 | */ 1669 | public function queryMassMessage($msg_id){ 1670 | if (!$this->access_token && !$this->checkAuth()) return false; 1671 | $result = $this->http_post(self::API_URL_PREFIX.self::MASS_QUERY_URL.'access_token='.$this->access_token,self::json_encode(array('msg_id'=>$msg_id))); 1672 | if ($result) 1673 | { 1674 | $json = json_decode($result,true); 1675 | if (!$json || !empty($json['errcode'])) { 1676 | $this->errCode = $json['errcode']; 1677 | $this->errMsg = $json['errmsg']; 1678 | return false; 1679 | } 1680 | return $json; 1681 | } 1682 | return false; 1683 | } 1684 | 1685 | /** 1686 | * 创建二维码ticket 1687 | * @param int $scene_id 自定义追踪id 1688 | * @param int $type 0:临时二维码;1:永久二维码(此时expire参数无效) 1689 | * @param int $expire 临时二维码有效期,最大为1800秒 1690 | * @return array('ticket'=>'qrcode字串','expire_seconds'=>1800,'url'=>'二维码图片解析后的地址') 1691 | */ 1692 | public function getQRCode($scene_id,$type=0,$expire=1800){ 1693 | if (!$this->access_token && !$this->checkAuth()) return false; 1694 | $data = array( 1695 | 'action_name'=>$type?"QR_LIMIT_SCENE":"QR_SCENE", 1696 | 'expire_seconds'=>$expire, 1697 | 'action_info'=>array('scene'=>array('scene_id'=>$scene_id)) 1698 | ); 1699 | if ($type == 1) { 1700 | unset($data['expire_seconds']); 1701 | } 1702 | $result = $this->http_post(self::API_URL_PREFIX.self::QRCODE_CREATE_URL.'access_token='.$this->access_token,self::json_encode($data)); 1703 | if ($result) 1704 | { 1705 | $json = json_decode($result,true); 1706 | if (!$json || !empty($json['errcode'])) { 1707 | $this->errCode = $json['errcode']; 1708 | $this->errMsg = $json['errmsg']; 1709 | return false; 1710 | } 1711 | return $json; 1712 | } 1713 | return false; 1714 | } 1715 | 1716 | /** 1717 | * 获取二维码图片 1718 | * @param string $ticket 传入由getQRCode方法生成的ticket参数 1719 | * @return string url 返回http地址 1720 | */ 1721 | public function getQRUrl($ticket) { 1722 | return self::QRCODE_IMG_URL.$ticket; 1723 | } 1724 | 1725 | /** 1726 | * 长链接转短链接接口 1727 | * @param string $long_url 传入要转换的长url 1728 | * @return boolean|string url 成功则返回转换后的短url 1729 | */ 1730 | public function getShortUrl($long_url){ 1731 | if (!$this->access_token && !$this->checkAuth()) return false; 1732 | $data = array( 1733 | 'action'=>'long2short', 1734 | 'long_url'=>$long_url 1735 | ); 1736 | $result = $this->http_post(self::API_URL_PREFIX.self::SHORT_URL.'access_token='.$this->access_token,self::json_encode($data)); 1737 | if ($result) 1738 | { 1739 | $json = json_decode($result,true); 1740 | if (!$json || !empty($json['errcode'])) { 1741 | $this->errCode = $json['errcode']; 1742 | $this->errMsg = $json['errmsg']; 1743 | return false; 1744 | } 1745 | return $json['short_url']; 1746 | } 1747 | return false; 1748 | } 1749 | 1750 | /** 1751 | * 获取统计数据 1752 | * @param string $type 数据分类(user|article|upstreammsg|interface)分别为(用户分析|图文分析|消息分析|接口分析) 1753 | * @param string $subtype 数据子分类,参考 DATACUBE_URL_ARR 常量定义部分 或者README.md说明文档 1754 | * @param string $begin_date 开始时间 1755 | * @param string $end_date 结束时间 1756 | * @return boolean|array 成功返回查询结果数组,其定义请看官方文档 1757 | */ 1758 | public function getDatacube($type,$subtype,$begin_date,$end_date=''){ 1759 | if (!$this->access_token && !$this->checkAuth()) return false; 1760 | if (!isset(self::$DATACUBE_URL_ARR[$type]) || !isset(self::$DATACUBE_URL_ARR[$type][$subtype])) 1761 | return false; 1762 | $data = array( 1763 | 'begin_date'=>$begin_date, 1764 | 'end_date'=>$end_date?$end_date:$begin_date 1765 | ); 1766 | $result = $this->http_post(self::API_URL_PREFIX.self::$DATACUBE_URL_ARR[$type][$subtype].'access_token='.$this->access_token,self::json_encode($data)); 1767 | if ($result) 1768 | { 1769 | $json = json_decode($result,true); 1770 | if (!$json || !empty($json['errcode'])) { 1771 | $this->errCode = $json['errcode']; 1772 | $this->errMsg = $json['errmsg']; 1773 | return false; 1774 | } 1775 | return isset($json['list'])?$json['list']:$json; 1776 | } 1777 | return false; 1778 | } 1779 | 1780 | /** 1781 | * 批量获取关注用户列表 1782 | * @param unknown $next_openid 1783 | */ 1784 | public function getUserList($next_openid=''){ 1785 | if (!$this->access_token && !$this->checkAuth()) return false; 1786 | $result = $this->http_get(self::API_URL_PREFIX.self::USER_GET_URL.'access_token='.$this->access_token.'&next_openid='.$next_openid); 1787 | if ($result) 1788 | { 1789 | $json = json_decode($result,true); 1790 | if (isset($json['errcode'])) { 1791 | $this->errCode = $json['errcode']; 1792 | $this->errMsg = $json['errmsg']; 1793 | return false; 1794 | } 1795 | return $json; 1796 | } 1797 | return false; 1798 | } 1799 | 1800 | /** 1801 | * 获取关注者详细信息 1802 | * @param string $openid 1803 | * @return array {subscribe,openid,nickname,sex,city,province,country,language,headimgurl,subscribe_time,[unionid]} 1804 | * 注意:unionid字段 只有在用户将公众号绑定到微信开放平台账号后,才会出现。建议调用前用isset()检测一下 1805 | */ 1806 | public function getUserInfo($openid){ 1807 | if (!$this->access_token && !$this->checkAuth()) return false; 1808 | $result = $this->http_get(self::API_URL_PREFIX.self::USER_INFO_URL.'access_token='.$this->access_token.'&openid='.$openid); 1809 | if ($result) 1810 | { 1811 | $json = json_decode($result,true); 1812 | if (isset($json['errcode'])) { 1813 | $this->errCode = $json['errcode']; 1814 | $this->errMsg = $json['errmsg']; 1815 | return false; 1816 | } 1817 | return $json; 1818 | } 1819 | return false; 1820 | } 1821 | 1822 | /** 1823 | * 设置用户备注名 1824 | * @param string $openid 1825 | * @param string $remark 备注名 1826 | * @return boolean|array 1827 | */ 1828 | public function updateUserRemark($openid,$remark){ 1829 | if (!$this->access_token && !$this->checkAuth()) return false; 1830 | $data = array( 1831 | 'openid'=>$openid, 1832 | 'remark'=>$remark 1833 | ); 1834 | $result = $this->http_post(self::API_URL_PREFIX.self::USER_UPDATEREMARK_URL.'access_token='.$this->access_token,self::json_encode($data)); 1835 | if ($result) 1836 | { 1837 | $json = json_decode($result,true); 1838 | if (!$json || !empty($json['errcode'])) { 1839 | $this->errCode = $json['errcode']; 1840 | $this->errMsg = $json['errmsg']; 1841 | return false; 1842 | } 1843 | return $json; 1844 | } 1845 | return false; 1846 | } 1847 | 1848 | /** 1849 | * 获取用户分组列表 1850 | * @return boolean|array 1851 | */ 1852 | public function getGroup(){ 1853 | if (!$this->access_token && !$this->checkAuth()) return false; 1854 | $result = $this->http_get(self::API_URL_PREFIX.self::GROUP_GET_URL.'access_token='.$this->access_token); 1855 | if ($result) 1856 | { 1857 | $json = json_decode($result,true); 1858 | if (isset($json['errcode'])) { 1859 | $this->errCode = $json['errcode']; 1860 | $this->errMsg = $json['errmsg']; 1861 | return false; 1862 | } 1863 | return $json; 1864 | } 1865 | return false; 1866 | } 1867 | 1868 | /** 1869 | * 获取用户所在分组 1870 | * @param string $openid 1871 | * @return boolean|int 成功则返回用户分组id 1872 | */ 1873 | public function getUserGroup($openid){ 1874 | if (!$this->access_token && !$this->checkAuth()) return false; 1875 | $data = array( 1876 | 'openid'=>$openid 1877 | ); 1878 | $result = $this->http_post(self::API_URL_PREFIX.self::USER_GROUP_URL.'access_token='.$this->access_token,self::json_encode($data)); 1879 | if ($result) 1880 | { 1881 | $json = json_decode($result,true); 1882 | if (!$json || !empty($json['errcode'])) { 1883 | $this->errCode = $json['errcode']; 1884 | $this->errMsg = $json['errmsg']; 1885 | return false; 1886 | } else 1887 | if (isset($json['groupid'])) return $json['groupid']; 1888 | } 1889 | return false; 1890 | } 1891 | 1892 | /** 1893 | * 新增自定分组 1894 | * @param string $name 分组名称 1895 | * @return boolean|array 1896 | */ 1897 | public function createGroup($name){ 1898 | if (!$this->access_token && !$this->checkAuth()) return false; 1899 | $data = array( 1900 | 'group'=>array('name'=>$name) 1901 | ); 1902 | $result = $this->http_post(self::API_URL_PREFIX.self::GROUP_CREATE_URL.'access_token='.$this->access_token,self::json_encode($data)); 1903 | if ($result) 1904 | { 1905 | $json = json_decode($result,true); 1906 | if (!$json || !empty($json['errcode'])) { 1907 | $this->errCode = $json['errcode']; 1908 | $this->errMsg = $json['errmsg']; 1909 | return false; 1910 | } 1911 | return $json; 1912 | } 1913 | return false; 1914 | } 1915 | 1916 | /** 1917 | * 更改分组名称 1918 | * @param int $groupid 分组id 1919 | * @param string $name 分组名称 1920 | * @return boolean|array 1921 | */ 1922 | public function updateGroup($groupid,$name){ 1923 | if (!$this->access_token && !$this->checkAuth()) return false; 1924 | $data = array( 1925 | 'group'=>array('id'=>$groupid,'name'=>$name) 1926 | ); 1927 | $result = $this->http_post(self::API_URL_PREFIX.self::GROUP_UPDATE_URL.'access_token='.$this->access_token,self::json_encode($data)); 1928 | if ($result) 1929 | { 1930 | $json = json_decode($result,true); 1931 | if (!$json || !empty($json['errcode'])) { 1932 | $this->errCode = $json['errcode']; 1933 | $this->errMsg = $json['errmsg']; 1934 | return false; 1935 | } 1936 | return $json; 1937 | } 1938 | return false; 1939 | } 1940 | 1941 | /** 1942 | * 移动用户分组 1943 | * @param int $groupid 分组id 1944 | * @param string $openid 用户openid 1945 | * @return boolean|array 1946 | */ 1947 | public function updateGroupMembers($groupid,$openid){ 1948 | if (!$this->access_token && !$this->checkAuth()) return false; 1949 | $data = array( 1950 | 'openid'=>$openid, 1951 | 'to_groupid'=>$groupid 1952 | ); 1953 | $result = $this->http_post(self::API_URL_PREFIX.self::GROUP_MEMBER_UPDATE_URL.'access_token='.$this->access_token,self::json_encode($data)); 1954 | if ($result) 1955 | { 1956 | $json = json_decode($result,true); 1957 | if (!$json || !empty($json['errcode'])) { 1958 | $this->errCode = $json['errcode']; 1959 | $this->errMsg = $json['errmsg']; 1960 | return false; 1961 | } 1962 | return $json; 1963 | } 1964 | return false; 1965 | } 1966 | 1967 | /** 1968 | * 发送客服消息 1969 | * @param array $data 消息结构{"touser":"OPENID","msgtype":"news","news":{...}} 1970 | * @return boolean|array 1971 | */ 1972 | public function sendCustomMessage($data){ 1973 | if (!$this->access_token && !$this->checkAuth()) return false; 1974 | $result = $this->http_post(self::API_URL_PREFIX.self::CUSTOM_SEND_URL.'access_token='.$this->access_token,self::json_encode($data)); 1975 | if ($result) 1976 | { 1977 | $json = json_decode($result,true); 1978 | if (!$json || !empty($json['errcode'])) { 1979 | $this->errCode = $json['errcode']; 1980 | $this->errMsg = $json['errmsg']; 1981 | return false; 1982 | } 1983 | return $json; 1984 | } 1985 | return false; 1986 | } 1987 | 1988 | /** 1989 | * oauth 授权跳转接口 1990 | * @param string $callback 回调URI 1991 | * @return string 1992 | */ 1993 | public function getOauthRedirect($callback,$state='',$scope='snsapi_userinfo'){ 1994 | return self::OAUTH_PREFIX.self::OAUTH_AUTHORIZE_URL.'appid='.$this->appid.'&redirect_uri='.urlencode($callback).'&response_type=code&scope='.$scope.'&state='.$state.'#wechat_redirect'; 1995 | } 1996 | 1997 | /** 1998 | * 通过code获取Access Token 1999 | * @return array {access_token,expires_in,refresh_token,openid,scope} 2000 | */ 2001 | public function getOauthAccessToken(){ 2002 | $code = isset($_GET['code'])?$_GET['code']:''; 2003 | if (!$code) return false; 2004 | $result = $this->http_get(self::API_BASE_URL_PREFIX.self::OAUTH_TOKEN_URL.'appid='.$this->appid.'&secret='.$this->appsecret.'&code='.$code.'&grant_type=authorization_code'); 2005 | if ($result) 2006 | { 2007 | $json = json_decode($result,true); 2008 | if (!$json || !empty($json['errcode'])) { 2009 | $this->errCode = $json['errcode']; 2010 | $this->errMsg = $json['errmsg']; 2011 | return false; 2012 | } 2013 | $this->user_token = $json['access_token']; 2014 | return $json; 2015 | } 2016 | return false; 2017 | } 2018 | 2019 | /** 2020 | * 刷新access token并续期 2021 | * @param string $refresh_token 2022 | * @return boolean|mixed 2023 | */ 2024 | public function getOauthRefreshToken($refresh_token){ 2025 | $result = $this->http_get(self::API_BASE_URL_PREFIX.self::OAUTH_REFRESH_URL.'appid='.$this->appid.'&grant_type=refresh_token&refresh_token='.$refresh_token); 2026 | if ($result) 2027 | { 2028 | $json = json_decode($result,true); 2029 | if (!$json || !empty($json['errcode'])) { 2030 | $this->errCode = $json['errcode']; 2031 | $this->errMsg = $json['errmsg']; 2032 | return false; 2033 | } 2034 | $this->user_token = $json['access_token']; 2035 | return $json; 2036 | } 2037 | return false; 2038 | } 2039 | 2040 | /** 2041 | * 获取授权后的用户资料 2042 | * @param string $access_token 2043 | * @param string $openid 2044 | * @return array {openid,nickname,sex,province,city,country,headimgurl,privilege,[unionid]} 2045 | * 注意:unionid字段 只有在用户将公众号绑定到微信开放平台账号后,才会出现。建议调用前用isset()检测一下 2046 | */ 2047 | public function getOauthUserinfo($access_token,$openid){ 2048 | $result = $this->http_get(self::API_BASE_URL_PREFIX.self::OAUTH_USERINFO_URL.'access_token='.$access_token.'&openid='.$openid); 2049 | if ($result) 2050 | { 2051 | $json = json_decode($result,true); 2052 | if (!$json || !empty($json['errcode'])) { 2053 | $this->errCode = $json['errcode']; 2054 | $this->errMsg = $json['errmsg']; 2055 | return false; 2056 | } 2057 | return $json; 2058 | } 2059 | return false; 2060 | } 2061 | 2062 | /** 2063 | * 检验授权凭证是否有效 2064 | * @param string $access_token 2065 | * @param string $openid 2066 | * @return boolean 是否有效 2067 | */ 2068 | public function getOauthAuth($access_token,$openid){ 2069 | $result = $this->http_get(self::API_BASE_URL_PREFIX.self::OAUTH_AUTH_URL.'access_token='.$access_token.'&openid='.$openid); 2070 | if ($result) 2071 | { 2072 | $json = json_decode($result,true); 2073 | if (!$json || !empty($json['errcode'])) { 2074 | $this->errCode = $json['errcode']; 2075 | $this->errMsg = $json['errmsg']; 2076 | return false; 2077 | } else 2078 | if ($json['errcode']==0) return true; 2079 | } 2080 | return false; 2081 | } 2082 | 2083 | /** 2084 | * 模板消息 设置所属行业 2085 | * @param int $id1 公众号模板消息所属行业编号,参看官方开发文档 行业代码 2086 | * @param int $id2 同$id1。但如果只有一个行业,此参数可省略 2087 | * @return boolean|array 2088 | */ 2089 | public function setTMIndustry($id1,$id2=''){ 2090 | if ($id1) $data['industry_id1'] = $id1; 2091 | if ($id2) $data['industry_id2'] = $id2; 2092 | if (!$this->access_token && !$this->checkAuth()) return false; 2093 | $result = $this->http_post(self::API_URL_PREFIX.self::TEMPLATE_SET_INDUSTRY_URL.'access_token='.$this->access_token,self::json_encode($data)); 2094 | if($result){ 2095 | $json = json_decode($result,true); 2096 | if (!$json || !empty($json['errcode'])) { 2097 | $this->errCode = $json['errcode']; 2098 | $this->errMsg = $json['errmsg']; 2099 | return false; 2100 | } 2101 | return $json; 2102 | } 2103 | return false; 2104 | } 2105 | 2106 | /** 2107 | * 模板消息 添加消息模板 2108 | * 成功返回消息模板的调用id 2109 | * @param string $tpl_id 模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式 2110 | * @return boolean|string 2111 | */ 2112 | public function addTemplateMessage($tpl_id){ 2113 | $data = array ('template_id_short' =>$tpl_id); 2114 | if (!$this->access_token && !$this->checkAuth()) return false; 2115 | $result = $this->http_post(self::API_URL_PREFIX.self::TEMPLATE_ADD_TPL_URL.'access_token='.$this->access_token,self::json_encode($data)); 2116 | if($result){ 2117 | $json = json_decode($result,true); 2118 | if (!$json || !empty($json['errcode'])) { 2119 | $this->errCode = $json['errcode']; 2120 | $this->errMsg = $json['errmsg']; 2121 | return false; 2122 | } 2123 | return $json['template_id']; 2124 | } 2125 | return false; 2126 | } 2127 | 2128 | /** 2129 | * 发送模板消息 2130 | * @param array $data 消息结构 2131 | * { 2132 | "touser":"OPENID", 2133 | "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY", 2134 | "url":"http://weixin.qq.com/download", 2135 | "topcolor":"#FF0000", 2136 | "data":{ 2137 | "参数名1": { 2138 | "value":"参数", 2139 | "color":"#173177" //参数颜色 2140 | }, 2141 | "Date":{ 2142 | "value":"06月07日 19时24分", 2143 | "color":"#173177" 2144 | }, 2145 | "CardNumber":{ 2146 | "value":"0426", 2147 | "color":"#173177" 2148 | }, 2149 | "Type":{ 2150 | "value":"消费", 2151 | "color":"#173177" 2152 | } 2153 | } 2154 | } 2155 | * @return boolean|array 2156 | */ 2157 | public function sendTemplateMessage($data){ 2158 | if (!$this->access_token && !$this->checkAuth()) return false; 2159 | $result = $this->http_post(self::API_URL_PREFIX.self::TEMPLATE_SEND_URL.'access_token='.$this->access_token,self::json_encode($data)); 2160 | if($result){ 2161 | $json = json_decode($result,true); 2162 | if (!$json || !empty($json['errcode'])) { 2163 | $this->errCode = $json['errcode']; 2164 | $this->errMsg = $json['errmsg']; 2165 | return false; 2166 | } 2167 | return $json; 2168 | } 2169 | return false; 2170 | } 2171 | 2172 | /** 2173 | * 获取多客服会话记录 2174 | * @param array $data 数据结构{"starttime":123456789,"endtime":987654321,"openid":"OPENID","pagesize":10,"pageindex":1,} 2175 | * @return boolean|array 2176 | */ 2177 | public function getCustomServiceMessage($data){ 2178 | if (!$this->access_token && !$this->checkAuth()) return false; 2179 | $result = $this->http_post(self::API_URL_PREFIX.self::CUSTOM_SERVICE_GET_RECORD.'access_token='.$this->access_token,self::json_encode($data)); 2180 | if ($result) 2181 | { 2182 | $json = json_decode($result,true); 2183 | if (!$json || !empty($json['errcode'])) { 2184 | $this->errCode = $json['errcode']; 2185 | $this->errMsg = $json['errmsg']; 2186 | return false; 2187 | } 2188 | return $json; 2189 | } 2190 | return false; 2191 | } 2192 | 2193 | /** 2194 | * 转发多客服消息 2195 | * Example: $obj->transfer_customer_service($customer_account)->reply(); 2196 | * @param string $customer_account 转发到指定客服帐号:test1@test 2197 | */ 2198 | public function transfer_customer_service($customer_account = '') 2199 | { 2200 | $msg = array( 2201 | 'ToUserName' => $this->getRevFrom(), 2202 | 'FromUserName'=>$this->getRevTo(), 2203 | 'CreateTime'=>time(), 2204 | 'MsgType'=>'transfer_customer_service', 2205 | ); 2206 | if (!$customer_account) { 2207 | $msg['TransInfo'] = array('KfAccount'=>$customer_account); 2208 | } 2209 | $this->Message($msg); 2210 | return $this; 2211 | } 2212 | 2213 | /** 2214 | * 获取多客服客服基本信息 2215 | * 2216 | * @return boolean|array 2217 | */ 2218 | public function getCustomServiceKFlist(){ 2219 | if (!$this->access_token && !$this->checkAuth()) return false; 2220 | $result = $this->http_get(self::API_URL_PREFIX.self::CUSTOM_SERVICE_GET_KFLIST.'access_token='.$this->access_token); 2221 | if ($result) 2222 | { 2223 | $json = json_decode($result,true); 2224 | if (!$json || !empty($json['errcode'])) { 2225 | $this->errCode = $json['errcode']; 2226 | $this->errMsg = $json['errmsg']; 2227 | return false; 2228 | } 2229 | return $json; 2230 | } 2231 | return false; 2232 | } 2233 | 2234 | /** 2235 | * 获取多客服在线客服接待信息 2236 | * 2237 | * @return boolean|array { 2238 | "kf_online_list": [ 2239 | { 2240 | "kf_account": "test1@test", //客服账号@微信别名 2241 | "status": 1, //客服在线状态 1:pc在线,2:手机在线,若pc和手机同时在线则为 1+2=3 2242 | "kf_id": "1001", //客服工号 2243 | "auto_accept": 0, //客服设置的最大自动接入数 2244 | "accepted_case": 1 //客服当前正在接待的会话数 2245 | } 2246 | ] 2247 | } 2248 | */ 2249 | public function getCustomServiceOnlineKFlist(){ 2250 | if (!$this->access_token && !$this->checkAuth()) return false; 2251 | $result = $this->http_get(self::API_URL_PREFIX.self::CUSTOM_SERVICE_GET_ONLINEKFLIST.'access_token='.$this->access_token); 2252 | if ($result) 2253 | { 2254 | $json = json_decode($result,true); 2255 | if (!$json || !empty($json['errcode'])) { 2256 | $this->errCode = $json['errcode']; 2257 | $this->errMsg = $json['errmsg']; 2258 | return false; 2259 | } 2260 | return $json; 2261 | } 2262 | return false; 2263 | } 2264 | 2265 | /** 2266 | * 创建指定多客服会话 2267 | * @tutorial 当用户已被其他客服接待或指定客服不在线则会失败 2268 | * @param string $openid //用户openid 2269 | * @param string $kf_account //客服账号 2270 | * @param string $text //附加信息,文本会展示在客服人员的多客服客户端,可为空 2271 | * @return boolean | array //成功返回json数组 2272 | * { 2273 | * "errcode": 0, 2274 | * "errmsg": "ok", 2275 | * } 2276 | */ 2277 | public function createKFSession($openid,$kf_account,$text=''){ 2278 | $data=array( 2279 | "openid" =>$openid, 2280 | "nickname" => $kf_account 2281 | ); 2282 | if ($text) $data["text"] = $text; 2283 | if (!$this->access_token && !$this->checkAuth()) return false; 2284 | $result = $this->http_post(self::API_URL_PREFIX.self::CUSTOM_SEESSION_CREATE.'access_token='.$this->access_token,self::json_encode($data)); 2285 | if ($result) 2286 | { 2287 | $json = json_decode($result,true); 2288 | if (!$json || !empty($json['errcode'])) { 2289 | $this->errCode = $json['errcode']; 2290 | $this->errMsg = $json['errmsg']; 2291 | return false; 2292 | } 2293 | return $json; 2294 | } 2295 | return false; 2296 | } 2297 | 2298 | /** 2299 | * 关闭指定多客服会话 2300 | * @tutorial 当用户被其他客服接待时则会失败 2301 | * @param string $openid //用户openid 2302 | * @param string $kf_account //客服账号 2303 | * @param string $text //附加信息,文本会展示在客服人员的多客服客户端,可为空 2304 | * @return boolean | array //成功返回json数组 2305 | * { 2306 | * "errcode": 0, 2307 | * "errmsg": "ok", 2308 | * } 2309 | */ 2310 | public function closeKFSession($openid,$kf_account,$text=''){ 2311 | $data=array( 2312 | "openid" =>$openid, 2313 | "nickname" => $kf_account 2314 | ); 2315 | if ($text) $data["text"] = $text; 2316 | if (!$this->access_token && !$this->checkAuth()) return false; 2317 | $result = $this->http_post(self::API_URL_PREFIX.self::CUSTOM_SEESSION_CLOSE .'access_token='.$this->access_token,self::json_encode($data)); 2318 | if ($result) 2319 | { 2320 | $json = json_decode($result,true); 2321 | if (!$json || !empty($json['errcode'])) { 2322 | $this->errCode = $json['errcode']; 2323 | $this->errMsg = $json['errmsg']; 2324 | return false; 2325 | } 2326 | return $json; 2327 | } 2328 | return false; 2329 | } 2330 | 2331 | /** 2332 | * 获取用户会话状态 2333 | * @param string $openid //用户openid 2334 | * @return boolean | array //成功返回json数组 2335 | * { 2336 | * "errcode" : 0, 2337 | * "errmsg" : "ok", 2338 | * "kf_account" : "test1@test", //正在接待的客服 2339 | * "createtime": 123456789, //会话接入时间 2340 | * } 2341 | */ 2342 | public function getKFSession($openid){ 2343 | if (!$this->access_token && !$this->checkAuth()) return false; 2344 | $result = $this->http_get(self::API_URL_PREFIX.self::CUSTOM_SEESSION_GET .'access_token='.$this->access_token.'&openid='.$openid); 2345 | if ($result) 2346 | { 2347 | $json = json_decode($result,true); 2348 | if (!$json || !empty($json['errcode'])) { 2349 | $this->errCode = $json['errcode']; 2350 | $this->errMsg = $json['errmsg']; 2351 | return false; 2352 | } 2353 | return $json; 2354 | } 2355 | return false; 2356 | } 2357 | 2358 | /** 2359 | * 获取指定客服的会话列表 2360 | * @param string $openid //用户openid 2361 | * @return boolean | array //成功返回json数组 2362 | * array( 2363 | * 'sessionlist' => array ( 2364 | * array ( 2365 | * 'openid'=>'OPENID', //客户 openid 2366 | * 'createtime'=>123456789, //会话创建时间,UNIX 时间戳 2367 | * ), 2368 | * array ( 2369 | * 'openid'=>'OPENID', //客户 openid 2370 | * 'createtime'=>123456789, //会话创建时间,UNIX 时间戳 2371 | * ), 2372 | * ) 2373 | * ) 2374 | */ 2375 | public function getKFSessionlist($kf_account){ 2376 | if (!$this->access_token && !$this->checkAuth()) return false; 2377 | $result = $this->http_get(self::API_URL_PREFIX.self::CUSTOM_SEESSION_GET_LIST .'access_token='.$this->access_token.'&kf_account='.$kf_account); 2378 | if ($result) 2379 | { 2380 | $json = json_decode($result,true); 2381 | if (!$json || !empty($json['errcode'])) { 2382 | $this->errCode = $json['errcode']; 2383 | $this->errMsg = $json['errmsg']; 2384 | return false; 2385 | } 2386 | return $json; 2387 | } 2388 | return false; 2389 | } 2390 | 2391 | /** 2392 | * 获取未接入会话列表 2393 | * @param string $openid //用户openid 2394 | * @return boolean | array //成功返回json数组 2395 | * array ( 2396 | * 'count' => 150 , //未接入会话数量 2397 | * 'waitcaselist' => array ( 2398 | * array ( 2399 | * 'openid'=>'OPENID', //客户 openid 2400 | * 'kf_account ' =>'', //指定接待的客服,为空则未指定 2401 | * 'createtime'=>123456789, //会话创建时间,UNIX 时间戳 2402 | * ), 2403 | * array ( 2404 | * 'openid'=>'OPENID', //客户 openid 2405 | * 'kf_account ' =>'', //指定接待的客服,为空则未指定 2406 | * 'createtime'=>123456789, //会话创建时间,UNIX 时间戳 2407 | * ) 2408 | * ) 2409 | * ) 2410 | */ 2411 | public function getKFSessionWait(){ 2412 | if (!$this->access_token && !$this->checkAuth()) return false; 2413 | $result = $this->http_get(self::API_URL_PREFIX.self::CUSTOM_SEESSION_GET_WAIT .'access_token='.$this->access_token); 2414 | if ($result) 2415 | { 2416 | $json = json_decode($result,true); 2417 | if (!$json || !empty($json['errcode'])) { 2418 | $this->errCode = $json['errcode']; 2419 | $this->errMsg = $json['errmsg']; 2420 | return false; 2421 | } 2422 | return $json; 2423 | } 2424 | return false; 2425 | } 2426 | 2427 | /** 2428 | * 添加客服账号 2429 | * 2430 | * @param string $account //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符 2431 | * @param string $nickname //客服昵称,最长6个汉字或12个英文字符 2432 | * @param string $password //客服账号明文登录密码,会自动加密 2433 | * @return boolean|array 2434 | * 成功返回结果 2435 | * { 2436 | * "errcode": 0, 2437 | * "errmsg": "ok", 2438 | * } 2439 | */ 2440 | public function addKFAccount($account,$nickname,$password){ 2441 | $data=array( 2442 | "kf_account" =>$account, 2443 | "nickname" => $nickname, 2444 | "password" => md5($password) 2445 | ); 2446 | if (!$this->access_token && !$this->checkAuth()) return false; 2447 | $result = $this->http_post(self::API_BASE_URL_PREFIX.self::CS_KF_ACCOUNT_ADD_URL.'access_token='.$this->access_token,self::json_encode($data)); 2448 | if ($result) 2449 | { 2450 | $json = json_decode($result,true); 2451 | if (!$json || !empty($json['errcode'])) { 2452 | $this->errCode = $json['errcode']; 2453 | $this->errMsg = $json['errmsg']; 2454 | return false; 2455 | } 2456 | return $json; 2457 | } 2458 | return false; 2459 | } 2460 | 2461 | /** 2462 | * 修改客服账号信息 2463 | * 2464 | * @param string $account //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符 2465 | * @param string $nickname //客服昵称,最长6个汉字或12个英文字符 2466 | * @param string $password //客服账号明文登录密码,会自动加密 2467 | * @return boolean|array 2468 | * 成功返回结果 2469 | * { 2470 | * "errcode": 0, 2471 | * "errmsg": "ok", 2472 | * } 2473 | */ 2474 | public function updateKFAccount($account,$nickname,$password){ 2475 | $data=array( 2476 | "kf_account" =>$account, 2477 | "nickname" => $nickname, 2478 | "password" => md5($password) 2479 | ); 2480 | if (!$this->access_token && !$this->checkAuth()) return false; 2481 | $result = $this->http_post(self::API_BASE_URL_PREFIX.self::CS_KF_ACCOUNT_UPDATE_URL.'access_token='.$this->access_token,self::json_encode($data)); 2482 | if ($result) 2483 | { 2484 | $json = json_decode($result,true); 2485 | if (!$json || !empty($json['errcode'])) { 2486 | $this->errCode = $json['errcode']; 2487 | $this->errMsg = $json['errmsg']; 2488 | return false; 2489 | } 2490 | return $json; 2491 | } 2492 | return false; 2493 | } 2494 | 2495 | /** 2496 | * 删除客服账号 2497 | * 2498 | * @param string $account //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符 2499 | * @return boolean|array 2500 | * 成功返回结果 2501 | * { 2502 | * "errcode": 0, 2503 | * "errmsg": "ok", 2504 | * } 2505 | */ 2506 | public function deleteKFAccount($account){ 2507 | if (!$this->access_token && !$this->checkAuth()) return false; 2508 | $result = $this->http_get(self::API_BASE_URL_PREFIX.self::CS_KF_ACCOUNT_DEL_URL.'access_token='.$this->access_token.'&kf_account='.$account); 2509 | if ($result) 2510 | { 2511 | $json = json_decode($result,true); 2512 | if (!$json || !empty($json['errcode'])) { 2513 | $this->errCode = $json['errcode']; 2514 | $this->errMsg = $json['errmsg']; 2515 | return false; 2516 | } 2517 | return $json; 2518 | } 2519 | return false; 2520 | } 2521 | 2522 | /** 2523 | * 上传客服头像 2524 | * 2525 | * @param string $account //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符 2526 | * @param string $imgfile //头像文件完整路径,如:'D:\user.jpg'。头像文件必须JPG格式,像素建议640*640 2527 | * @return boolean|array 2528 | * 成功返回结果 2529 | * { 2530 | * "errcode": 0, 2531 | * "errmsg": "ok", 2532 | * } 2533 | */ 2534 | public function setKFHeadImg($account,$imgfile){ 2535 | if (!$this->access_token && !$this->checkAuth()) return false; 2536 | $result = $this->http_post(self::API_BASE_URL_PREFIX.self::CS_KF_ACCOUNT_UPLOAD_HEADIMG_URL.'access_token='.$this->access_token.'&kf_account='.$account,array('media'=>'@'.$imgfile),true); 2537 | if ($result) 2538 | { 2539 | $json = json_decode($result,true); 2540 | if (!$json || !empty($json['errcode'])) { 2541 | $this->errCode = $json['errcode']; 2542 | $this->errMsg = $json['errmsg']; 2543 | return false; 2544 | } 2545 | return $json; 2546 | } 2547 | return false; 2548 | } 2549 | 2550 | /** 2551 | * 语义理解接口 2552 | * @param String $uid 用户唯一id(非开发者id),用户区分公众号下的不同用户(建议填入用户openid) 2553 | * @param String $query 输入文本串 2554 | * @param String $category 需要使用的服务类型,多个用“,”隔开,不能为空 2555 | * @param Float $latitude 纬度坐标,与经度同时传入;与城市二选一传入 2556 | * @param Float $longitude 经度坐标,与纬度同时传入;与城市二选一传入 2557 | * @param String $city 城市名称,与经纬度二选一传入 2558 | * @param String $region 区域名称,在城市存在的情况下可省略;与经纬度二选一传入 2559 | * @return boolean|array 2560 | */ 2561 | public function querySemantic($uid,$query,$category,$latitude=0,$longitude=0,$city="",$region=""){ 2562 | if (!$this->access_token && !$this->checkAuth()) return false; 2563 | $data=array( 2564 | 'query' => $query, 2565 | 'category' => $category, 2566 | 'appid' => $this->appid, 2567 | 'uid' => '' 2568 | ); 2569 | //地理坐标或城市名称二选一 2570 | if ($latitude) { 2571 | $data['latitude'] = $latitude; 2572 | $data['longitude'] = $longitude; 2573 | } elseif ($city) { 2574 | $data['city'] = $city; 2575 | } elseif ($region) { 2576 | $data['region'] = $region; 2577 | } 2578 | $result = $this->http_post(self::API_BASE_URL_PREFIX.self::SEMANTIC_API_URL.'access_token='.$this->access_token,self::json_encode($data)); 2579 | if ($result) 2580 | { 2581 | $json = json_decode($result,true); 2582 | if (!$json || !empty($json['errcode'])) { 2583 | $this->errCode = $json['errcode']; 2584 | $this->errMsg = $json['errmsg']; 2585 | return false; 2586 | } 2587 | return $json; 2588 | } 2589 | return false; 2590 | } 2591 | 2592 | /** 2593 | * 创建卡券 2594 | * @param Array $data 卡券数据 2595 | * @return array|boolean 返回数组中card_id为卡券ID 2596 | */ 2597 | public function createCard($data) { 2598 | if (!$this->access_token && !$this->checkAuth()) return false; 2599 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CREATE . 'access_token=' . $this->access_token, self::json_encode($data)); 2600 | if ($result) { 2601 | $json = json_decode($result, true); 2602 | if (!$json || !empty($json['errcode'])) { 2603 | $this->errCode = $json['errcode']; 2604 | $this->errMsg = $json['errmsg']; 2605 | return false; 2606 | } 2607 | return $json; 2608 | } 2609 | return false; 2610 | } 2611 | 2612 | /** 2613 | * 更改卡券信息 2614 | * 调用该接口更新信息后会重新送审,卡券状态变更为待审核。已被用户领取的卡券会实时更新票面信息。 2615 | * @param string $data 2616 | * @return boolean 2617 | */ 2618 | public function updateCard($data) { 2619 | if (!$this->access_token && !$this->checkAuth()) return false; 2620 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data)); 2621 | if ($result) { 2622 | $json = json_decode($result, true); 2623 | if (!$json || !empty($json['errcode'])) { 2624 | $this->errCode = $json['errcode']; 2625 | $this->errMsg = $json['errmsg']; 2626 | return false; 2627 | } 2628 | return true; 2629 | } 2630 | return false; 2631 | } 2632 | 2633 | /** 2634 | * 删除卡券 2635 | * 允许商户删除任意一类卡券。删除卡券后,该卡券对应已生成的领取用二维码、添加到卡包 JS API 均会失效。 2636 | * 注意:删除卡券不能删除已被用户领取,保存在微信客户端中的卡券,已领取的卡券依旧有效。 2637 | * @param string $card_id 卡券ID 2638 | * @return boolean 2639 | */ 2640 | public function delCard($card_id) { 2641 | $data = array( 2642 | 'card_id' => $card_id, 2643 | ); 2644 | if (!$this->access_token && !$this->checkAuth()) return false; 2645 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_DELETE . 'access_token=' . $this->access_token, self::json_encode($data)); 2646 | if ($result) { 2647 | $json = json_decode($result, true); 2648 | if (!$json || !empty($json['errcode'])) { 2649 | $this->errCode = $json['errcode']; 2650 | $this->errMsg = $json['errmsg']; 2651 | return false; 2652 | } 2653 | return true; 2654 | } 2655 | return false; 2656 | } 2657 | 2658 | /** 2659 | * 查询卡券详情 2660 | * @param string $card_id 2661 | * @return boolean|array 返回数组信息比较复杂,请参看卡券接口文档 2662 | */ 2663 | public function getCardInfo($card_id) { 2664 | $data = array( 2665 | 'card_id' => $card_id, 2666 | ); 2667 | if (!$this->access_token && !$this->checkAuth()) return false; 2668 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_GET . 'access_token=' . $this->access_token, self::json_encode($data)); 2669 | if ($result) { 2670 | $json = json_decode($result, true); 2671 | if (!$json || !empty($json['errcode'])) { 2672 | $this->errCode = $json['errcode']; 2673 | $this->errMsg = $json['errmsg']; 2674 | return false; 2675 | } 2676 | return $json; 2677 | } 2678 | return false; 2679 | } 2680 | 2681 | /** 2682 | * 获取颜色列表 2683 | * 获得卡券的最新颜色列表,用于创建卡券 2684 | * @return boolean|array 返回数组请参看 微信卡券接口文档 的json格式 2685 | */ 2686 | public function getCardColors() { 2687 | if (!$this->access_token && !$this->checkAuth()) return false; 2688 | $result = $this->http_get(self::API_BASE_URL_PREFIX . self::CARD_GETCOLORS . 'access_token=' . $this->access_token); 2689 | if ($result) { 2690 | $json = json_decode($result, true); 2691 | if (!$json || !empty($json['errcode'])) { 2692 | $this->errCode = $json['errcode']; 2693 | $this->errMsg = $json['errmsg']; 2694 | return false; 2695 | } 2696 | return $json; 2697 | } 2698 | return false; 2699 | } 2700 | 2701 | /** 2702 | * 拉取门店列表 2703 | * 获取在公众平台上申请创建的门店列表 2704 | * @param int $offset 开始拉取的偏移,默认为0从头开始 2705 | * @param int $count 拉取的数量,默认为0拉取全部 2706 | * @return boolean|array 返回数组请参看 微信卡券接口文档 的json格式 2707 | */ 2708 | public function getCardLocations($offset=0,$count=0) { 2709 | $data=array( 2710 | 'offset'=>$offset, 2711 | 'count'=>$count 2712 | ); 2713 | if (!$this->access_token && !$this->checkAuth()) return false; 2714 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_LOCATION_BATCHGET . 'access_token=' . $this->access_token, self::json_encode($data)); 2715 | if ($result) { 2716 | $json = json_decode($result, true); 2717 | if (!$json || !empty($json['errcode'])) { 2718 | $this->errCode = $json['errcode']; 2719 | $this->errMsg = $json['errmsg']; 2720 | return false; 2721 | } 2722 | return $json; 2723 | } 2724 | return false; 2725 | } 2726 | 2727 | /** 2728 | * 批量导入门店信息 2729 | * @tutorial 返回插入的门店id列表,以逗号分隔。如果有插入失败的,则为-1,请自行核查是哪个插入失败 2730 | * @param array $data 数组形式的json数据,由于内容较多,具体内容格式请查看 微信卡券接口文档 2731 | * @return boolean|string 成功返回插入的门店id列表 2732 | */ 2733 | public function addCardLocations($data) { 2734 | if (!$this->access_token && !$this->checkAuth()) return false; 2735 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_LOCATION_BATCHADD . 'access_token=' . $this->access_token, self::json_encode($data)); 2736 | if ($result) { 2737 | $json = json_decode($result, true); 2738 | if (!$json || !empty($json['errcode'])) { 2739 | $this->errCode = $json['errcode']; 2740 | $this->errMsg = $json['errmsg']; 2741 | return false; 2742 | } 2743 | return $json; 2744 | } 2745 | return false; 2746 | } 2747 | 2748 | /** 2749 | * 生成卡券二维码 2750 | * 成功则直接返回ticket值,可以用 getQRUrl($ticket) 换取二维码url 2751 | * 2752 | * @param string $cardid 卡券ID 必须 2753 | * @param string $code 指定卡券 code 码,只能被领一次。use_custom_code 字段为 true 的卡券必须填写,非自定义 code 不必填写。 2754 | * @param string $openid 指定领取者的 openid,只有该用户能领取。bind_openid 字段为 true 的卡券必须填写,非自定义 openid 不必填写。 2755 | * @param int $expire_seconds 指定二维码的有效时间,范围是 60 ~ 1800 秒。不填默认为永久有效。 2756 | * @param boolean $is_unique_code 指定下发二维码,生成的二维码随机分配一个 code,领取后不可再次扫描。填写 true 或 false。默认 false。 2757 | * @param string $balance 红包余额,以分为单位。红包类型必填(LUCKY_MONEY),其他卡券类型不填。 2758 | * @return boolean|string 2759 | */ 2760 | public function createCardQrcode($card_id,$code='',$openid='',$expire_seconds=0,$is_unique_code=false,$balance='') { 2761 | $card = array( 2762 | 'card_id' => $card_id 2763 | ); 2764 | if ($code) 2765 | $card['code'] = $code; 2766 | if ($openid) 2767 | $card['openid'] = $openid; 2768 | if ($expire_seconds) 2769 | $card['expire_seconds'] = $expire_seconds; 2770 | if ($is_unique_code) 2771 | $card['is_unique_code'] = $is_unique_code; 2772 | if ($balance) 2773 | $card['balance'] = $balance; 2774 | $data = array( 2775 | 'action_name' => "QR_CARD", 2776 | 'action_info' => array('card' => $card) 2777 | ); 2778 | if (!$this->access_token && !$this->checkAuth()) return false; 2779 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_QRCODE_CREATE . 'access_token=' . $this->access_token, self::json_encode($data)); 2780 | if ($result) { 2781 | $json = json_decode($result, true); 2782 | if (!$json || !empty($json['errcode'])) { 2783 | $this->errCode = $json['errcode']; 2784 | $this->errMsg = $json['errmsg']; 2785 | return false; 2786 | } 2787 | return $json; 2788 | } 2789 | return false; 2790 | } 2791 | 2792 | /** 2793 | * 消耗 code 2794 | * 自定义 code(use_custom_code 为 true)的优惠券,在 code 被核销时,必须调用此接口。 2795 | * 2796 | * @param string $code 要消耗的序列号 2797 | * @param string $card_id 要消耗序列号所述的 card_id,创建卡券时use_custom_code 填写 true 时必填。 2798 | * @return boolean|array 2799 | * { 2800 | * "errcode":0, 2801 | * "errmsg":"ok", 2802 | * "card":{"card_id":"pFS7Fjg8kV1IdDz01r4SQwMkuCKc"}, 2803 | * "openid":"oFS7Fjl0WsZ9AMZqrI80nbIq8xrA" 2804 | * } 2805 | */ 2806 | public function consumeCardCode($code,$card_id='') { 2807 | $data = array('code' => $code); 2808 | if ($card_id) 2809 | $data['card_id'] = $card_id; 2810 | if (!$this->access_token && !$this->checkAuth()) return false; 2811 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_CONSUME . 'access_token=' . $this->access_token, self::json_encode($data)); 2812 | if ($result) { 2813 | $json = json_decode($result, true); 2814 | if (!$json || !empty($json['errcode'])) { 2815 | $this->errCode = $json['errcode']; 2816 | $this->errMsg = $json['errmsg']; 2817 | return false; 2818 | } 2819 | return $json; 2820 | } 2821 | return false; 2822 | } 2823 | 2824 | /** 2825 | * code 解码 2826 | * @param string $encrypt_code 通过 choose_card_info 获取的加密字符串 2827 | * @return boolean|array 2828 | * { 2829 | * "errcode":0, 2830 | * "errmsg":"ok", 2831 | * "code":"751234212312" 2832 | * } 2833 | */ 2834 | public function decryptCardCode($encrypt_code) { 2835 | $data = array( 2836 | 'encrypt_code' => $encrypt_code, 2837 | ); 2838 | if (!$this->access_token && !$this->checkAuth()) return false; 2839 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_DECRYPT . 'access_token=' . $this->access_token, self::json_encode($data)); 2840 | if ($result) { 2841 | $json = json_decode($result, true); 2842 | if (!$json || !empty($json['errcode'])) { 2843 | $this->errCode = $json['errcode']; 2844 | $this->errMsg = $json['errmsg']; 2845 | return false; 2846 | } 2847 | return $json; 2848 | } 2849 | return false; 2850 | } 2851 | 2852 | /** 2853 | * 查询 code 的有效性(非自定义 code) 2854 | * @param string $code 2855 | * @return boolean|array 2856 | * { 2857 | * "errcode":0, 2858 | * "errmsg":"ok", 2859 | * "openid":"oFS7Fjl0WsZ9AMZqrI80nbIq8xrA", //用户 openid 2860 | * "card":{ 2861 | * "card_id":"pFS7Fjg8kV1IdDz01r4SQwMkuCKc", 2862 | * "begin_time": 1404205036, //起始使用时间 2863 | * "end_time": 1404205036, //结束时间 2864 | * } 2865 | * } 2866 | */ 2867 | public function checkCardCode($code) { 2868 | $data = array( 2869 | 'code' => $code, 2870 | ); 2871 | if (!$this->access_token && !$this->checkAuth()) return false; 2872 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_GET . 'access_token=' . $this->access_token, self::json_encode($data)); 2873 | if ($result) { 2874 | $json = json_decode($result, true); 2875 | if (!$json || !empty($json['errcode'])) { 2876 | $this->errCode = $json['errcode']; 2877 | $this->errMsg = $json['errmsg']; 2878 | return false; 2879 | } 2880 | return $json; 2881 | } 2882 | return false; 2883 | } 2884 | 2885 | /** 2886 | * 批量查询卡列表 2887 | * @param $offset 开始拉取的偏移,默认为0从头开始 2888 | * @param $count 需要查询的卡片的数量(数量最大50,默认50) 2889 | * @return boolean|array 2890 | * { 2891 | * "errcode":0, 2892 | * "errmsg":"ok", 2893 | * "card_id_list":["ph_gmt7cUVrlRk8swPwx7aDyF-pg"], //卡 id 列表 2894 | * "total_num":1 //该商户名下 card_id 总数 2895 | * } 2896 | */ 2897 | public function getCardIdList($offset=0,$count=50) { 2898 | if ($count>50) 2899 | $count = 50; 2900 | $data = array( 2901 | 'offset' => $offset, 2902 | 'count' => $count, 2903 | ); 2904 | if (!$this->access_token && !$this->checkAuth()) return false; 2905 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_BATCHGET . 'access_token=' . $this->access_token, self::json_encode($data)); 2906 | if ($result) { 2907 | $json = json_decode($result, true); 2908 | if (!$json || !empty($json['errcode'])) { 2909 | $this->errCode = $json['errcode']; 2910 | $this->errMsg = $json['errmsg']; 2911 | return false; 2912 | } 2913 | return $json; 2914 | } 2915 | return false; 2916 | } 2917 | 2918 | /** 2919 | * 更改 code 2920 | * 为确保转赠后的安全性,微信允许自定义code的商户对已下发的code进行更改。 2921 | * 注:为避免用户疑惑,建议仅在发生转赠行为后(发生转赠后,微信会通过事件推送的方式告知商户被转赠的卡券code)对用户的code进行更改。 2922 | * @param string $code 卡券的 code 编码 2923 | * @param string $card_id 卡券 ID 2924 | * @param string $new_code 新的卡券 code 编码 2925 | * @return boolean 2926 | */ 2927 | public function updateCardCode($code,$card_id,$new_code) { 2928 | $data = array( 2929 | 'code' => $code, 2930 | 'card_id' => $card_id, 2931 | 'new_code' => $new_code, 2932 | ); 2933 | if (!$this->access_token && !$this->checkAuth()) return false; 2934 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data)); 2935 | if ($result) { 2936 | $json = json_decode($result, true); 2937 | if (!$json || !empty($json['errcode'])) { 2938 | $this->errCode = $json['errcode']; 2939 | $this->errMsg = $json['errmsg']; 2940 | return false; 2941 | } 2942 | return true; 2943 | } 2944 | return false; 2945 | } 2946 | 2947 | /** 2948 | * 设置卡券失效 2949 | * 设置卡券失效的操作不可逆 2950 | * @param string $code 需要设置为失效的 code 2951 | * @param string $card_id 自定义 code 的卡券必填。非自定义 code 的卡券不填。 2952 | * @return boolean 2953 | */ 2954 | public function unavailableCardCode($code,$card_id='') { 2955 | $data = array( 2956 | 'code' => $code, 2957 | ); 2958 | if ($card_id) 2959 | $data['card_id'] = $card_id; 2960 | if (!$this->access_token && !$this->checkAuth()) return false; 2961 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_UNAVAILABLE . 'access_token=' . $this->access_token, self::json_encode($data)); 2962 | if ($result) { 2963 | $json = json_decode($result, true); 2964 | if (!$json || !empty($json['errcode'])) { 2965 | $this->errCode = $json['errcode']; 2966 | $this->errMsg = $json['errmsg']; 2967 | return false; 2968 | } 2969 | return true; 2970 | } 2971 | return false; 2972 | } 2973 | 2974 | /** 2975 | * 库存修改 2976 | * @param string $data 2977 | * @return boolean 2978 | */ 2979 | public function modifyCardStock($data) { 2980 | if (!$this->access_token && !$this->checkAuth()) return false; 2981 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_MODIFY_STOCK . 'access_token=' . $this->access_token, self::json_encode($data)); 2982 | if ($result) { 2983 | $json = json_decode($result, true); 2984 | if (!$json || !empty($json['errcode'])) { 2985 | $this->errCode = $json['errcode']; 2986 | $this->errMsg = $json['errmsg']; 2987 | return false; 2988 | } 2989 | return true; 2990 | } 2991 | return false; 2992 | } 2993 | 2994 | /** 2995 | * 激活/绑定会员卡 2996 | * @param string $data 具体结构请参看卡券开发文档(6.1.1 激活/绑定会员卡)章节 2997 | * @return boolean 2998 | */ 2999 | public function activateMemberCard($data) { 3000 | if (!$this->access_token && !$this->checkAuth()) return false; 3001 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_MEMBERCARD_ACTIVATE . 'access_token=' . $this->access_token, self::json_encode($data)); 3002 | if ($result) { 3003 | $json = json_decode($result, true); 3004 | if (!$json || !empty($json['errcode'])) { 3005 | $this->errCode = $json['errcode']; 3006 | $this->errMsg = $json['errmsg']; 3007 | return false; 3008 | } 3009 | return true; 3010 | } 3011 | return false; 3012 | } 3013 | 3014 | /** 3015 | * 会员卡交易 3016 | * 会员卡交易后每次积分及余额变更需通过接口通知微信,便于后续消息通知及其他扩展功能。 3017 | * @param string $data 具体结构请参看卡券开发文档(6.1.2 会员卡交易)章节 3018 | * @return boolean|array 3019 | */ 3020 | public function updateMemberCard($data) { 3021 | if (!$this->access_token && !$this->checkAuth()) return false; 3022 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_MEMBERCARD_UPDATEUSER . 'access_token=' . $this->access_token, self::json_encode($data)); 3023 | if ($result) { 3024 | $json = json_decode($result, true); 3025 | if (!$json || !empty($json['errcode'])) { 3026 | $this->errCode = $json['errcode']; 3027 | $this->errMsg = $json['errmsg']; 3028 | return false; 3029 | } 3030 | return $json; 3031 | } 3032 | return false; 3033 | } 3034 | 3035 | /** 3036 | * 更新红包金额 3037 | * @param string $code 红包的序列号 3038 | * @param $balance 红包余额 3039 | * @param string $card_id 自定义 code 的卡券必填。非自定义 code 可不填。 3040 | * @return boolean|array 3041 | */ 3042 | public function updateLuckyMoney($code,$balance,$card_id='') { 3043 | $data = array( 3044 | 'code' => $code, 3045 | 'balance' => $balance 3046 | ); 3047 | if ($card_id) 3048 | $data['card_id'] = $card_id; 3049 | if (!$this->access_token && !$this->checkAuth()) return false; 3050 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_LUCKYMONEY_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data)); 3051 | if ($result) { 3052 | $json = json_decode($result, true); 3053 | if (!$json || !empty($json['errcode'])) { 3054 | $this->errCode = $json['errcode']; 3055 | $this->errMsg = $json['errmsg']; 3056 | return false; 3057 | } 3058 | return true; 3059 | } 3060 | return false; 3061 | } 3062 | 3063 | /** 3064 | * 设置卡券测试白名单 3065 | * @param string $openid 测试的 openid 列表 3066 | * @param string $user 测试的微信号列表 3067 | * @return boolean 3068 | */ 3069 | public function setCardTestWhiteList($openid=array(),$user=array()) { 3070 | $data = array(); 3071 | if (count($openid) > 0) 3072 | $data['openid'] = $openid; 3073 | if (count($user) > 0) 3074 | $data['username'] = $user; 3075 | if (!$this->access_token && !$this->checkAuth()) return false; 3076 | $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_TESTWHILELIST_SET . 'access_token=' . $this->access_token, self::json_encode($data)); 3077 | if ($result) { 3078 | $json = json_decode($result, true); 3079 | if (!$json || !empty($json['errcode'])) { 3080 | $this->errCode = $json['errcode']; 3081 | $this->errMsg = $json['errmsg']; 3082 | return false; 3083 | } 3084 | return true; 3085 | } 3086 | return false; 3087 | } 3088 | 3089 | } 3090 | /** 3091 | * PKCS7Encoder class 3092 | * 3093 | * 提供基于PKCS7算法的加解密接口. 3094 | */ 3095 | class PKCS7Encoder 3096 | { 3097 | public static $block_size = 32; 3098 | 3099 | /** 3100 | * 对需要加密的明文进行填充补位 3101 | * @param $text 需要进行填充补位操作的明文 3102 | * @return 补齐明文字符串 3103 | */ 3104 | function encode($text) 3105 | { 3106 | $block_size = PKCS7Encoder::$block_size; 3107 | $text_length = strlen($text); 3108 | //计算需要填充的位数 3109 | $amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size); 3110 | if ($amount_to_pad == 0) { 3111 | $amount_to_pad = PKCS7Encoder::block_size; 3112 | } 3113 | //获得补位所用的字符 3114 | $pad_chr = chr($amount_to_pad); 3115 | $tmp = ""; 3116 | for ($index = 0; $index < $amount_to_pad; $index++) { 3117 | $tmp .= $pad_chr; 3118 | } 3119 | return $text . $tmp; 3120 | } 3121 | 3122 | /** 3123 | * 对解密后的明文进行补位删除 3124 | * @param decrypted 解密后的明文 3125 | * @return 删除填充补位后的明文 3126 | */ 3127 | function decode($text) 3128 | { 3129 | 3130 | $pad = ord(substr($text, -1)); 3131 | if ($pad < 1 || $pad > PKCS7Encoder::$block_size) { 3132 | $pad = 0; 3133 | } 3134 | return substr($text, 0, (strlen($text) - $pad)); 3135 | } 3136 | 3137 | } 3138 | 3139 | /** 3140 | * Prpcrypt class 3141 | * 3142 | * 提供接收和推送给公众平台消息的加解密接口. 3143 | */ 3144 | class Prpcrypt 3145 | { 3146 | public $key; 3147 | 3148 | function Prpcrypt($k) 3149 | { 3150 | $this->key = base64_decode($k . "="); 3151 | } 3152 | 3153 | /** 3154 | * 对明文进行加密 3155 | * @param string $text 需要加密的明文 3156 | * @return string 加密后的密文 3157 | */ 3158 | public function encrypt($text, $appid) 3159 | { 3160 | 3161 | try { 3162 | //获得16位随机字符串,填充到明文之前 3163 | $random = $this->getRandomStr();//"aaaabbbbccccdddd"; 3164 | $text = $random . pack("N", strlen($text)) . $text . $appid; 3165 | // 网络字节序 3166 | $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); 3167 | $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); 3168 | $iv = substr($this->key, 0, 16); 3169 | //使用自定义的填充方式对明文进行补位填充 3170 | $pkc_encoder = new PKCS7Encoder; 3171 | $text = $pkc_encoder->encode($text); 3172 | mcrypt_generic_init($module, $this->key, $iv); 3173 | //加密 3174 | $encrypted = mcrypt_generic($module, $text); 3175 | mcrypt_generic_deinit($module); 3176 | mcrypt_module_close($module); 3177 | 3178 | // print(base64_encode($encrypted)); 3179 | //使用BASE64对加密后的字符串进行编码 3180 | return array(ErrorCode::$OK, base64_encode($encrypted)); 3181 | } catch (Exception $e) { 3182 | //print $e; 3183 | return array(ErrorCode::$EncryptAESError, null); 3184 | } 3185 | } 3186 | 3187 | /** 3188 | * 对密文进行解密 3189 | * @param string $encrypted 需要解密的密文 3190 | * @return string 解密得到的明文 3191 | */ 3192 | public function decrypt($encrypted, $appid) 3193 | { 3194 | 3195 | try { 3196 | //使用BASE64对需要解密的字符串进行解码 3197 | $ciphertext_dec = base64_decode($encrypted); 3198 | $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); 3199 | $iv = substr($this->key, 0, 16); 3200 | mcrypt_generic_init($module, $this->key, $iv); 3201 | //解密 3202 | $decrypted = mdecrypt_generic($module, $ciphertext_dec); 3203 | mcrypt_generic_deinit($module); 3204 | mcrypt_module_close($module); 3205 | } catch (Exception $e) { 3206 | return array(ErrorCode::$DecryptAESError, null); 3207 | } 3208 | 3209 | 3210 | try { 3211 | //去除补位字符 3212 | $pkc_encoder = new PKCS7Encoder; 3213 | $result = $pkc_encoder->decode($decrypted); 3214 | //去除16位随机字符串,网络字节序和AppId 3215 | if (strlen($result) < 16) 3216 | return ""; 3217 | $content = substr($result, 16, strlen($result)); 3218 | $len_list = unpack("N", substr($content, 0, 4)); 3219 | $xml_len = $len_list[1]; 3220 | $xml_content = substr($content, 4, $xml_len); 3221 | $from_appid = substr($content, $xml_len + 4); 3222 | if (!$appid) 3223 | $appid = $from_appid; 3224 | //如果传入的appid是空的,则认为是订阅号,使用数据中提取出来的appid 3225 | } catch (Exception $e) { 3226 | //print $e; 3227 | return array(ErrorCode::$IllegalBuffer, null); 3228 | } 3229 | if ($from_appid != $appid) 3230 | return array(ErrorCode::$ValidateAppidError, null); 3231 | //不注释上边两行,避免传入appid是错误的情况 3232 | return array(0, $xml_content, $from_appid); //增加appid,为了解决后面加密回复消息的时候没有appid的订阅号会无法回复 3233 | 3234 | } 3235 | 3236 | 3237 | /** 3238 | * 随机生成16位字符串 3239 | * @return string 生成的字符串 3240 | */ 3241 | function getRandomStr() 3242 | { 3243 | 3244 | $str = ""; 3245 | $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; 3246 | $max = strlen($str_pol) - 1; 3247 | for ($i = 0; $i < 16; $i++) { 3248 | $str .= $str_pol[mt_rand(0, $max)]; 3249 | } 3250 | return $str; 3251 | } 3252 | 3253 | } 3254 | 3255 | /** 3256 | * error code 3257 | * 仅用作类内部使用,不用于官方API接口的errCode码 3258 | */ 3259 | class ErrorCode 3260 | { 3261 | public static $OK = 0; 3262 | public static $ValidateSignatureError = 40001; 3263 | public static $ParseXmlError = 40002; 3264 | public static $ComputeSignatureError = 40003; 3265 | public static $IllegalAesKey = 40004; 3266 | public static $ValidateAppidError = 40005; 3267 | public static $EncryptAESError = 40006; 3268 | public static $DecryptAESError = 40007; 3269 | public static $IllegalBuffer = 40008; 3270 | public static $EncodeBase64Error = 40009; 3271 | public static $DecodeBase64Error = 40010; 3272 | public static $GenReturnXmlError = 40011; 3273 | public static $errCode=array( 3274 | '0' => '处理成功', 3275 | '40001' => '校验签名失败', 3276 | '40002' => '解析xml失败', 3277 | '40003' => '计算签名失败', 3278 | '40004' => '不合法的AESKey', 3279 | '40005' => '校验AppID失败', 3280 | '40006' => 'AES加密失败', 3281 | '40007' => 'AES解密失败', 3282 | '40008' => '公众平台发送的xml不合法', 3283 | '40009' => 'Base64编码失败', 3284 | '40010' => 'Base64解码失败', 3285 | '40011' => '公众帐号生成回包xml失败' 3286 | ); 3287 | public static function getErrText($err) { 3288 | if (isset(self::$errCode[$err])) { 3289 | return self::$errCode[$err]; 3290 | }else { 3291 | return false; 3292 | }; 3293 | } 3294 | } 3295 | -------------------------------------------------------------------------------- /src/Atan/Wechat/WechatServiceProvider.php: -------------------------------------------------------------------------------- 1 | package('atan/wechat'); 22 | } 23 | 24 | /** 25 | * Register the service provider. 26 | * 27 | * @return void 28 | */ 29 | public function register() 30 | { 31 | $this->app->bind('wechat', 'Atan\Wechat\Wechat'); 32 | } 33 | 34 | /** 35 | * Get the services provided by the provider. 36 | * 37 | * @return array 38 | */ 39 | public function provides() 40 | { 41 | return array('wechat'); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/config/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanshiqi/laravel-wechat/f1755966efe8c3347c090ea245dd06f7961e177b/src/config/.gitkeep -------------------------------------------------------------------------------- /src/config/wechat.php: -------------------------------------------------------------------------------- 1 | array( 5 | 'token' => 'token', 6 | 'encodingaeskey' => 'encodingaeskey', 7 | 'appid' => 'appid', 8 | 'appsecret' => 'appsecret', 9 | 'debug' => false, 10 | 'logcallback' => false, 11 | ) 12 | ); 13 | -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanshiqi/laravel-wechat/f1755966efe8c3347c090ea245dd06f7961e177b/tests/.gitkeep --------------------------------------------------------------------------------