├── docs ├── .nojekyll ├── thinkphp.md ├── crm.md ├── corp.md ├── token.md ├── auth.md ├── agent.md ├── checkin.md ├── jssdk.md ├── laravel.md ├── media.md ├── menu.md ├── department.md ├── batch.md ├── invoice.md ├── tag.md ├── chat.md ├── _sidebar.md ├── quick-start.md ├── README.md ├── user.md ├── index.html ├── callback.md └── message.md ├── examples ├── public │ ├── wework.txt │ └── 20180103195745.png ├── cache │ └── .gitignore ├── log │ └── .gitignore ├── crm.php ├── corp.php ├── token.php ├── ticket.php ├── jssdk.php ├── jsticket.php ├── agent.php ├── checkin.php ├── menu.php ├── app.php ├── media.php ├── batch.php ├── invoice.php ├── department.php ├── tag.php ├── callback.php ├── user.php ├── message.php └── chat.php ├── .gitignore ├── .travis.yml ├── src ├── Message │ ├── ReplyMessageInterface.php │ ├── ResponseMessageInterface.php │ ├── File.php │ ├── Markdown.php │ ├── MPNews.php │ ├── Text.php │ ├── Image.php │ ├── Voice.php │ ├── News.php │ ├── Receiver.php │ ├── TextCard.php │ ├── Video.php │ ├── MPArticle.php │ └── Article.php ├── Laravel │ ├── LogBridge.php │ ├── CacheBridge.php │ ├── config.php │ └── WeWorkServiceProvider.php ├── Traits │ ├── CorpIdTrait.php │ ├── AgentIdTrait.php │ ├── SecretTrait.php │ ├── TicketTrait.php │ ├── CacheTrait.php │ ├── JsApiTicketTrait.php │ └── HttpClientTrait.php ├── Api │ ├── CRM.php │ ├── Corp.php │ ├── Message.php │ ├── Agent.php │ ├── Menu.php │ ├── Department.php │ ├── CheckIn.php │ ├── Media.php │ ├── Batch.php │ ├── AppChat.php │ ├── Invoice.php │ ├── Tag.php │ └── User.php ├── ApiCache │ ├── Ticket.php │ ├── JsApiTicket.php │ ├── Token.php │ └── AbstractApiCache.php ├── Http │ ├── Response.php │ ├── ClientFactory.php │ ├── HttpClientInterface.php │ ├── HttpClient.php │ └── Middleware.php ├── Crypt │ ├── SHA1.php │ ├── ErrorCode.php │ ├── PKCS7Encoder.php │ ├── XMLParse.php │ ├── PrpCrypt.php │ └── WXBizMsgCrypt.php ├── JSSdk.php ├── Callback.php └── App.php ├── .editorconfig ├── tests ├── Message │ ├── FileTest.php │ ├── MarkdownTest.php │ ├── TextCardTest.php │ ├── MPArticleTest.php │ ├── TextTest.php │ ├── ImageTest.php │ ├── VoiceTest.php │ ├── ArticleTest.php │ ├── MPNewsTest.php │ ├── VideoTest.php │ ├── NewsTest.php │ └── ReceiverTest.php ├── Http │ ├── ClientFactoryTest.php │ ├── ResponseTest.php │ ├── MiddlewareTest.php │ └── HttpClientTest.php ├── TestCase.php ├── Api │ ├── CRMTest.php │ ├── CorpTest.php │ ├── MessageTest.php │ ├── AgentTest.php │ ├── MenuTest.php │ ├── MediaTest.php │ ├── InvoiceTest.php │ ├── AppChatTest.php │ ├── DepartmentTest.php │ ├── BatchTest.php │ ├── CheckInTest.php │ ├── TagTest.php │ └── UserTest.php ├── ApiCache │ ├── TicketTest.php │ ├── JsTicketTest.php │ ├── TokenTest.php │ └── AbstractApiCacheTest.php ├── AppTest.php ├── JSSdkTest.php ├── CallbackTest.php └── Crypt │ └── WXBizMsgCryptTest.php ├── phpunit.xml.dist ├── README.md ├── LICENSE └── composer.json /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/public/wework.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/public/20180103195745.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /examples/log/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /vendor/ 3 | /composer.lock 4 | -------------------------------------------------------------------------------- /docs/thinkphp.md: -------------------------------------------------------------------------------- 1 | # ThinkPHP 2 | 3 | 请参考 [pithyone/think-wechat](https://github.com/pithyone/think-wechat) 4 | -------------------------------------------------------------------------------- /docs/crm.md: -------------------------------------------------------------------------------- 1 | # 外部联系人管理 2 | 3 | ```php 4 | $crm = $app->get('crm'); 5 | ``` 6 | 7 | ## 获取外部联系人详情 8 | 9 | ```php 10 | $crm->getExternalContact('EXTERNAL_USERID'); 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/corp.md: -------------------------------------------------------------------------------- 1 | # 审批管理 2 | 3 | ```php 4 | $corp = $app->get('corp'); 5 | ``` 6 | 7 | ## 获取审批数据 8 | 9 | ```php 10 | $corp->getApprovalData(1492617600, 1492790400, 201704200003); 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/token.md: -------------------------------------------------------------------------------- 1 | # access_token 2 | 3 | ```php 4 | $token = $app->get('token'); 5 | ``` 6 | 7 | ## 获取 8 | 9 | ```php 10 | $token->get(); 11 | ``` 12 | 13 | ## 刷新 14 | 15 | ```php 16 | $token->get(true); 17 | ``` 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.1 5 | - 7.2 6 | 7 | before_install: composer self-update 8 | 9 | install: composer install --prefer-source --no-interaction --dev 10 | 11 | script: ./vendor/bin/phpunit 12 | -------------------------------------------------------------------------------- /src/Message/ReplyMessageInterface.php: -------------------------------------------------------------------------------- 1 | get('crm'); 7 | 8 | try { 9 | $crm->getExternalContact('EXTERNAL_USERID'); 10 | } catch (Exception $e) { 11 | } 12 | -------------------------------------------------------------------------------- /src/Message/ResponseMessageInterface.php: -------------------------------------------------------------------------------- 1 | get('user'); 5 | ``` 6 | 7 | ## 根据code获取成员信息 8 | 9 | ```php 10 | $user->getInfo('CODE'); 11 | ``` 12 | 13 | ## 使用user_ticket获取成员详情 14 | 15 | ```php 16 | $user->getDetail('USER_TICKET'); 17 | ``` 18 | -------------------------------------------------------------------------------- /examples/corp.php: -------------------------------------------------------------------------------- 1 | get('corp'); 7 | 8 | try { 9 | $corp->getApprovalData(1492617600, 1492790400, 201704200003); 10 | } catch (Exception $e) { 11 | } 12 | -------------------------------------------------------------------------------- /docs/agent.md: -------------------------------------------------------------------------------- 1 | # 应用管理 2 | 3 | ```php 4 | $agent = $app->get('agent'); 5 | ``` 6 | 7 | ## 获取应用 8 | 9 | ```php 10 | $agent->get(); 11 | ``` 12 | 13 | ## 设置应用 14 | 15 | ```php 16 | $agent->set([ 17 | 'close' => 0 18 | ]); 19 | ``` 20 | 21 | ## 获取应用列表 22 | 23 | ```php 24 | $agent->list(); 25 | ``` 26 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.yml] 15 | indent_style = space 16 | indent_size = 2 -------------------------------------------------------------------------------- /docs/checkin.md: -------------------------------------------------------------------------------- 1 | # 打卡管理 2 | 3 | ```php 4 | $checkIn = $app->get('checkIn'); 5 | ``` 6 | 7 | ## 获取打卡规则 8 | 9 | ```php 10 | $checkIn->getOption(1511971200, ['james', 'paul']); 11 | ``` 12 | 13 | ## 获取打卡数据 14 | 15 | ```php 16 | $checkIn->getData(\WeWork\Api\CheckIn::TYPE_ALL, 1492617600, 1492790400, ['james', 'paul']); 17 | ``` 18 | -------------------------------------------------------------------------------- /src/Laravel/LogBridge.php: -------------------------------------------------------------------------------- 1 | corpId = $corpId; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Traits/AgentIdTrait.php: -------------------------------------------------------------------------------- 1 | agentId = $agentId; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Traits/SecretTrait.php: -------------------------------------------------------------------------------- 1 | secret = $secret; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /examples/token.php: -------------------------------------------------------------------------------- 1 | get('token'); 7 | 8 | try { 9 | $token->get(); 10 | } catch (\Psr\SimpleCache\InvalidArgumentException $e) { 11 | } 12 | 13 | try { 14 | $token->get(true); 15 | } catch (\Psr\SimpleCache\InvalidArgumentException $e) { 16 | } 17 | -------------------------------------------------------------------------------- /examples/ticket.php: -------------------------------------------------------------------------------- 1 | get('ticket'); 7 | 8 | try { 9 | $ticket->get(); 10 | } catch (\Psr\SimpleCache\InvalidArgumentException $e) { 11 | } 12 | 13 | try { 14 | $ticket->get(true); 15 | } catch (\Psr\SimpleCache\InvalidArgumentException $e) { 16 | } 17 | -------------------------------------------------------------------------------- /docs/jssdk.md: -------------------------------------------------------------------------------- 1 | # JS-SDK 2 | 3 | ## 获取企业的jsapi_ticket 4 | 5 | ```php 6 | $app->get('jsApiTicket')->get(); 7 | ``` 8 | 9 | ## 生成权限验证配置 10 | 11 | ```php 12 | $app->get('jssdk')->getConfig('URL'); 13 | ``` 14 | 15 | ## 获取电子发票ticket 16 | 17 | ```php 18 | $app->get('ticket')->get(); 19 | ``` 20 | 21 | ## 生成拉起电子发票列表配置 22 | 23 | ```php 24 | $app->get('jssdk')->getChooseInvoiceConfig(); 25 | ``` 26 | -------------------------------------------------------------------------------- /src/Traits/TicketTrait.php: -------------------------------------------------------------------------------- 1 | ticket = $ticket; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/laravel.md: -------------------------------------------------------------------------------- 1 | # Laravel 2 | 3 | ## 创建配置文件 4 | 5 | ``` bash 6 | php artisan vendor:publish --provider="WeWork\Laravel\WeWorkServiceProvider" 7 | ``` 8 | 9 | ## 用法 10 | 11 | 以获取 access_token 为例: 12 | 13 | ```php 14 | app('wework')->get('token')->get(); 15 | ``` 16 | 17 | 使用其他应用时,改变默认设置即可,假设有一个测试应用为 `test` 18 | 19 | ```php 20 | app()->make('wework', ['default' => 'test'])->get('token')->get(); 21 | ``` 22 | -------------------------------------------------------------------------------- /examples/jssdk.php: -------------------------------------------------------------------------------- 1 | get('jssdk'); 7 | 8 | try { 9 | var_dump($jssdk->getConfig('URL')); 10 | } catch (\Psr\SimpleCache\InvalidArgumentException $e) { 11 | } 12 | 13 | try { 14 | var_dump($jssdk->getChooseInvoiceConfig()); 15 | } catch (\Psr\SimpleCache\InvalidArgumentException $e) { 16 | } 17 | -------------------------------------------------------------------------------- /examples/jsticket.php: -------------------------------------------------------------------------------- 1 | get('jsApiTicket'); 7 | 8 | try { 9 | $jsApiTicket->get(); 10 | } catch (\Psr\SimpleCache\InvalidArgumentException $e) { 11 | } 12 | 13 | try { 14 | $jsApiTicket->get(true); 15 | } catch (\Psr\SimpleCache\InvalidArgumentException $e) { 16 | } 17 | -------------------------------------------------------------------------------- /examples/agent.php: -------------------------------------------------------------------------------- 1 | get('agent'); 7 | 8 | try { 9 | $agent->get(); 10 | } catch (Exception $e) { 11 | } 12 | 13 | try { 14 | $agent->set([ 15 | 'close' => 0 16 | ]); 17 | } catch (Exception $e) { 18 | } 19 | 20 | try { 21 | $agent->list(); 22 | } catch (Exception $e) { 23 | } 24 | -------------------------------------------------------------------------------- /examples/checkin.php: -------------------------------------------------------------------------------- 1 | get('checkIn'); 7 | 8 | try { 9 | $checkIn->getOption(1511971200, ['james', 'paul']); 10 | } catch (Exception $e) { 11 | } 12 | 13 | try { 14 | $checkIn->getData(\WeWork\Api\CheckIn::TYPE_ALL, 1492617600, 1492790400, ['james', 'paul']); 15 | } catch (Exception $e) { 16 | } 17 | -------------------------------------------------------------------------------- /src/Traits/CacheTrait.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/media.md: -------------------------------------------------------------------------------- 1 | # 素材管理 2 | 3 | ```php 4 | $media = $app->get('media'); 5 | ``` 6 | 7 | ## 上传临时素材 8 | 9 | ```php 10 | $media->upload('file', __DIR__ . '/public/wework.txt'); 11 | ``` 12 | 13 | ## 获取临时素材 14 | 15 | ```php 16 | $media->get('MEDIA_ID'); 17 | ``` 18 | 19 | ## 获取高清语音素材 20 | 21 | ```php 22 | $media->getVoice('MEDIA_ID'); 23 | ``` 24 | 25 | ## 上传图片 26 | 27 | ```php 28 | $media->uploadImg(__DIR__ . '/public/20180103195745.png'); 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/menu.md: -------------------------------------------------------------------------------- 1 | # 自定义菜单 2 | 3 | ```php 4 | $menu = $app->get('menu'); 5 | ``` 6 | 7 | ## 创建菜单 8 | 9 | ```php 10 | $menu->create([ 11 | 'button' => [ 12 | [ 13 | 'type' => 'click', 14 | 'name' => '今日歌曲', 15 | 'key' => 'V1001_TODAY_MUSIC' 16 | ] 17 | ] 18 | ]); 19 | ``` 20 | 21 | ## 获取菜单 22 | 23 | ```php 24 | $menu->get(); 25 | ``` 26 | 27 | ## 删除菜单 28 | 29 | ```php 30 | $menu->delete(); 31 | ``` 32 | -------------------------------------------------------------------------------- /src/Traits/JsApiTicketTrait.php: -------------------------------------------------------------------------------- 1 | jsApiTicket = $jsApiTicket; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Traits/HttpClientTrait.php: -------------------------------------------------------------------------------- 1 | httpClient = $httpClient; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Api/CRM.php: -------------------------------------------------------------------------------- 1 | httpClient->get('crm/get_external_contact', ['external_userid' => $externalUserId]); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/department.md: -------------------------------------------------------------------------------- 1 | # 部门管理 2 | 3 | ```php 4 | $department = $app->get('department'); 5 | ``` 6 | 7 | ## 创建部门 8 | 9 | ```php 10 | $department->create([ 11 | 'name' => '广州研发中心', 12 | 'parentid' => 1, 13 | 'order' => 1, 14 | 'id' => 1024 15 | ]); 16 | ``` 17 | 18 | ## 更新部门 19 | 20 | ```php 21 | $department->update([ 22 | 'id' => 1024, 23 | 'name' => '广州研发中心', 24 | ]); 25 | ``` 26 | 27 | ## 删除部门 28 | 29 | ```php 30 | $department->delete(1024); 31 | ``` 32 | 33 | ## 获取部门列表 34 | 35 | ```php 36 | $department->list(); 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/batch.md: -------------------------------------------------------------------------------- 1 | # 异步批量接口 2 | 3 | ```php 4 | $batch = $app->get('batch'); 5 | ``` 6 | 7 | ## 邀请成员 8 | 9 | ```php 10 | $batch->invite([ 11 | 'user' => ['UserID1', 'UserID2', 'UserID3'] 12 | ]); 13 | ``` 14 | 15 | ## 增量更新成员 16 | 17 | ```php 18 | $batch->syncUser(['media_id' => 'xxxxxx']); 19 | ``` 20 | 21 | ## 全量覆盖成员 22 | 23 | ```php 24 | $batch->replaceUser(['media_id' => 'xxxxxx']); 25 | ``` 26 | 27 | ## 全量覆盖部门 28 | 29 | ```php 30 | $batch->replaceParty(['media_id' => 'xxxxxx']); 31 | ``` 32 | 33 | ## 获取异步任务结果 34 | 35 | ```php 36 | $batch->getResult('JOBID'); 37 | ``` 38 | -------------------------------------------------------------------------------- /examples/menu.php: -------------------------------------------------------------------------------- 1 | get('menu'); 7 | 8 | try { 9 | $menu->create([ 10 | 'button' => [ 11 | [ 12 | 'type' => 'click', 13 | 'name' => '今日歌曲', 14 | 'key' => 'V1001_TODAY_MUSIC' 15 | ] 16 | ] 17 | ]); 18 | } catch (Exception $e) { 19 | } 20 | 21 | try { 22 | $menu->get(); 23 | } catch (Exception $e) { 24 | } 25 | 26 | try { 27 | $menu->delete(); 28 | } catch (Exception $e) { 29 | } 30 | -------------------------------------------------------------------------------- /src/ApiCache/Ticket.php: -------------------------------------------------------------------------------- 1 | httpClient->get('ticket/get', ['type' => 'wx_card']); 25 | 26 | return $data['ticket']; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /docs/invoice.md: -------------------------------------------------------------------------------- 1 | # 电子发票 2 | 3 | ```php 4 | $invoice = $app->get('invoice'); 5 | ``` 6 | 7 | ## 查询电子发票 8 | 9 | ```php 10 | $invoice->getInfo('CARDID', 'ENCRYPTCODE'); 11 | ``` 12 | 13 | ## 更新发票状态 14 | 15 | ```php 16 | $invoice->updateStatus('CARDID', 'ENCRYPTCODE', 'INVOICE_REIMBURSE_INIT'); 17 | ``` 18 | 19 | ## 批量更新发票状态 20 | 21 | ```php 22 | $invoice->updateStatusBatch('OPENID', 'INVOICE_REIMBURSE_INIT', [ 23 | ['card_id' => 'CARDID', 'encrypt_code' => 'ENCRYPTCODE'] 24 | ]); 25 | ``` 26 | 27 | ## 批量查询电子发票 28 | 29 | ```php 30 | $invoice->getInfoBatch([ 31 | ['card_id' => 'CARDID', 'encrypt_code' => 'ENCRYPTCODE'] 32 | ]); 33 | ``` 34 | -------------------------------------------------------------------------------- /examples/app.php: -------------------------------------------------------------------------------- 1 | 'xxxxxxxxxxxxxxxxxx', 8 | 'secret' => 'fsAQmTHCGvd5ZsRJnkr9jj9aCR2sqOKypAUw6D3Jy5Y', 9 | 'agent_id' => 0, 10 | 'token' => 'cxvhat5gfJW30J9YUsq', 11 | 'aes_key' => 'ULPUgqKDC8LTI1fRJ1jOxHrwbUV2QxR5jnSKrlh4meT', 12 | 'cache' => [ 13 | 'path' => __DIR__.'/cache', 14 | ], 15 | 'log' => [ 16 | 'file' => __DIR__.'/log/app.log', 17 | 'level' => 'debug', 18 | ], 19 | ] 20 | ); 21 | 22 | return $app; 23 | -------------------------------------------------------------------------------- /docs/tag.md: -------------------------------------------------------------------------------- 1 | # 标签管理 2 | 3 | ```php 4 | $tag = $app->get('tag'); 5 | ``` 6 | 7 | ## 创建标签 8 | 9 | ```php 10 | $tag->create('UI', 1024); 11 | ``` 12 | 13 | ## 更新标签名字 14 | 15 | ```php 16 | $tag->update(1024, 'UI design'); 17 | ``` 18 | 19 | ## 删除标签 20 | 21 | ```php 22 | $tag->delete(1024); 23 | ``` 24 | 25 | ## 获取标签成员 26 | 27 | ```php 28 | $tag->get(1024); 29 | ``` 30 | 31 | ## 增加标签成员 32 | 33 | ```php 34 | $tag->addUsers(['tagid' => 1024, 'userlist' => ['user1', 'user2']]); 35 | ``` 36 | 37 | ## 删除标签成员 38 | 39 | ```php 40 | $tag->delUsers(['tagid' => 1024, 'userlist' => ['user1', 'user2']]); 41 | ``` 42 | 43 | ## 获取标签列表 44 | 45 | ```php 46 | $tag->list(); 47 | ``` 48 | -------------------------------------------------------------------------------- /src/Message/File.php: -------------------------------------------------------------------------------- 1 | mediaId = $mediaId; 18 | } 19 | 20 | /** 21 | * @return array 22 | */ 23 | public function formatForResponse(): array 24 | { 25 | return [ 26 | 'msgtype' => 'file', 27 | 'file' => [ 28 | 'media_id' => $this->mediaId 29 | ] 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/media.php: -------------------------------------------------------------------------------- 1 | get('media'); 7 | 8 | try { 9 | $media->upload('file', __DIR__ . '/public/wework.txt'); 10 | } catch (Exception $e) { 11 | } 12 | 13 | try { 14 | $data = $media->get('MEDIA_ID'); 15 | file_put_contents(__DIR__ . '/app/MEDIA_ID.jpg', $data); 16 | } catch (Exception $e) { 17 | } 18 | 19 | try { 20 | $data = $media->getVoice('MEDIA_ID'); 21 | file_put_contents(__DIR__ . '/app/XXX', $data); 22 | } catch (Exception $e) { 23 | } 24 | 25 | try { 26 | $media->uploadImg(__DIR__ . '/public/20180103195745.png'); 27 | } catch (Exception $e) { 28 | } 29 | -------------------------------------------------------------------------------- /examples/batch.php: -------------------------------------------------------------------------------- 1 | get('batch'); 7 | 8 | try { 9 | $batch->invite([ 10 | 'user' => ['UserID1', 'UserID2', 'UserID3'] 11 | ]); 12 | } catch (Exception $e) { 13 | } 14 | 15 | try { 16 | $batch->syncUser(['media_id' => 'xxxxxx']); 17 | } catch (Exception $e) { 18 | } 19 | 20 | try { 21 | $batch->replaceUser(['media_id' => 'xxxxxx']); 22 | } catch (Exception $e) { 23 | } 24 | 25 | try { 26 | $batch->replaceParty(['media_id' => 'xxxxxx']); 27 | } catch (Exception $e) { 28 | } 29 | 30 | try { 31 | $batch->getResult('JOBID'); 32 | } catch (Exception $e) { 33 | } 34 | -------------------------------------------------------------------------------- /src/ApiCache/JsApiTicket.php: -------------------------------------------------------------------------------- 1 | secret); 18 | 19 | return md5('wework.api.js_ticket.' . $unique); 20 | } 21 | 22 | /** 23 | * @return string 24 | */ 25 | protected function getFromServer(): string 26 | { 27 | $data = $this->httpClient->get('get_jsapi_ticket'); 28 | 29 | return $data['ticket']; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/chat.md: -------------------------------------------------------------------------------- 1 | # 发送消息到群聊会话 2 | 3 | ```php 4 | $chat = $app->get('appChat'); 5 | ``` 6 | 7 | ## 创建群聊会话 8 | 9 | ```php 10 | $chat->create([ 11 | 'name' => 'NAME', 12 | 'owner' => 'userid1', 13 | 'userlist' => ['userid1', 'userid2'], 14 | 'chatid' => 'CHATID' 15 | ]); 16 | ``` 17 | 18 | ## 修改群聊会话 19 | 20 | ```php 21 | $chat->update([ 22 | 'chatid' => 'CHATID', 23 | 'name' => 'NAME', 24 | 'owner' => 'userid2' 25 | ]); 26 | ``` 27 | 28 | ## 获取群聊会话 29 | 30 | ```php 31 | $chat->get('CHATID'); 32 | ``` 33 | 34 | ## 应用推送消息 35 | 36 | ```php 37 | $text = new \WeWork\Message\Text("你的快递已到,请携带工卡前往邮件中心领取。\n出发前可查看邮件中心视频实况,聪明避开排队。"); 38 | $chat->send('CHATID', $text, true); 39 | ``` 40 | -------------------------------------------------------------------------------- /src/Message/Markdown.php: -------------------------------------------------------------------------------- 1 | content = $content; 20 | } 21 | 22 | /** 23 | * @inheritDoc 24 | */ 25 | public function formatForResponse(): array 26 | { 27 | return [ 28 | 'msgtype' => 'markdown', 29 | 'markdown' => [ 30 | 'content' => $this->content, 31 | ] 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * 入门 2 | 3 | * [快速开始](quick-start.md) 4 | * [access_token](token.md) 5 | 6 | * 通讯录管理 7 | 8 | * [成员管理](user.md) 9 | * [部门管理](department.md) 10 | * [标签管理](tag.md) 11 | * [异步批量接口](batch.md) 12 | 13 | * [外部联系人管理](crm.md) 14 | 15 | * [应用管理](agent.md) 16 | 17 | * [自定义菜单](menu.md) 18 | 19 | * 消息推送 20 | 21 | * [发送应用消息](message.md) 22 | * [接收消息与事件](callback.md) 23 | * [发送消息到群聊会话](chat.md) 24 | 25 | * [素材管理](media.md) 26 | 27 | * 身份验证 28 | 29 | * [网页授权登录](auth.md) 30 | 31 | * 移动端SDK 32 | 33 | * [JS-SDK](jssdk.md) 34 | 35 | * OA数据接口 36 | 37 | * [打卡管理](checkin.md) 38 | * [审批管理](corp.md) 39 | 40 | * [电子发票](invoice.md) 41 | 42 | * 集成 43 | 44 | * [Laravel](laravel.md) 45 | * [ThinkPHP](thinkphp.md) 46 | -------------------------------------------------------------------------------- /src/Http/Response.php: -------------------------------------------------------------------------------- 1 | getBody(), true); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Api/Corp.php: -------------------------------------------------------------------------------- 1 | $startTime, 23 | 'endtime' => $endTime 24 | ]; 25 | 26 | if ($nextSPNum > 0) { 27 | $json['next_spnum'] = $nextSPNum; 28 | } 29 | 30 | return $this->httpClient->postJson('corp/getapprovaldata', $json); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/invoice.php: -------------------------------------------------------------------------------- 1 | get('invoice'); 7 | 8 | try { 9 | $invoice->getInfo('CARDID', 'ENCRYPTCODE'); 10 | } catch (Exception $e) { 11 | } 12 | 13 | try { 14 | $invoice->updateStatus('CARDID', 'ENCRYPTCODE', 'INVOICE_REIMBURSE_INIT'); 15 | } catch (Exception $e) { 16 | } 17 | 18 | try { 19 | $invoice->updateStatusBatch('OPENID', 'INVOICE_REIMBURSE_INIT', [ 20 | ['card_id' => 'CARDID', 'encrypt_code' => 'ENCRYPTCODE'] 21 | ]); 22 | } catch (Exception $e) { 23 | } 24 | 25 | try { 26 | $invoice->getInfoBatch([ 27 | ['card_id' => 'CARDID', 'encrypt_code' => 'ENCRYPTCODE'] 28 | ]); 29 | } catch (Exception $e) { 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/department.php: -------------------------------------------------------------------------------- 1 | get('department'); 7 | 8 | try { 9 | $department->create([ 10 | 'name' => '广州研发中心', 11 | 'parentid' => 1, 12 | 'order' => 1, 13 | 'id' => 1024 14 | ]); 15 | } catch (Exception $e) { 16 | } 17 | 18 | try { 19 | $department->update([ 20 | 'id' => 1024, 21 | 'name' => '广州研发中心', 22 | ]); 23 | } catch (Exception $e) { 24 | } 25 | 26 | try { 27 | $department->delete(1024); 28 | } catch (Exception $e) { 29 | } 30 | 31 | try { 32 | $department->list(); 33 | } catch (Exception $e) { 34 | } 35 | 36 | try { 37 | $department->list(1); 38 | } catch (Exception $e) { 39 | } 40 | -------------------------------------------------------------------------------- /docs/quick-start.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | ## 安装 4 | 5 | ``` bash 6 | composer require pithyone/wechat 7 | ``` 8 | 9 | ## 用法 10 | 11 | 初始化: 12 | 13 | ```php 14 | $app = new \WeWork\App( 15 | [ 16 | 'corp_id' => 'xxxxxxxxxxxxxxxxxx', 17 | 'secret' => 'fsAQmTHCGvd5ZsRJnkr9jj9aCR2sqOKypAUw6D3Jy5Y', 18 | 'agent_id' => 0, 19 | 'token' => 'cxvhat5gfJW30J9YUsq', 20 | 'aes_key' => 'ULPUgqKDC8LTI1fRJ1jOxHrwbUV2QxR5jnSKrlh4meT', 21 | 'cache' => [ 22 | 'path' => __DIR__.'/cache', 23 | ], 24 | 'log' => [ 25 | 'file' => __DIR__.'/log/app.log', 26 | 'level' => 'debug', 27 | ], 28 | ] 29 | ); 30 | ``` 31 | 32 | 以获取 access_token 为例: 33 | 34 | ```php 35 | $app->get('token')->get(); 36 | ``` 37 | -------------------------------------------------------------------------------- /tests/Message/FileTest.php: -------------------------------------------------------------------------------- 1 | file = new File('media_id'); 23 | } 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function testFormatForResponse() 29 | { 30 | $this->assertEquals([ 31 | 'msgtype' => 'file', 32 | 'file' => [ 33 | 'media_id' => 'media_id' 34 | ] 35 | ], $this->file->formatForResponse()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | 18 | ./src 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/tag.php: -------------------------------------------------------------------------------- 1 | get('tag'); 7 | 8 | try { 9 | $tag->create('UI', 1024); 10 | } catch (Exception $e) { 11 | } 12 | 13 | try { 14 | $tag->update(1024, 'UI design'); 15 | } catch (Exception $e) { 16 | } 17 | 18 | try { 19 | $tag->delete(1024); 20 | } catch (Exception $e) { 21 | } 22 | 23 | try { 24 | $tag->get(1024); 25 | } catch (Exception $e) { 26 | } 27 | 28 | try { 29 | $tag->addUsers(['tagid' => 1024, 'userlist' => ['user1', 'user2']]); 30 | } catch (Exception $e) { 31 | } 32 | 33 | try { 34 | $tag->delUsers(['tagid' => 1024, 'userlist' => ['user1', 'user2']]); 35 | } catch (Exception $e) { 36 | } 37 | 38 | try { 39 | $tag->list(); 40 | } catch (Exception $e) { 41 | } 42 | -------------------------------------------------------------------------------- /src/ApiCache/Token.php: -------------------------------------------------------------------------------- 1 | secret); 19 | 20 | return md5('wework.api.token.' . $unique); 21 | } 22 | 23 | /** 24 | * @return string 25 | */ 26 | protected function getFromServer(): string 27 | { 28 | $data = $this->httpClient->get('gettoken', ['corpid' => $this->corpId, 'corpsecret' => $this->secret]); 29 | 30 | return $data['access_token']; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Message/MPNews.php: -------------------------------------------------------------------------------- 1 | articles = $articles; 18 | } 19 | 20 | /** 21 | * @return array 22 | */ 23 | public function formatForResponse(): array 24 | { 25 | return [ 26 | 'msgtype' => 'mpnews', 27 | 'mpnews' => [ 28 | 'articles' => array_map(function (MPArticle $article) { 29 | return $article->formatForResponse(); 30 | }, $this->articles) 31 | ] 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Message/MarkdownTest.php: -------------------------------------------------------------------------------- 1 | markdown = new Markdown('content'); 23 | } 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function testFormatForResponse() 29 | { 30 | $this->assertEquals([ 31 | 'msgtype' => 'markdown', 32 | 'markdown' => [ 33 | 'content' => 'content' 34 | ] 35 | ], $this->textCard->formatForResponse()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Http/ClientFactoryTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Client::class, ClientFactory::create(\Mockery::mock(LoggerInterface::class))); 19 | } 20 | 21 | /** 22 | * @return void 23 | */ 24 | public function testCreateWithToken() 25 | { 26 | $this->assertInstanceOf(Client::class, ClientFactory::create( 27 | \Mockery::mock(LoggerInterface::class), 28 | \Mockery::mock(Token::class) 29 | )); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | getMethod($name); 20 | $method->setAccessible(true); 21 | return $method->invokeArgs($object, $args); 22 | } 23 | 24 | /** 25 | * @inheritdoc 26 | */ 27 | protected function tearDown() 28 | { 29 | parent::tearDown(); 30 | 31 | \Mockery::close(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Crypt/SHA1.php: -------------------------------------------------------------------------------- 1 | 最最最简单易用的企业微信SDK 9 | 10 | ## Requirement 11 | 12 | - PHP >= 7.1.3 13 | - SimpleXML PHP Extension 14 | - DOM PHP Extension 15 | - JSON PHP Extension 16 | - OpenSSL PHP Extension 17 | 18 | ## Installation 19 | 20 | ```bash 21 | composer require pithyone/wechat 22 | ``` 23 | 24 | ## License 25 | 26 | [MIT](https://github.com/pithyone/wechat/blob/master/LICENSE) 27 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/pithyone/wechat.svg?branch=master)](https://travis-ci.org/pithyone/wechat) 2 | [![Latest Stable Version](https://poser.pugx.org/pithyone/wechat/v/stable)](https://packagist.org/packages/pithyone/wechat) 3 | [![Latest Unstable Version](https://poser.pugx.org/pithyone/wechat/v/unstable)](https://packagist.org/packages/pithyone/wechat) 4 | [![License](https://poser.pugx.org/pithyone/wechat/license)](https://packagist.org/packages/pithyone/wechat) 5 | 6 | # WeWork 7 | 8 | > 最最最简单易用的企业微信SDK 9 | 10 | ## Requirement 11 | 12 | - PHP >= 7.1.3 13 | - SimpleXML PHP Extension 14 | - DOM PHP Extension 15 | - JSON PHP Extension 16 | - OpenSSL PHP Extension 17 | 18 | ## Installation 19 | 20 | ```bash 21 | composer require pithyone/wechat 22 | ``` 23 | 24 | ## License 25 | 26 | [MIT](https://github.com/pithyone/wechat/blob/master/LICENSE) 27 | -------------------------------------------------------------------------------- /src/Api/Message.php: -------------------------------------------------------------------------------- 1 | httpClient->postJson('message/send', array_merge( 25 | ['agentid' => $this->agentId], 26 | $receiver->get(), 27 | $responseMessage->formatForResponse(), 28 | ['safe' => (int)$safe] 29 | )); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ApiCache/AbstractApiCache.php: -------------------------------------------------------------------------------- 1 | getCacheKey(); 29 | 30 | $value = $this->cache->get($key); 31 | 32 | if ($refresh || !$value) { 33 | $value = $this->getFromServer(); 34 | $this->cache->set($key, $value, 7100); 35 | } 36 | 37 | return $value; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Api/Agent.php: -------------------------------------------------------------------------------- 1 | httpClient->get('agent/get', ['agentid' => $this->agentId]); 20 | } 21 | 22 | /** 23 | * 设置应用 24 | * 25 | * @param array $json 26 | * @return array 27 | */ 28 | public function set(array $json): array 29 | { 30 | return $this->httpClient->postJson('agent/set', array_merge(['agentid' => $this->agentId], $json)); 31 | } 32 | 33 | /** 34 | * 获取应用列表 35 | * 36 | * @return array 37 | */ 38 | public function list(): array 39 | { 40 | return $this->httpClient->get('agent/list'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Http/ClientFactory.php: -------------------------------------------------------------------------------- 1 | push(Middleware::retry($logger)); 22 | $stack->push(Middleware::response()); 23 | $stack->push(Middleware::log($logger)); 24 | 25 | if ($token instanceof Token) { 26 | $stack->push(Middleware::auth($token)); 27 | } 28 | 29 | return new Client([ 30 | 'base_uri' => 'https://qyapi.weixin.qq.com/cgi-bin/', 31 | 'handler' => $stack 32 | ]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Message/Text.php: -------------------------------------------------------------------------------- 1 | content = $content; 18 | } 19 | 20 | /** 21 | * @return array 22 | */ 23 | public function formatForReply(): array 24 | { 25 | return [ 26 | 'MsgType' => 'text', 27 | 'Content' => $this->content 28 | ]; 29 | } 30 | 31 | /** 32 | * @return array 33 | */ 34 | public function formatForResponse(): array 35 | { 36 | return [ 37 | 'msgtype' => 'text', 38 | 'text' => [ 39 | 'content' => $this->content 40 | ] 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Api/Menu.php: -------------------------------------------------------------------------------- 1 | httpClient->postJson('menu/create', $json, ['agentid' => $this->agentId]); 21 | } 22 | 23 | /** 24 | * 获取菜单 25 | * 26 | * @return array 27 | */ 28 | public function get(): array 29 | { 30 | return $this->httpClient->get('menu/get', ['agentid' => $this->agentId]); 31 | } 32 | 33 | /** 34 | * 删除菜单 35 | * 36 | * @return array 37 | */ 38 | public function delete(): array 39 | { 40 | return $this->httpClient->get('menu/delete', ['agentid' => $this->agentId]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Message/TextCardTest.php: -------------------------------------------------------------------------------- 1 | textCard = new TextCard('title', 'description', 'url', 'btntxt'); 23 | } 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function testFormatForResponse() 29 | { 30 | $this->assertEquals([ 31 | 'msgtype' => 'textcard', 32 | 'textcard' => [ 33 | 'title' => 'title', 34 | 'description' => 'description', 35 | 'url' => 'url', 36 | 'btntxt' => 'btntxt' 37 | ] 38 | ], $this->textCard->formatForResponse()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Message/Image.php: -------------------------------------------------------------------------------- 1 | mediaId = $mediaId; 18 | } 19 | 20 | /** 21 | * @return array 22 | */ 23 | public function formatForReply(): array 24 | { 25 | return [ 26 | 'MsgType' => 'image', 27 | 'Image' => [ 28 | 'MediaId' => $this->mediaId 29 | ] 30 | ]; 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | public function formatForResponse(): array 37 | { 38 | return [ 39 | 'msgtype' => 'image', 40 | 'image' => [ 41 | 'media_id' => $this->mediaId 42 | ] 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Message/Voice.php: -------------------------------------------------------------------------------- 1 | mediaId = $mediaId; 18 | } 19 | 20 | /** 21 | * @return array 22 | */ 23 | public function formatForReply(): array 24 | { 25 | return [ 26 | 'MsgType' => 'voice', 27 | 'Voice' => [ 28 | 'MediaId' => $this->mediaId 29 | ] 30 | ]; 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | public function formatForResponse(): array 37 | { 38 | return [ 39 | 'msgtype' => 'voice', 40 | 'voice' => [ 41 | 'media_id' => $this->mediaId 42 | ] 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Http/HttpClientInterface.php: -------------------------------------------------------------------------------- 1 | article = new MPArticle('title', 'thumb_media_id', 'content', 'author', 'content_source_url', 'digest'); 23 | } 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function testFormatForResponse() 29 | { 30 | $this->assertEquals([ 31 | 'title' => 'title', 32 | 'thumb_media_id' => 'thumb_media_id', 33 | 'author' => 'author', 34 | 'content_source_url' => 'content_source_url', 35 | 'content' => 'content', 36 | 'digest' => 'digest' 37 | ], $this->article->formatForResponse()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Http/ResponseTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('getBody') 18 | ->once() 19 | ->withNoArgs() 20 | ->andReturn('{"errcode":0,"errmsg":"foo"}'); 21 | 22 | $this->assertEquals(['errcode' => 0, 'errmsg' => 'foo'], $response->toArray()); 23 | } 24 | 25 | /** 26 | * @expectedException \InvalidArgumentException 27 | */ 28 | public function testToArrayException() 29 | { 30 | $response = \Mockery::mock(Response::class . '[getBody]'); 31 | 32 | $response->shouldReceive('getBody') 33 | ->once() 34 | ->withNoArgs() 35 | ->andReturn(''); 36 | 37 | $response->toArray(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Message/TextTest.php: -------------------------------------------------------------------------------- 1 | text = new Text('content'); 23 | } 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function testFormatForReply() 29 | { 30 | $this->assertEquals([ 31 | 'MsgType' => 'text', 32 | 'Content' => 'content' 33 | ], $this->text->formatForReply()); 34 | } 35 | 36 | /** 37 | * @return void 38 | */ 39 | public function testFormatForResponse() 40 | { 41 | $this->assertEquals([ 42 | 'msgtype' => 'text', 43 | 'text' => [ 44 | 'content' => 'content' 45 | ] 46 | ], $this->text->formatForResponse()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/callback.php: -------------------------------------------------------------------------------- 1 | get('callback'); 7 | 8 | switch ($callback->get('MsgType')) { 9 | case 'text': 10 | echo $callback->reply(new \WeWork\Message\Text($callback->get('Content'))); // 文本消息 11 | break; 12 | case 'image': 13 | echo $callback->reply(new \WeWork\Message\Image($callback->get('MediaId'))); // 图片消息 14 | break; 15 | case 'voice': 16 | echo $callback->reply(new \WeWork\Message\Voice($callback->get('MediaId'))); // 语音消息 17 | break; 18 | case 'video': 19 | echo $callback->reply(new \WeWork\Message\Video($callback->get('MediaId'), 'Title', 'Description')); // 视频消息 20 | break; 21 | default: 22 | $article = new \WeWork\Message\Article('title', 'http://www.soso.com', 'description', 'http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png'); 23 | echo $callback->reply(new \WeWork\Message\News([$article])); // 图文消息 24 | break; 25 | } 26 | -------------------------------------------------------------------------------- /docs/user.md: -------------------------------------------------------------------------------- 1 | # 成员管理 2 | 3 | ```php 4 | $user = $app->get('user'); 5 | ``` 6 | 7 | ## 创建成员 8 | 9 | ```php 10 | $user->create([ 11 | 'userid' => 'zhangsan', 12 | 'name' => '张三', 13 | 'department' => [1], 14 | 'email' => 'zhangsan@gzdev.com' 15 | ]); 16 | ``` 17 | 18 | ## 读取成员 19 | 20 | ```php 21 | $user->get('zhangsan'); 22 | ``` 23 | 24 | ## 更新成员 25 | 26 | ```php 27 | $user->update([ 28 | 'userid' => 'zhangsan', 29 | 'name' => '张三三' 30 | ]); 31 | ``` 32 | 33 | ## 删除成员 34 | 35 | ```php 36 | $user->delete('zhangsan'); 37 | ``` 38 | 39 | ## 批量删除成员 40 | 41 | ```php 42 | $user->batchDelete(['zhangsan', 'lisi']); 43 | ``` 44 | 45 | ## 获取部门成员 46 | 47 | ```php 48 | $user->list(1); 49 | ``` 50 | 51 | ## 获取部门成员详情 52 | 53 | ```php 54 | $user->list(1, true, true); 55 | ``` 56 | 57 | ## userid转openid 58 | 59 | ```php 60 | $user->convertIdToOpenid('zhangsan'); 61 | ``` 62 | 63 | ## openid转userid 64 | 65 | ```php 66 | $user->convertOpenidToUserId('oDOGms-6yCnGrRovBj2yHij5JL6E'); 67 | ``` 68 | 69 | ## 二次验证 70 | 71 | ```php 72 | $user->authSuccess('zhangsan'); 73 | ``` 74 | -------------------------------------------------------------------------------- /tests/Http/MiddlewareTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(\Closure::class, Middleware::auth(\Mockery::mock(Token::class))); 18 | } 19 | 20 | /** 21 | * @return void 22 | */ 23 | public function testLog() 24 | { 25 | $this->assertInstanceOf(\Closure::class, Middleware::log(\Mockery::mock(LoggerInterface::class))); 26 | } 27 | 28 | /** 29 | * @return void 30 | */ 31 | public function testRetry() 32 | { 33 | $this->assertInstanceOf(\Closure::class, Middleware::retry(\Mockery::mock(LoggerInterface::class))); 34 | } 35 | 36 | /** 37 | * @return void 38 | */ 39 | public function testResponse() 40 | { 41 | $this->assertInstanceOf(\Closure::class, Middleware::response()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 王兵 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 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WeWork 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/Message/ImageTest.php: -------------------------------------------------------------------------------- 1 | image = new Image('media_id'); 23 | } 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function testFormatForReply() 29 | { 30 | $this->assertEquals([ 31 | 'MsgType' => 'image', 32 | 'Image' => [ 33 | 'MediaId' => 'media_id' 34 | ] 35 | ], $this->image->formatForReply()); 36 | } 37 | 38 | /** 39 | * @return void 40 | */ 41 | public function testFormatForResponse() 42 | { 43 | $this->assertEquals([ 44 | 'msgtype' => 'image', 45 | 'image' => [ 46 | 'media_id' => 'media_id' 47 | ] 48 | ], $this->image->formatForResponse()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Message/VoiceTest.php: -------------------------------------------------------------------------------- 1 | voice = new Voice('media_id'); 23 | } 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function testFormatForReply() 29 | { 30 | $this->assertEquals([ 31 | 'MsgType' => 'voice', 32 | 'Voice' => [ 33 | 'MediaId' => 'media_id' 34 | ] 35 | ], $this->voice->formatForReply()); 36 | } 37 | 38 | /** 39 | * @return void 40 | */ 41 | public function testFormatForResponse() 42 | { 43 | $this->assertEquals([ 44 | 'msgtype' => 'voice', 45 | 'voice' => [ 46 | 'media_id' => 'media_id' 47 | ] 48 | ], $this->voice->formatForResponse()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Api/CRMTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 30 | 31 | $this->crm = new CRM(); 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | public function testGet() 38 | { 39 | $this->httpClient->shouldReceive('get') 40 | ->once() 41 | ->with('crm/get_external_contact', ['external_userid' => 'foo']) 42 | ->andReturn(['errcode' => 0]); 43 | 44 | $this->crm->setHttpClient($this->httpClient); 45 | 46 | $this->assertEquals(['errcode' => 0], $this->crm->getExternalContact('foo')); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Crypt/ErrorCode.php: -------------------------------------------------------------------------------- 1 | 8 | *
  • -40001: 签名验证错误
  • 9 | *
  • -40002: xml解析失败
  • 10 | *
  • -40003: sha加密生成签名失败
  • 11 | *
  • -40004: encodingAesKey 非法
  • 12 | *
  • -40005: corpid 校验错误
  • 13 | *
  • -40006: aes 加密失败
  • 14 | *
  • -40007: aes 解密失败
  • 15 | *
  • -40008: 解密后得到的buffer非法
  • 16 | *
  • -40009: base64加密失败
  • 17 | *
  • -40010: base64解密失败
  • 18 | *
  • -40011: 生成xml失败
  • 19 | * 20 | */ 21 | class ErrorCode 22 | { 23 | public static $OK = 0; 24 | public static $ValidateSignatureError = -40001; 25 | public static $ParseXmlError = -40002; 26 | public static $ComputeSignatureError = -40003; 27 | public static $IllegalAesKey = -40004; 28 | public static $ValidateCorpidError = -40005; 29 | public static $EncryptAESError = -40006; 30 | public static $DecryptAESError = -40007; 31 | public static $IllegalBuffer = -40008; 32 | public static $EncodeBase64Error = -40009; 33 | public static $DecodeBase64Error = -40010; 34 | public static $GenReturnXmlError = -40011; 35 | } 36 | -------------------------------------------------------------------------------- /src/Laravel/CacheBridge.php: -------------------------------------------------------------------------------- 1 | toMinutes($ttl)); 18 | } 19 | 20 | public function delete($key) 21 | { 22 | // 23 | } 24 | 25 | public function clear() 26 | { 27 | // 28 | } 29 | 30 | public function getMultiple($keys, $default = null) 31 | { 32 | // 33 | } 34 | 35 | public function setMultiple($values, $ttl = null) 36 | { 37 | // 38 | } 39 | 40 | public function deleteMultiple($keys) 41 | { 42 | // 43 | } 44 | 45 | public function has($key) 46 | { 47 | return Cache::has($key); 48 | } 49 | 50 | protected function toMinutes($ttl = null) 51 | { 52 | if (!is_null($ttl)) { 53 | return $ttl / 60; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /docs/callback.md: -------------------------------------------------------------------------------- 1 | # 接收消息与事件 2 | 3 | 只需要配置 `corp_id` `token` `aes_key` 即可。 4 | 5 | ```php 6 | $callback = $app->get('callback'); 7 | ``` 8 | 9 | 示例: 10 | 11 | ```php 12 | switch ($callback->get('MsgType')) { 13 | case 'text': // 文本消息 14 | echo $callback->reply(new \WeWork\Message\Text($callback->get('Content'))); 15 | break; 16 | case 'image': // 图片消息 17 | echo $callback->reply(new \WeWork\Message\Image($callback->get('MediaId'))); 18 | break; 19 | case 'voice': // 语音消息 20 | echo $callback->reply(new \WeWork\Message\Voice($callback->get('MediaId'))); 21 | break; 22 | case 'video': // 视频消息 23 | $video = new \WeWork\Message\Video($callback->get('MediaId'), 'Title', 'Description'); 24 | echo $callback->reply($video); 25 | break; 26 | default: // 图文消息 27 | $article = new \WeWork\Message\Article( 28 | 'title', 29 | 'http://www.soso.com', 30 | 'description', 31 | 'http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png' 32 | ); 33 | echo $callback->reply(new \WeWork\Message\News([$article])); 34 | break; 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pithyone/wechat", 3 | "description": "企业微信SDK", 4 | "type": "library", 5 | "license": "MIT", 6 | "require": { 7 | "php": "^7.1.3", 8 | "ext-SimpleXML": "*", 9 | "ext-dom": "*", 10 | "ext-json": "*", 11 | "ext-openssl": "*", 12 | "doctrine/collections": "^1.5", 13 | "guzzlehttp/guzzle": "^6.3", 14 | "monolog/monolog": "^1.23", 15 | "symfony/cache": "^4.1", 16 | "symfony/dependency-injection": "^4.1", 17 | "symfony/http-foundation": "^4.1" 18 | }, 19 | "require-dev": { 20 | "mikey179/vfsStream": "^1.6", 21 | "mockery/mockery": "^1.1", 22 | "phpunit/phpunit": "^6" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "WeWork\\": "src/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "WeWork\\Tests\\": "tests/" 32 | } 33 | }, 34 | "config": { 35 | "sort-packages": true 36 | }, 37 | "extra": { 38 | "laravel": { 39 | "providers": [ 40 | "WeWork\\Laravel\\WeWorkServiceProvider" 41 | ] 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Api/Department.php: -------------------------------------------------------------------------------- 1 | httpClient->postJson('department/create', $json); 20 | } 21 | 22 | /** 23 | * 更新部门 24 | * 25 | * @param array $json 26 | * @return array 27 | */ 28 | public function update(array $json): array 29 | { 30 | return $this->httpClient->postJson('department/update', $json); 31 | } 32 | 33 | /** 34 | * 删除部门 35 | * 36 | * @param int $id 37 | * @return array 38 | */ 39 | public function delete(int $id): array 40 | { 41 | return $this->httpClient->get('department/delete', compact('id')); 42 | } 43 | 44 | /** 45 | * 获取部门列表 46 | * 47 | * @param int $id 48 | * @return array 49 | */ 50 | public function list(int $id = 0): array 51 | { 52 | return $this->httpClient->get('department/list', $id > 0 ? compact('id') : []); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Api/CheckIn.php: -------------------------------------------------------------------------------- 1 | httpClient->postJson('checkin/getcheckinoption', ['datetime' => $datetime, 'useridlist' => $userIdList]); 25 | } 26 | 27 | /** 28 | * 获取打卡数据 29 | * 30 | * @param int $type 31 | * @param int $startTime 32 | * @param int $endTime 33 | * @param array $userIdList 34 | * @return array 35 | */ 36 | public function getData(int $type, int $startTime, int $endTime, array $userIdList): array 37 | { 38 | return $this->httpClient->postJson('checkin/getcheckindata', [ 39 | 'opencheckindatatype' => $type, 40 | 'starttime' => $startTime, 41 | 'endtime' => $endTime, 42 | 'useridlist' => $userIdList 43 | ]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Message/News.php: -------------------------------------------------------------------------------- 1 | articles = $articles; 18 | } 19 | 20 | /** 21 | * @return array 22 | */ 23 | public function formatForReply(): array 24 | { 25 | return [ 26 | 'MsgType' => 'news', 27 | 'ArticleCount' => count($this->articles), 28 | 'Articles' => array_map(function (Article $article) { 29 | return $article->formatForReply(); 30 | }, $this->articles) 31 | ]; 32 | } 33 | 34 | /** 35 | * @return array 36 | */ 37 | public function formatForResponse(): array 38 | { 39 | return [ 40 | 'msgtype' => 'news', 41 | 'news' => [ 42 | 'articles' => array_map(function (Article $article) { 43 | return $article->formatForResponse(); 44 | }, $this->articles) 45 | ] 46 | ]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Laravel/config.php: -------------------------------------------------------------------------------- 1 | 'xxxxxxxxxxxxxxxxx', 14 | 15 | /* 16 | |-------------------------------------------------------------------------- 17 | | 默认应用 18 | |-------------------------------------------------------------------------- 19 | | 20 | | 定义默认使用的应用。 21 | | 22 | */ 23 | 24 | 'default' => 'contacts', 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | 应用列表 29 | |-------------------------------------------------------------------------- 30 | | 31 | | 定义应用列表,如果某个应用的某些配置用不到或者不存在,可以不定义。 32 | | 33 | */ 34 | 35 | 'agents' => [ 36 | 'contacts' => [ 37 | 'agent_id' => 0, 38 | 'secret' => 'xxxxxxxxxxxxxxxxx', 39 | 'token' => 'xxxxxxxxxxxxxxxxx', 40 | 'aes_key' => 'xxxxxxxxxxxxxxxxx', 41 | ], 42 | 43 | //... 44 | ], 45 | ]; 46 | -------------------------------------------------------------------------------- /src/Message/Receiver.php: -------------------------------------------------------------------------------- 1 | set('touser', $user); 19 | } 20 | 21 | /** 22 | * @param string|array $party 23 | * @return void 24 | */ 25 | public function setParty($party): void 26 | { 27 | $this->set('toparty', $party); 28 | } 29 | 30 | /** 31 | * @param string|array $tag 32 | * @return void 33 | */ 34 | public function setTag($tag): void 35 | { 36 | $this->set('totag', $tag); 37 | } 38 | 39 | /** 40 | * @return array 41 | */ 42 | public function get(): array 43 | { 44 | return $this->receiver; 45 | } 46 | 47 | /** 48 | * @param string $key 49 | * @param mixed $value 50 | * @return void 51 | */ 52 | private function set(string $key, $value): void 53 | { 54 | if (is_array($value)) { 55 | $value = implode('|', $value); 56 | } 57 | 58 | $this->receiver[$key] = $value; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Message/ArticleTest.php: -------------------------------------------------------------------------------- 1 | article = new Article('title', 'url', 'description', 'picurl', 'btntxt'); 23 | } 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function testFormatForReply() 29 | { 30 | $this->assertEquals([ 31 | 'Title' => 'title', 32 | 'Description' => 'description', 33 | 'PicUrl' => 'picurl', 34 | 'Url' => 'url' 35 | ], $this->article->formatForReply()); 36 | } 37 | 38 | /** 39 | * @return void 40 | */ 41 | public function testFormatForResponse() 42 | { 43 | $this->assertEquals([ 44 | 'title' => 'title', 45 | 'description' => 'description', 46 | 'url' => 'url', 47 | 'picurl' => 'picurl', 48 | 'btntxt' => 'btntxt' 49 | ], $this->article->formatForResponse()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Crypt/PKCS7Encoder.php: -------------------------------------------------------------------------------- 1 | PKCS7Encoder::$block_size) { 45 | $pad = 0; 46 | } 47 | return substr($text, 0, (strlen($text) - $pad)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Message/MPNewsTest.php: -------------------------------------------------------------------------------- 1 | news = new MPNews([new MPArticle('title', 'thumb_media_id', 'content', 'author', 'content_source_url', 'digest')]); 24 | } 25 | 26 | /** 27 | * @return void 28 | */ 29 | public function testFormatForResponse() 30 | { 31 | $this->assertEquals([ 32 | 'msgtype' => 'mpnews', 33 | 'mpnews' => [ 34 | 'articles' => [ 35 | [ 36 | 'title' => 'title', 37 | 'thumb_media_id' => 'thumb_media_id', 38 | 'author' => 'author', 39 | 'content_source_url' => 'content_source_url', 40 | 'content' => 'content', 41 | 'digest' => 'digest' 42 | ] 43 | ] 44 | ] 45 | ], $this->news->formatForResponse()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Message/TextCard.php: -------------------------------------------------------------------------------- 1 | title = $title; 36 | $this->description = $description; 37 | $this->url = $url; 38 | $this->btnTxt = $btnTxt; 39 | } 40 | 41 | /** 42 | * @return array 43 | */ 44 | public function formatForResponse(): array 45 | { 46 | return [ 47 | 'msgtype' => 'textcard', 48 | 'textcard' => [ 49 | 'title' => $this->title, 50 | 'description' => $this->description, 51 | 'url' => $this->url, 52 | 'btntxt' => $this->btnTxt 53 | ] 54 | ]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Message/VideoTest.php: -------------------------------------------------------------------------------- 1 | video = new Video('media_id', 'title', 'description'); 23 | } 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function testFormatForReply() 29 | { 30 | $this->assertEquals([ 31 | 'MsgType' => 'video', 32 | 'Video' => [ 33 | 'MediaId' => 'media_id', 34 | 'Title' => 'title', 35 | 'Description' => 'description' 36 | ] 37 | ], $this->video->formatForReply()); 38 | } 39 | 40 | /** 41 | * @return void 42 | */ 43 | public function testFormatForResponse() 44 | { 45 | $this->assertEquals([ 46 | 'msgtype' => 'video', 47 | 'video' => [ 48 | 'media_id' => 'media_id', 49 | 'title' => 'title', 50 | 'description' => 'description' 51 | ] 52 | ], $this->video->formatForResponse()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/ApiCache/TicketTest.php: -------------------------------------------------------------------------------- 1 | ticket = new Ticket(); 24 | } 25 | 26 | /** 27 | * @return void 28 | * @throws \ReflectionException 29 | */ 30 | public function testGetCacheKey() 31 | { 32 | $this->assertEquals('eca7e9ee3d2dc60c8d25d665119b7a3e', $this->callMethod($this->ticket, 'getCacheKey')); 33 | } 34 | 35 | /** 36 | * @return void 37 | * @throws \ReflectionException 38 | */ 39 | public function testGetFromServer() 40 | { 41 | $httpClient = \Mockery::mock(HttpClientInterface::class); 42 | 43 | $httpClient->shouldReceive('get') 44 | ->once() 45 | ->with('ticket/get', ['type' => 'wx_card']) 46 | ->andReturn(['ticket' => 'foo']); 47 | 48 | $this->ticket->setHttpClient($httpClient); 49 | 50 | $this->assertEquals('foo', $this->callMethod($this->ticket, 'getFromServer')); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Api/Media.php: -------------------------------------------------------------------------------- 1 | httpClient->postFile('media/upload', $path, compact('type')); 22 | } 23 | 24 | /** 25 | * 获取临时素材 26 | * 27 | * @param string $id 28 | * @return StreamInterface 29 | */ 30 | public function get(string $id): StreamInterface 31 | { 32 | return $this->httpClient->getStream('media/get', ['media_id' => $id]); 33 | } 34 | 35 | /** 36 | * 获取高清语音素材 37 | * 38 | * @param string $id 39 | * @return StreamInterface 40 | */ 41 | public function getVoice(string $id): StreamInterface 42 | { 43 | return $this->httpClient->getStream('media/get/jssdk', ['media_id' => $id]); 44 | } 45 | 46 | /** 47 | * 上传图片 48 | * 49 | * @param string $path 50 | * @return array 51 | */ 52 | public function uploadImg(string $path): array 53 | { 54 | return $this->httpClient->postFile('media/uploadimg', $path); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Crypt/XMLParse.php: -------------------------------------------------------------------------------- 1 | loadXML($xmltext); 23 | $array_e = $xml->getElementsByTagName('Encrypt'); 24 | $encrypt = $array_e->item(0)->nodeValue; 25 | return array(0, $encrypt); 26 | } catch (\Exception $e) { 27 | return array(ErrorCode::$ParseXmlError, null, null); 28 | } 29 | } 30 | 31 | /** 32 | * 生成xml消息 33 | * 34 | * @param string $encrypt 加密后的消息密文 35 | * @param string $signature 安全签名 36 | * @param string $timestamp 时间戳 37 | * @param string $nonce 随机字符串 38 | * @return string 39 | */ 40 | public function generate($encrypt, $signature, $timestamp, $nonce) 41 | { 42 | $format = " 43 | 44 | 45 | %s 46 | 47 | "; 48 | return sprintf($format, $encrypt, $signature, $timestamp, $nonce); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/ApiCache/JsTicketTest.php: -------------------------------------------------------------------------------- 1 | jsTicket = new JsApiTicket(); 24 | } 25 | 26 | /** 27 | * @return void 28 | * @throws \ReflectionException 29 | */ 30 | public function testGetCacheKey() 31 | { 32 | $this->jsTicket->setSecret('foo'); 33 | 34 | $this->assertEquals('4ce02e279b2db945760a47d20069a2f2', $this->callMethod($this->jsTicket, 'getCacheKey')); 35 | } 36 | 37 | /** 38 | * @return void 39 | * @throws \ReflectionException 40 | */ 41 | public function testGetFromServer() 42 | { 43 | $httpClient = \Mockery::mock(HttpClientInterface::class); 44 | 45 | $httpClient->shouldReceive('get') 46 | ->once() 47 | ->with('get_jsapi_ticket') 48 | ->andReturn(['ticket' => 'foo']); 49 | 50 | $this->jsTicket->setHttpClient($httpClient); 51 | 52 | $this->assertEquals('foo', $this->callMethod($this->jsTicket, 'getFromServer')); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/user.php: -------------------------------------------------------------------------------- 1 | get('user'); 7 | 8 | try { 9 | $user->create([ 10 | 'userid' => 'zhangsan', 11 | 'name' => '张三', 12 | 'department' => [1], 13 | 'email' => 'zhangsan@gzdev.com' 14 | ]); 15 | } catch (Exception $e) { 16 | } 17 | 18 | try { 19 | $user->get('zhangsan'); 20 | } catch (Exception $e) { 21 | } 22 | 23 | try { 24 | $user->update([ 25 | 'userid' => 'zhangsan', 26 | 'name' => '张三三' 27 | ]); 28 | } catch (Exception $e) { 29 | } 30 | 31 | try { 32 | $user->delete('zhangsan'); 33 | } catch (Exception $e) { 34 | } 35 | 36 | try { 37 | $user->batchDelete(['zhangsan', 'lisi']); 38 | } catch (Exception $e) { 39 | } 40 | 41 | try { 42 | $user->list(1); 43 | } catch (Exception $e) { 44 | } 45 | 46 | try { 47 | $user->list(1, true, true); 48 | } catch (Exception $e) { 49 | } 50 | 51 | try { 52 | $user->convertIdToOpenid('zhangsan'); 53 | } catch (Exception $e) { 54 | } 55 | 56 | try { 57 | $user->convertOpenidToUserId('oDOGms-6yCnGrRovBj2yHij5JL6E'); 58 | } catch (Exception $e) { 59 | } 60 | 61 | try { 62 | $user->authSuccess('zhangsan'); 63 | } catch (Exception $e) { 64 | } 65 | 66 | try { 67 | $user->getInfo('CODE'); 68 | } catch (Exception $e) { 69 | } 70 | 71 | try { 72 | $user->getDetail('USER_TICKET'); 73 | } catch (Exception $e) { 74 | } 75 | -------------------------------------------------------------------------------- /tests/ApiCache/TokenTest.php: -------------------------------------------------------------------------------- 1 | token = new Token(); 24 | 25 | $this->token->setSecret('foo'); 26 | } 27 | 28 | /** 29 | * @return void 30 | * @throws \ReflectionException 31 | */ 32 | public function testGetCacheKey() 33 | { 34 | $this->assertEquals('75e021ba614c0848bf2fbac06afec8ca', $this->callMethod($this->token, 'getCacheKey')); 35 | } 36 | 37 | /** 38 | * @return void 39 | * @throws \ReflectionException 40 | */ 41 | public function testGetFromServer() 42 | { 43 | $this->token->setCorpId('bar'); 44 | 45 | $httpClient = \Mockery::mock(HttpClientInterface::class); 46 | 47 | $httpClient->shouldReceive('get') 48 | ->once() 49 | ->with('gettoken', ['corpid' => 'bar', 'corpsecret' => 'foo']) 50 | ->andReturn(['access_token' => 'baz']); 51 | 52 | $this->token->setHttpClient($httpClient); 53 | 54 | $this->assertEquals('baz', $this->callMethod($this->token, 'getFromServer')); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Api/Batch.php: -------------------------------------------------------------------------------- 1 | httpClient->postJson('batch/invite', $json); 20 | } 21 | 22 | /** 23 | * 增量更新成员 24 | * 25 | * @param array $json 26 | * @return array 27 | */ 28 | public function syncUser(array $json): array 29 | { 30 | return $this->httpClient->postJson('batch/syncuser', $json); 31 | } 32 | 33 | /** 34 | * 全量覆盖成员 35 | * 36 | * @param array $json 37 | * @return array 38 | */ 39 | public function replaceUser(array $json): array 40 | { 41 | return $this->httpClient->postJson('batch/replaceuser', $json); 42 | } 43 | 44 | /** 45 | * 全量覆盖部门 46 | * 47 | * @param array $json 48 | * @return array 49 | */ 50 | public function replaceParty(array $json): array 51 | { 52 | return $this->httpClient->postJson('batch/replaceparty', $json); 53 | } 54 | 55 | /** 56 | * 获取异步任务结果 57 | * 58 | * @param string $jobId 59 | * @return array 60 | */ 61 | public function getResult(string $jobId): array 62 | { 63 | return $this->httpClient->get('batch/getresult', ['jobid' => $jobId]); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Api/AppChat.php: -------------------------------------------------------------------------------- 1 | httpClient->postJson('appchat/create', $json); 21 | } 22 | 23 | /** 24 | * 修改群聊会话 25 | * 26 | * @param array $json 27 | * @return array 28 | */ 29 | public function update(array $json): array 30 | { 31 | return $this->httpClient->postJson('appchat/update', $json); 32 | } 33 | 34 | /** 35 | * 获取群聊会话 36 | * 37 | * @param string $id 38 | * @return array 39 | */ 40 | public function get(string $id): array 41 | { 42 | return $this->httpClient->get('appchat/get', ['chatid' => $id]); 43 | } 44 | 45 | /** 46 | * 应用推送消息 47 | * 48 | * @param string $id 49 | * @param ResponseMessageInterface $responseMessage 50 | * @param bool $safe 51 | * @return array 52 | */ 53 | public function send(string $id, ResponseMessageInterface $responseMessage, bool $safe = false): array 54 | { 55 | return $this->httpClient->postJson( 56 | 'appchat/send', 57 | array_merge(['chatid' => $id], $responseMessage->formatForResponse(), ['safe' => (int)$safe]) 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Message/Video.php: -------------------------------------------------------------------------------- 1 | mediaId = $mediaId; 30 | $this->title = $title; 31 | $this->description = $description; 32 | } 33 | 34 | /** 35 | * @return array 36 | */ 37 | public function formatForReply(): array 38 | { 39 | return [ 40 | 'MsgType' => 'video', 41 | 'Video' => [ 42 | 'MediaId' => $this->mediaId, 43 | 'Title' => $this->title, 44 | 'Description' => $this->description 45 | ] 46 | ]; 47 | } 48 | 49 | /** 50 | * @return array 51 | */ 52 | public function formatForResponse(): array 53 | { 54 | return [ 55 | 'msgtype' => 'video', 56 | 'video' => [ 57 | 'media_id' => $this->mediaId, 58 | 'title' => $this->title, 59 | 'description' => $this->description 60 | ] 61 | ]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Laravel/WeWorkServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes( 25 | [ 26 | __DIR__.'/config.php' => config_path('wework.php'), 27 | ] 28 | ); 29 | } 30 | 31 | /** 32 | * Register services. 33 | * 34 | * @return void 35 | */ 36 | public function register() 37 | { 38 | $this->app->singleton( 39 | 'wework', 40 | function ($app, $parameters) { 41 | $wework = array_merge($app['config']['wework'], $parameters); 42 | 43 | $agent = $wework['agents'][$wework['default']]; 44 | 45 | $config = array_merge($agent, $wework); 46 | 47 | $config['log'] = LogBridge::class; 48 | 49 | $config['cache'] = CacheBridge::class; 50 | 51 | return new App($config); 52 | } 53 | ); 54 | } 55 | 56 | /** 57 | * Get the services provided by the provider. 58 | * 59 | * @return array 60 | */ 61 | public function provides() 62 | { 63 | return ['wework']; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Api/CorpTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 30 | 31 | $this->corp = new Corp(); 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | public function testGetApprovalData() 38 | { 39 | $this->httpClient->shouldReceive('postJson') 40 | ->once() 41 | ->with('corp/getapprovaldata', ['starttime' => 0, 'endtime' => 1]) 42 | ->andReturn(['errcode' => 0]); 43 | 44 | $this->corp->setHttpClient($this->httpClient); 45 | 46 | $this->assertEquals(['errcode' => 0], $this->corp->getApprovalData(0, 1)); 47 | } 48 | 49 | /** 50 | * @return void 51 | */ 52 | public function testGetApprovalDataWithNextSPNum() 53 | { 54 | $this->httpClient->shouldReceive('postJson') 55 | ->once() 56 | ->with('corp/getapprovaldata', ['starttime' => 0, 'endtime' => 1, 'next_spnum' => 2]) 57 | ->andReturn(['errcode' => 0]); 58 | 59 | $this->corp->setHttpClient($this->httpClient); 60 | 61 | $this->assertEquals(['errcode' => 0], $this->corp->getApprovalData(0, 1, 2)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Message/NewsTest.php: -------------------------------------------------------------------------------- 1 | news = new News([new Article('title', 'url', 'description', 'picurl', 'btntxt')]); 24 | } 25 | 26 | /** 27 | * @return void 28 | */ 29 | public function testFormatForReply() 30 | { 31 | $this->assertEquals([ 32 | 'MsgType' => 'news', 33 | 'ArticleCount' => 1, 34 | 'Articles' => [ 35 | [ 36 | 'Title' => 'title', 37 | 'Description' => 'description', 38 | 'PicUrl' => 'picurl', 39 | 'Url' => 'url' 40 | ] 41 | ] 42 | ], $this->news->formatForReply()); 43 | } 44 | 45 | /** 46 | * @return void 47 | */ 48 | public function testFormatForResponse() 49 | { 50 | $this->assertEquals([ 51 | 'msgtype' => 'news', 52 | 'news' => [ 53 | 'articles' => [ 54 | [ 55 | 'title' => 'title', 56 | 'description' => 'description', 57 | 'url' => 'url', 58 | 'picurl' => 'picurl', 59 | 'btntxt' => 'btntxt' 60 | ] 61 | ] 62 | ] 63 | ], $this->news->formatForResponse()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Message/MPArticle.php: -------------------------------------------------------------------------------- 1 | title = $title; 48 | $this->thumbMediaId = $thumbMediaId; 49 | $this->content = $content; 50 | $this->author = $author; 51 | $this->contentSourceUrl = $contentSourceUrl; 52 | $this->digest = $digest; 53 | } 54 | 55 | /** 56 | * @return array 57 | */ 58 | public function formatForResponse(): array 59 | { 60 | return [ 61 | 'title' => $this->title, 62 | 'thumb_media_id' => $this->thumbMediaId, 63 | 'author' => $this->author, 64 | 'content_source_url' => $this->contentSourceUrl, 65 | 'content' => $this->content, 66 | 'digest' => $this->digest 67 | ]; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/JSSdk.php: -------------------------------------------------------------------------------- 1 | corpId; 22 | 23 | $timestamp = $this->getTimestamp(); 24 | 25 | $nonceStr = $this->getNonceStr(); 26 | 27 | $signature = sha1("jsapi_ticket={$this->jsApiTicket->get()}&noncestr={$nonceStr}×tamp={$timestamp}&url={$url}"); 28 | 29 | return compact('appId', 'timestamp', 'nonceStr', 'signature'); 30 | } 31 | 32 | /** 33 | * @return array 34 | * @throws \Psr\SimpleCache\InvalidArgumentException 35 | */ 36 | public function getChooseInvoiceConfig(): array 37 | { 38 | $timestamp = $this->getTimestamp(); 39 | 40 | $nonceStr = $this->getNonceStr(); 41 | 42 | $array = ['INVOICE', $this->corpId, $timestamp, $nonceStr, $this->ticket->get()]; 43 | 44 | sort($array, SORT_STRING); 45 | 46 | $str = implode($array); 47 | 48 | $cardSign = sha1($str); 49 | 50 | return compact('timestamp', 'nonceStr', 'cardSign'); 51 | } 52 | 53 | /** 54 | * @return int 55 | */ 56 | protected function getTimestamp(): int 57 | { 58 | return time(); 59 | } 60 | 61 | /** 62 | * @return string 63 | */ 64 | protected function getNonceStr(): string 65 | { 66 | return (new PrpCrypt(null))->getRandomStr(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/Message/ReceiverTest.php: -------------------------------------------------------------------------------- 1 | receiver = new Receiver(); 23 | } 24 | 25 | /** 26 | * @param $user 27 | * @param $expected 28 | * @return void 29 | * 30 | * @dataProvider setProvider 31 | */ 32 | public function testSetUser($user, $expected) 33 | { 34 | $this->receiver->setUser($user); 35 | 36 | $this->assertEquals(['touser' => $expected], $this->receiver->get()); 37 | } 38 | 39 | /** 40 | * @param $party 41 | * @param $expected 42 | * @return void 43 | * 44 | * @dataProvider setProvider 45 | */ 46 | public function testSetParty($party, $expected) 47 | { 48 | $this->receiver->setParty($party); 49 | 50 | $this->assertEquals(['toparty' => $expected], $this->receiver->get()); 51 | } 52 | 53 | /** 54 | * @param $tag 55 | * @param $expected 56 | * @return void 57 | * 58 | * @dataProvider setProvider 59 | */ 60 | public function testSetTag($tag, $expected) 61 | { 62 | $this->receiver->setTag($tag); 63 | 64 | $this->assertEquals(['totag' => $expected], $this->receiver->get()); 65 | } 66 | 67 | /** 68 | * @return array 69 | */ 70 | public function setProvider() 71 | { 72 | return [ 73 | ['foo', 'foo'], 74 | [['foo', 'bar'], 'foo|bar'] 75 | ]; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Message/Article.php: -------------------------------------------------------------------------------- 1 | title = $title; 42 | $this->url = $url; 43 | $this->description = $description; 44 | $this->picUrl = $picUrl; 45 | $this->btnTxt = $btnTxt; 46 | } 47 | 48 | /** 49 | * @return array 50 | */ 51 | public function formatForReply(): array 52 | { 53 | return [ 54 | 'Title' => $this->title, 55 | 'Description' => $this->description, 56 | 'PicUrl' => $this->picUrl, 57 | 'Url' => $this->url 58 | ]; 59 | } 60 | 61 | /** 62 | * @return array 63 | */ 64 | public function formatForResponse(): array 65 | { 66 | return [ 67 | 'title' => $this->title, 68 | 'description' => $this->description, 69 | 'url' => $this->url, 70 | 'picurl' => $this->picUrl, 71 | 'btntxt' => $this->btnTxt 72 | ]; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Api/Invoice.php: -------------------------------------------------------------------------------- 1 | httpClient->postJson('card/invoice/reimburse/getinvoiceinfo', ['card_id' => $id, 'encrypt_code' => $code]); 21 | } 22 | 23 | /** 24 | * 更新发票状态 25 | * 26 | * @param string $id 27 | * @param string $code 28 | * @param string $status 29 | * @return array 30 | */ 31 | public function updateStatus(string $id, string $code, string $status): array 32 | { 33 | return $this->httpClient->postJson('card/invoice/reimburse/updateinvoicestatus', ['card_id' => $id, 'encrypt_code' => $code, 'reimburse_status' => $status]); 34 | } 35 | 36 | /** 37 | * 批量更新发票状态 38 | * 39 | * @param string $openid 40 | * @param string $status 41 | * @param array $list 42 | * @return array 43 | */ 44 | public function updateStatusBatch(string $openid, string $status, array $list): array 45 | { 46 | return $this->httpClient->postJson('card/invoice/reimburse/updatestatusbatch', [ 47 | 'openid' => $openid, 48 | 'reimburse_status' => $status, 49 | 'invoice_list' => $list 50 | ]); 51 | } 52 | 53 | /** 54 | * 批量查询电子发票 55 | * 56 | * @param array $list 57 | * @return array 58 | */ 59 | public function getInfoBatch(array $list): array 60 | { 61 | return $this->httpClient->postJson('card/invoice/reimburse/getinvoiceinfobatch', ['item_list' => $list]); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Api/MessageTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 32 | 33 | $this->message = new Message(); 34 | 35 | $this->message->setAgentId('AGENTID'); 36 | } 37 | 38 | /** 39 | * @return void 40 | */ 41 | public function testSend() 42 | { 43 | $receiver = \Mockery::mock(Receiver::class); 44 | 45 | $receiver->shouldReceive('get') 46 | ->once() 47 | ->withNoArgs() 48 | ->andReturn(['foo' => 'bar']); 49 | 50 | $responseFormatter = \Mockery::mock(ResponseMessageInterface::class); 51 | 52 | $responseFormatter->shouldReceive('formatForResponse') 53 | ->once() 54 | ->withNoArgs() 55 | ->andReturn(['baz' => 'foo']); 56 | 57 | $this->httpClient->shouldReceive('postJson') 58 | ->once() 59 | ->with('message/send', ['agentid' => 'AGENTID', 'foo' => 'bar', 'baz' => 'foo', 'safe' => 0]) 60 | ->andReturn(['errcode' => 0]); 61 | 62 | $this->message->setHttpClient($this->httpClient); 63 | 64 | $this->assertEquals(['errcode' => 0], $this->message->send($receiver, $responseFormatter)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/AppTest.php: -------------------------------------------------------------------------------- 1 | app = new App(['corp_id' => 'corpid', 'secret' => 'secret']); 22 | } 23 | 24 | /** 25 | * @param $expected 26 | * @param $id 27 | * @throws \Exception 28 | * 29 | * @dataProvider getProvider 30 | */ 31 | public function testGet($expected, $id) 32 | { 33 | $this->assertInstanceOf($expected, $this->app->get($id)); 34 | } 35 | 36 | /** 37 | * @return array 38 | */ 39 | public function getProvider() 40 | { 41 | return [ 42 | [\WeWork\ApiCache\Token::class, 'token'], 43 | [\WeWork\Callback::class, 'callback'], 44 | [\WeWork\Api\Agent::class, 'agent'], 45 | [\WeWork\Api\AppChat::class, 'appChat'], 46 | [\WeWork\Api\Batch::class, 'batch'], 47 | [\WeWork\Api\CheckIn::class, 'checkIn'], 48 | [\WeWork\Api\Corp::class, 'corp'], 49 | [\WeWork\Api\CRM::class, 'crm'], 50 | [\WeWork\Api\Department::class, 'department'], 51 | [\WeWork\Api\Invoice::class, 'invoice'], 52 | [\WeWork\Api\Media::class, 'media'], 53 | [\WeWork\Api\Menu::class, 'menu'], 54 | [\WeWork\Api\Message::class, 'message'], 55 | [\WeWork\Api\Tag::class, 'tag'], 56 | [\WeWork\Api\User::class, 'user'], 57 | [\WeWork\ApiCache\JsApiTicket::class, 'jsApiTicket'], 58 | [\WeWork\ApiCache\Ticket::class, 'ticket'], 59 | [\WeWork\JSSdk::class, 'jssdk'], 60 | ]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /docs/message.md: -------------------------------------------------------------------------------- 1 | # 发送应用消息 2 | 3 | ```php 4 | $message = $app->get('message'); 5 | ``` 6 | 7 | 消息接收者 8 | 9 | ```php 10 | $receiver = new \WeWork\Message\Receiver(); 11 | $receiver->setUser('userid1'); 12 | $receiver->setParty([1024, 2048]); 13 | $receiver->setTag([1024, 2048]); 14 | ``` 15 | 16 | ## 文本消息 17 | 18 | ```php 19 | $text = new \WeWork\Message\Text("你的快递已到,请携带工卡前往邮件中心领取。\n出发前可查看邮件中心视频实况,聪明避开排队。"); 20 | $message->send($receiver, $text, true); 21 | ``` 22 | 23 | ## 图片消息 24 | 25 | ```php 26 | $image = new \WeWork\Message\Image('MEDIA_ID'); 27 | $message->send($receiver, $image); 28 | ``` 29 | 30 | ## 语音消息 31 | 32 | ```php 33 | $voice = new \WeWork\Message\Voice('MEDIA_ID'); 34 | $message->send($receiver, $voice); 35 | ``` 36 | 37 | ## 视频消息 38 | 39 | ```php 40 | $video = new \WeWork\Message\Video('MEDIA_ID', 'Title', 'Description'); 41 | $message->send($receiver, $video); 42 | ``` 43 | 44 | ## 文件消息 45 | 46 | ```php 47 | $file = new \WeWork\Message\File('MEDIA_ID'); 48 | $message->send($receiver, $file); 49 | ``` 50 | 51 | ## 文本卡片消息 52 | 53 | ```php 54 | $textCard = new \WeWork\Message\TextCard('领奖通知', "
    2016年9月26日
    恭喜你抽中iPhone 7一台,领奖码:xxxx
    请于2016年10月10日前联系行政同事领取
    ", 'URL', '更多'); 55 | $message->send($receiver, $textCard); 56 | ``` 57 | 58 | ## 图文消息 59 | 60 | ```php 61 | $news = new \WeWork\Message\News([ 62 | new \WeWork\Message\Article('中秋节礼品领取', 'URL', '今年中秋节公司有豪礼相送', 'http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png', '更多') 63 | ]); 64 | $message->send($receiver, $news); 65 | ``` 66 | 67 | ## 图文消息(mpnews) 68 | 69 | ```php 70 | $news = new \WeWork\Message\MPNews([ 71 | new \WeWork\Message\MPArticle('Title', 'MEDIA_ID', 'Content', 'Author', 'URL', 'Digest description') 72 | ]); 73 | $message->send($receiver, $news); 74 | ``` 75 | -------------------------------------------------------------------------------- /src/Http/HttpClient.php: -------------------------------------------------------------------------------- 1 | client = $client; 21 | } 22 | 23 | /** 24 | * @param string $uri 25 | * @param array $query 26 | * @return array 27 | */ 28 | public function get(string $uri, array $query = []): array 29 | { 30 | return $this->client->get($uri, compact('query'))->toArray(); 31 | } 32 | 33 | /** 34 | * @param string $uri 35 | * @param array $query 36 | * @return StreamInterface 37 | */ 38 | public function getStream(string $uri, array $query = []): StreamInterface 39 | { 40 | return $this->client->get($uri, compact('query'))->getBody(); 41 | } 42 | 43 | /** 44 | * @param string $uri 45 | * @param array $json 46 | * @param array $query 47 | * @return array 48 | */ 49 | public function postJson(string $uri, array $json = [], array $query = []): array 50 | { 51 | return $this->client->post($uri, compact('json', 'query'))->toArray(); 52 | } 53 | 54 | /** 55 | * @param string $uri 56 | * @param string $path 57 | * @param array $query 58 | * @return array 59 | */ 60 | public function postFile(string $uri, string $path, array $query = []): array 61 | { 62 | return $this->client->post($uri, array_merge([ 63 | 'multipart' => [ 64 | [ 65 | 'name' => 'media', 66 | 'contents' => fopen($path, 'r') 67 | ] 68 | ] 69 | ], compact('query')))->toArray(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/ApiCache/AbstractApiCacheTest.php: -------------------------------------------------------------------------------- 1 | mock = $this->getMockForAbstractClass(AbstractApiCache::class); 25 | 26 | $this->mock->expects($this->once()) 27 | ->method('getCacheKey') 28 | ->will($this->returnValue('foo')); 29 | } 30 | 31 | /** 32 | * @param bool $refresh 33 | * @param string $value 34 | * @param int $limit 35 | * @param string $expected 36 | * @return void 37 | * 38 | * @dataProvider getProvider 39 | */ 40 | public function testGet($refresh, $value, $limit, $expected) 41 | { 42 | $cache = \Mockery::mock(CacheInterface::class); 43 | 44 | $cache->shouldReceive('get') 45 | ->once() 46 | ->with('foo') 47 | ->andReturn($value); 48 | 49 | $cache->shouldReceive('set') 50 | ->times($limit) 51 | ->with('foo', 'bar', 7100) 52 | ->andReturnSelf(); 53 | 54 | $this->mock->setCache($cache); 55 | 56 | $this->mock->expects($this->exactly($limit)) 57 | ->method('getFromServer') 58 | ->will($this->returnValue('bar')); 59 | 60 | $this->assertEquals($expected, $this->mock->get($refresh)); 61 | } 62 | 63 | /** 64 | * @return array 65 | */ 66 | public function getProvider() 67 | { 68 | return [ 69 | [true, 'foo', 1, 'bar'], 70 | [true, '', 1, 'bar'], 71 | [false, 'foo', 0, 'foo'], 72 | [false, '', 1, 'bar'], 73 | ]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/message.php: -------------------------------------------------------------------------------- 1 | get('message'); 7 | 8 | $receiver = new \WeWork\Message\Receiver(); 9 | $receiver->setUser('userid1'); 10 | $receiver->setParty([1024, 2048]); 11 | $receiver->setTag([1024, 2048]); 12 | 13 | try { 14 | $text = new \WeWork\Message\Text("你的快递已到,请携带工卡前往邮件中心领取。\n出发前可查看邮件中心视频实况,聪明避开排队。"); 15 | $message->send($receiver, $text, true); 16 | } catch (Exception $e) { 17 | } 18 | 19 | try { 20 | $image = new \WeWork\Message\Image('MEDIA_ID'); 21 | $message->send($receiver, $image); 22 | } catch (Exception $e) { 23 | } 24 | 25 | try { 26 | $voice = new \WeWork\Message\Voice('MEDIA_ID'); 27 | $message->send($receiver, $voice); 28 | } catch (Exception $e) { 29 | } 30 | 31 | try { 32 | $video = new \WeWork\Message\Video('MEDIA_ID', 'Title', 'Description'); 33 | $message->send($receiver, $video); 34 | } catch (Exception $e) { 35 | } 36 | 37 | try { 38 | $file = new \WeWork\Message\File('MEDIA_ID'); 39 | $message->send($receiver, $file); 40 | } catch (Exception $e) { 41 | } 42 | 43 | try { 44 | $textCard = new \WeWork\Message\TextCard('领奖通知', "
    2016年9月26日
    恭喜你抽中iPhone 7一台,领奖码:xxxx
    请于2016年10月10日前联系行政同事领取
    ", 'URL', '更多'); 45 | $message->send($receiver, $textCard); 46 | } catch (Exception $e) { 47 | } 48 | 49 | try { 50 | $news = new \WeWork\Message\News([ 51 | new \WeWork\Message\Article('中秋节礼品领取', 'URL', '今年中秋节公司有豪礼相送', 'http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png', '更多') 52 | ]); 53 | $message->send($receiver, $news); 54 | } catch (Exception $e) { 55 | } 56 | 57 | try { 58 | $news = new \WeWork\Message\MPNews([ 59 | new \WeWork\Message\MPArticle('Title', 'MEDIA_ID', 'Content', 'Author', 'URL', 'Digest description') 60 | ]); 61 | $message->send($receiver, $news); 62 | } catch (Exception $e) { 63 | } 64 | -------------------------------------------------------------------------------- /tests/Api/AgentTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 30 | 31 | $this->agent = new Agent(); 32 | 33 | $this->agent->setAgentId('AGENTID'); 34 | } 35 | 36 | /** 37 | * @return void 38 | */ 39 | public function testGet() 40 | { 41 | $this->httpClient->shouldReceive('get') 42 | ->once() 43 | ->with('agent/get', ['agentid' => 'AGENTID']) 44 | ->andReturn(['errcode' => 0]); 45 | 46 | $this->agent->setHttpClient($this->httpClient); 47 | 48 | $this->assertEquals(['errcode' => 0], $this->agent->get()); 49 | } 50 | 51 | /** 52 | * @return void 53 | */ 54 | public function testSet() 55 | { 56 | $this->httpClient->shouldReceive('postJson') 57 | ->once() 58 | ->with('agent/set', ['agentid' => 'AGENTID', 'foo' => 'bar']) 59 | ->andReturn(['errcode' => 0]); 60 | 61 | $this->agent->setHttpClient($this->httpClient); 62 | 63 | $this->assertEquals(['errcode' => 0], $this->agent->set(['foo' => 'bar'])); 64 | } 65 | 66 | /** 67 | * @return void 68 | */ 69 | public function testList() 70 | { 71 | $this->httpClient->shouldReceive('get') 72 | ->once() 73 | ->with('agent/list') 74 | ->andReturn(['errcode' => 0]); 75 | 76 | $this->agent->setHttpClient($this->httpClient); 77 | 78 | $this->assertEquals(['errcode' => 0], $this->agent->list()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/Api/MenuTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 30 | 31 | $this->menu = new Menu(); 32 | 33 | $this->menu->setAgentId('AGENTID'); 34 | } 35 | 36 | /** 37 | * @return void 38 | */ 39 | public function testCreate() 40 | { 41 | $this->httpClient->shouldReceive('postJson') 42 | ->once() 43 | ->with('menu/create', ['bar' => 'baz'], ['agentid' => 'AGENTID']) 44 | ->andReturn(['errcode' => 0]); 45 | 46 | $this->menu->setHttpClient($this->httpClient); 47 | 48 | $this->assertEquals(['errcode' => 0], $this->menu->create(['bar' => 'baz'])); 49 | } 50 | 51 | /** 52 | * @return void 53 | */ 54 | public function testGet() 55 | { 56 | $this->httpClient->shouldReceive('get') 57 | ->once() 58 | ->with('menu/get', ['agentid' => 'AGENTID']) 59 | ->andReturn(['errcode' => 0]); 60 | 61 | $this->menu->setHttpClient($this->httpClient); 62 | 63 | $this->assertEquals(['errcode' => 0], $this->menu->get()); 64 | } 65 | 66 | /** 67 | * @return void 68 | */ 69 | public function testDelete() 70 | { 71 | $this->httpClient->shouldReceive('get') 72 | ->once() 73 | ->with('menu/delete', ['agentid' => 'AGENTID']) 74 | ->andReturn(['errcode' => 0]); 75 | 76 | $this->menu->setHttpClient($this->httpClient); 77 | 78 | $this->assertEquals(['errcode' => 0], $this->menu->delete()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Api/Tag.php: -------------------------------------------------------------------------------- 1 | $name]; 21 | 22 | if ($id > 0) { 23 | $json['tagid'] = $id; 24 | } 25 | 26 | return $this->httpClient->postJson('tag/create', $json); 27 | } 28 | 29 | /** 30 | * 更新标签名字 31 | * 32 | * @param int $id 33 | * @param string $name 34 | * @return array 35 | */ 36 | public function update(int $id, string $name): array 37 | { 38 | return $this->httpClient->postJson('tag/update', ['tagid' => $id, 'tagname' => $name]); 39 | } 40 | 41 | /** 42 | * 删除标签 43 | * 44 | * @param int $id 45 | * @return array 46 | */ 47 | public function delete(int $id): array 48 | { 49 | return $this->httpClient->get('tag/delete', ['tagid' => $id]); 50 | } 51 | 52 | /** 53 | * 获取标签成员 54 | * 55 | * @param int $id 56 | * @return array 57 | */ 58 | public function get(int $id): array 59 | { 60 | return $this->httpClient->get('tag/get', ['tagid' => $id]); 61 | } 62 | 63 | /** 64 | * 增加标签成员 65 | * 66 | * @param array $json 67 | * @return array 68 | */ 69 | public function addUsers(array $json): array 70 | { 71 | return $this->httpClient->postJson('tag/addtagusers', $json); 72 | } 73 | 74 | /** 75 | * 删除标签成员 76 | * 77 | * @param array $json 78 | * @return array 79 | */ 80 | public function delUsers(array $json): array 81 | { 82 | return $this->httpClient->postJson('tag/deltagusers', $json); 83 | } 84 | 85 | /** 86 | * 获取标签列表 87 | * 88 | * @return array 89 | */ 90 | public function list(): array 91 | { 92 | return $this->httpClient->get('tag/list'); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/chat.php: -------------------------------------------------------------------------------- 1 | get('appChat'); 7 | 8 | try { 9 | $chat->create([ 10 | 'name' => 'NAME', 11 | 'owner' => 'userid1', 12 | 'userlist' => ['userid1', 'userid2'], 13 | 'chatid' => 'CHATID' 14 | ]); 15 | } catch (Exception $e) { 16 | } 17 | 18 | try { 19 | $chat->update([ 20 | 'chatid' => 'CHATID', 21 | 'name' => 'NAME', 22 | 'owner' => 'userid2' 23 | ]); 24 | } catch (Exception $e) { 25 | } 26 | 27 | try { 28 | $chat->get('CHATID'); 29 | } catch (Exception $e) { 30 | } 31 | 32 | try { 33 | $text = new \WeWork\Message\Text("你的快递已到,请携带工卡前往邮件中心领取。\n出发前可查看邮件中心视频实况,聪明避开排队。"); 34 | $chat->send('CHATID', $text, true); 35 | } catch (Exception $e) { 36 | } 37 | 38 | try { 39 | $image = new \WeWork\Message\Image('MEDIA_ID'); 40 | $chat->send('CHATID', $image); 41 | } catch (Exception $e) { 42 | } 43 | 44 | try { 45 | $voice = new \WeWork\Message\Voice('MEDIA_ID'); 46 | $chat->send('CHATID', $voice); 47 | } catch (Exception $e) { 48 | } 49 | 50 | try { 51 | $video = new \WeWork\Message\Video('MEDIA_ID', 'Title', 'Description'); 52 | $chat->send('CHATID', $video); 53 | } catch (Exception $e) { 54 | } 55 | 56 | try { 57 | $file = new \WeWork\Message\File('MEDIA_ID'); 58 | $chat->send('CHATID', $file); 59 | } catch (Exception $e) { 60 | } 61 | 62 | try { 63 | $textCard = new \WeWork\Message\TextCard('领奖通知', "
    2016年9月26日
    恭喜你抽中iPhone 7一台,领奖码:xxxx
    请于2016年10月10日前联系行政同事领取
    ", 'URL', '更多'); 64 | $chat->send('CHATID', $textCard); 65 | } catch (Exception $e) { 66 | } 67 | 68 | try { 69 | $news = new \WeWork\Message\News([ 70 | new \WeWork\Message\Article('中秋节礼品领取', 'URL', '今年中秋节公司有豪礼相送', 'http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png', '更多') 71 | ]); 72 | $chat->send('CHATID', $news); 73 | } catch (Exception $e) { 74 | } 75 | 76 | try { 77 | $news = new \WeWork\Message\MPNews([ 78 | new \WeWork\Message\MPArticle('Title', 'MEDIA_ID', 'Content', 'Author', 'URL', 'Digest description') 79 | ]); 80 | $chat->send('CHATID', $news); 81 | } catch (Exception $e) { 82 | } 83 | -------------------------------------------------------------------------------- /tests/JSSdkTest.php: -------------------------------------------------------------------------------- 1 | jssdk = \Mockery::mock(JSSdk::class . '[getTimestamp,getNonceStr]') 24 | ->shouldAllowMockingProtectedMethods(); 25 | 26 | $this->jssdk->shouldReceive('getTimestamp') 27 | ->once() 28 | ->withNoArgs() 29 | ->andReturn(1234567890); 30 | 31 | $this->jssdk->shouldReceive('getNonceStr') 32 | ->once() 33 | ->withNoArgs() 34 | ->andReturn('noncestr'); 35 | } 36 | 37 | /** 38 | * @return void 39 | * @throws \Psr\SimpleCache\InvalidArgumentException 40 | */ 41 | public function testGetConfig() 42 | { 43 | $jsApiTicket = \Mockery::mock(JsApiTicket::class); 44 | 45 | $jsApiTicket->shouldReceive('get') 46 | ->once() 47 | ->withNoArgs() 48 | ->andReturn('jsapi_ticket'); 49 | 50 | $this->jssdk->setCorpId('corpid'); 51 | 52 | $this->jssdk->setJsApiTicket($jsApiTicket); 53 | 54 | $this->assertEquals([ 55 | 'appId' => 'corpid', 56 | 'timestamp' => 1234567890, 57 | 'nonceStr' => 'noncestr', 58 | 'signature' => 'ae51fe229cba1316ed477ecbb06a53631ceaef2f' 59 | ], $this->jssdk->getConfig('url')); 60 | } 61 | 62 | /** 63 | * @return void 64 | * @throws \Psr\SimpleCache\InvalidArgumentException 65 | */ 66 | public function testGetChooseInvoiceConfig() 67 | { 68 | $ticket = \Mockery::mock(Ticket::class); 69 | 70 | $ticket->shouldReceive('get') 71 | ->once() 72 | ->withNoArgs() 73 | ->andReturn('api_ticket'); 74 | 75 | $this->jssdk->setCorpId('corpid'); 76 | 77 | $this->jssdk->setTicket($ticket); 78 | 79 | $this->assertEquals([ 80 | 'timestamp' => 1234567890, 81 | 'nonceStr' => 'noncestr', 82 | 'cardSign' => 'c1140f85f3fd19bd2f2e5716684086dca782774b' 83 | ], $this->jssdk->getChooseInvoiceConfig('url')); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/Api/MediaTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 31 | 32 | $this->media = new Media(); 33 | } 34 | 35 | /** 36 | * @return void 37 | */ 38 | public function testUpload() 39 | { 40 | $this->httpClient->shouldReceive('postFile') 41 | ->once() 42 | ->with('media/upload', 'foo', ['type' => 'file']) 43 | ->andReturn(['errcode' => 0]); 44 | 45 | $this->media->setHttpClient($this->httpClient); 46 | 47 | $this->assertEquals(['errcode' => 0], $this->media->upload('file', 'foo')); 48 | } 49 | 50 | /** 51 | * @return void 52 | */ 53 | public function testGet() 54 | { 55 | $this->httpClient->shouldReceive('getStream') 56 | ->once() 57 | ->with('media/get', ['media_id' => 'foo']) 58 | ->andReturn(\Mockery::mock(StreamInterface::class)); 59 | 60 | $this->media->setHttpClient($this->httpClient); 61 | 62 | $this->assertInstanceOf(StreamInterface::class, $this->media->get('foo')); 63 | } 64 | 65 | /** 66 | * @return void 67 | */ 68 | public function testGetVoice() 69 | { 70 | $this->httpClient->shouldReceive('getStream') 71 | ->once() 72 | ->with('media/get/jssdk', ['media_id' => 'foo']) 73 | ->andReturn(\Mockery::mock(StreamInterface::class)); 74 | 75 | $this->media->setHttpClient($this->httpClient); 76 | 77 | $this->assertInstanceOf(StreamInterface::class, $this->media->getVoice('foo')); 78 | } 79 | 80 | /** 81 | * @return void 82 | */ 83 | public function testUploadImg() 84 | { 85 | $this->httpClient->shouldReceive('postFile') 86 | ->once() 87 | ->with('media/uploadimg', 'foo') 88 | ->andReturn(['errcode' => 0]); 89 | 90 | $this->media->setHttpClient($this->httpClient); 91 | 92 | $this->assertEquals(['errcode' => 0], $this->media->uploadImg('foo')); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tests/Api/InvoiceTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 30 | 31 | $this->invoice = new Invoice(); 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | public function testGetInfo() 38 | { 39 | $this->httpClient->shouldReceive('postJson') 40 | ->once() 41 | ->with('card/invoice/reimburse/getinvoiceinfo', ['card_id' => 'foo', 'encrypt_code' => 'bar']) 42 | ->andReturn(['errcode' => 0]); 43 | 44 | $this->invoice->setHttpClient($this->httpClient); 45 | 46 | $this->assertEquals(['errcode' => 0], $this->invoice->getInfo('foo', 'bar')); 47 | } 48 | 49 | /** 50 | * @return void 51 | */ 52 | public function testUpdateStatus() 53 | { 54 | $this->httpClient->shouldReceive('postJson') 55 | ->once() 56 | ->with('card/invoice/reimburse/updateinvoicestatus', ['card_id' => 'foo', 'encrypt_code' => 'bar', 'reimburse_status' => 'baz']) 57 | ->andReturn(['errcode' => 0]); 58 | 59 | $this->invoice->setHttpClient($this->httpClient); 60 | 61 | $this->assertEquals(['errcode' => 0], $this->invoice->updateStatus('foo', 'bar', 'baz')); 62 | } 63 | 64 | /** 65 | * @return void 66 | */ 67 | public function testUpdateStatusBatch() 68 | { 69 | $this->httpClient->shouldReceive('postJson') 70 | ->once() 71 | ->with('card/invoice/reimburse/updatestatusbatch', [ 72 | 'openid' => 'foo', 73 | 'reimburse_status' => 'bar', 74 | 'invoice_list' => ['baz'] 75 | ]) 76 | ->andReturn(['errcode' => 0]); 77 | 78 | $this->invoice->setHttpClient($this->httpClient); 79 | 80 | $this->assertEquals(['errcode' => 0], $this->invoice->updateStatusBatch('foo', 'bar', ['baz'])); 81 | } 82 | 83 | /** 84 | * @return void 85 | */ 86 | public function testGetInfoBatch() 87 | { 88 | $this->httpClient->shouldReceive('postJson') 89 | ->once() 90 | ->with('card/invoice/reimburse/getinvoiceinfobatch', ['item_list' => ['foo']]) 91 | ->andReturn(['errcode' => 0]); 92 | 93 | $this->invoice->setHttpClient($this->httpClient); 94 | 95 | $this->assertEquals(['errcode' => 0], $this->invoice->getInfoBatch(['foo'])); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/Api/AppChatTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 31 | 32 | $this->appChat = new AppChat(); 33 | } 34 | 35 | /** 36 | * @return void 37 | */ 38 | public function testCreate() 39 | { 40 | $this->httpClient->shouldReceive('postJson') 41 | ->once() 42 | ->with('appchat/create', ['foo' => 'bar']) 43 | ->andReturn(['errcode' => 0]); 44 | 45 | $this->appChat->setHttpClient($this->httpClient); 46 | 47 | $this->assertEquals(['errcode' => 0], $this->appChat->create(['foo' => 'bar'])); 48 | } 49 | 50 | /** 51 | * @return void 52 | */ 53 | public function testUpdate() 54 | { 55 | $this->httpClient->shouldReceive('postJson') 56 | ->once() 57 | ->with('appchat/update', ['foo' => 'bar']) 58 | ->andReturn(['errcode' => 0]); 59 | 60 | $this->appChat->setHttpClient($this->httpClient); 61 | 62 | $this->assertEquals(['errcode' => 0], $this->appChat->update(['foo' => 'bar'])); 63 | } 64 | 65 | /** 66 | * @return void 67 | */ 68 | public function testGet() 69 | { 70 | $this->httpClient->shouldReceive('get') 71 | ->once() 72 | ->with('appchat/get', ['chatid' => 'foo']) 73 | ->andReturn(['errcode' => 0]); 74 | 75 | $this->appChat->setHttpClient($this->httpClient); 76 | 77 | $this->assertEquals(['errcode' => 0], $this->appChat->get('foo')); 78 | } 79 | 80 | /** 81 | * @return void 82 | */ 83 | public function testSend() 84 | { 85 | $responseFormatter = \Mockery::mock(ResponseMessageInterface::class); 86 | 87 | $responseFormatter->shouldReceive('formatForResponse') 88 | ->once() 89 | ->withNoArgs() 90 | ->andReturn(['foo' => 'bar']); 91 | 92 | $this->httpClient->shouldReceive('postJson') 93 | ->once() 94 | ->with('appchat/send', ['chatid' => 'id', 'foo' => 'bar', 'safe' => 0]) 95 | ->andReturn(['errcode' => 0]); 96 | 97 | $this->appChat->setHttpClient($this->httpClient); 98 | 99 | $this->assertEquals(['errcode' => 0], $this->appChat->send('id', $responseFormatter)); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/Api/DepartmentTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 30 | 31 | $this->department = new Department(); 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | public function testCreate() 38 | { 39 | $this->httpClient->shouldReceive('postJson') 40 | ->once() 41 | ->with('department/create', ['foo' => 'bar']) 42 | ->andReturn(['errcode' => 0]); 43 | 44 | $this->department->setHttpClient($this->httpClient); 45 | 46 | $this->assertEquals(['errcode' => 0], $this->department->create(['foo' => 'bar'])); 47 | } 48 | 49 | /** 50 | * @return void 51 | */ 52 | public function testUpdate() 53 | { 54 | $this->httpClient->shouldReceive('postJson') 55 | ->once() 56 | ->with('department/update', ['foo' => 'bar']) 57 | ->andReturn(['errcode' => 0]); 58 | 59 | $this->department->setHttpClient($this->httpClient); 60 | 61 | $this->assertEquals(['errcode' => 0], $this->department->update(['foo' => 'bar'])); 62 | } 63 | 64 | /** 65 | * @return void 66 | */ 67 | public function testDelete() 68 | { 69 | $this->httpClient->shouldReceive('get') 70 | ->once() 71 | ->with('department/delete', ['id' => 1]) 72 | ->andReturn(['errcode' => 0]); 73 | 74 | $this->department->setHttpClient($this->httpClient); 75 | 76 | $this->assertEquals(['errcode' => 0], $this->department->delete(1)); 77 | } 78 | 79 | /** 80 | * @return void 81 | */ 82 | public function testAllList() 83 | { 84 | $this->httpClient->shouldReceive('get') 85 | ->once() 86 | ->with('department/list', []) 87 | ->andReturn(['errcode' => 0]); 88 | 89 | $this->department->setHttpClient($this->httpClient); 90 | 91 | $this->assertEquals(['errcode' => 0], $this->department->list()); 92 | } 93 | 94 | /** 95 | * @return void 96 | */ 97 | public function testList() 98 | { 99 | $this->httpClient->shouldReceive('get') 100 | ->once() 101 | ->with('department/list', ['id' => 1]) 102 | ->andReturn(['errcode' => 0]); 103 | 104 | $this->department->setHttpClient($this->httpClient); 105 | 106 | $this->assertEquals(['errcode' => 0], $this->department->list(1)); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/Api/BatchTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 30 | 31 | $this->batch = new Batch(); 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | public function testInvite() 38 | { 39 | $this->httpClient->shouldReceive('postJson') 40 | ->once() 41 | ->with('batch/invite', ['foo' => 'bar']) 42 | ->andReturn(['errcode' => 0]); 43 | 44 | $this->batch->setHttpClient($this->httpClient); 45 | 46 | $this->assertEquals(['errcode' => 0], $this->batch->invite(['foo' => 'bar'])); 47 | } 48 | 49 | /** 50 | * @return void 51 | */ 52 | public function testSyncUser() 53 | { 54 | $this->httpClient->shouldReceive('postJson') 55 | ->once() 56 | ->with('batch/syncuser', ['foo' => 'bar']) 57 | ->andReturn(['errcode' => 0]); 58 | 59 | $this->batch->setHttpClient($this->httpClient); 60 | 61 | $this->assertEquals(['errcode' => 0], $this->batch->syncUser(['foo' => 'bar'])); 62 | } 63 | 64 | /** 65 | * @return void 66 | */ 67 | public function testReplaceUser() 68 | { 69 | $this->httpClient->shouldReceive('postJson') 70 | ->once() 71 | ->with('batch/replaceuser', ['foo' => 'bar']) 72 | ->andReturn(['errcode' => 0]); 73 | 74 | $this->batch->setHttpClient($this->httpClient); 75 | 76 | $this->assertEquals(['errcode' => 0], $this->batch->replaceUser(['foo' => 'bar'])); 77 | } 78 | 79 | /** 80 | * @return void 81 | */ 82 | public function testReplaceParty() 83 | { 84 | $this->httpClient->shouldReceive('postJson') 85 | ->once() 86 | ->with('batch/replaceparty', ['foo' => 'bar']) 87 | ->andReturn(['errcode' => 0]); 88 | 89 | $this->batch->setHttpClient($this->httpClient); 90 | 91 | $this->assertEquals(['errcode' => 0], $this->batch->replaceParty(['foo' => 'bar'])); 92 | } 93 | 94 | /** 95 | * @return void 96 | */ 97 | public function testGetResult() 98 | { 99 | $this->httpClient->shouldReceive('get') 100 | ->once() 101 | ->with('batch/getresult', ['jobid' => 'foo']) 102 | ->andReturn(['errcode' => 0]); 103 | 104 | $this->batch->setHttpClient($this->httpClient); 105 | 106 | $this->assertEquals(['errcode' => 0], $this->batch->getResult('foo')); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Crypt/PrpCrypt.php: -------------------------------------------------------------------------------- 1 | key = base64_decode($k . '='); 26 | $this->iv = substr($this->key, 0, 16); 27 | } 28 | 29 | /** 30 | * 加密 31 | * 32 | * @param string $text 33 | * @param string $corpid 34 | * @return array 35 | */ 36 | public function encrypt($text, $corpid) 37 | { 38 | try { 39 | //拼接 40 | $text = $this->getRandomStr() . pack('N', strlen($text)) . $text . $corpid; 41 | //添加PKCS#7填充 42 | $pkc_encoder = new PKCS7Encoder; 43 | $text = $pkc_encoder->encode($text); 44 | //加密 45 | $encrypted = openssl_encrypt($text, 'AES-256-CBC', $this->key, OPENSSL_ZERO_PADDING, $this->iv); 46 | return [ErrorCode::$OK, $encrypted]; 47 | } catch (\Exception $e) { 48 | return [ErrorCode::$EncryptAESError, null]; 49 | } 50 | } 51 | 52 | /** 53 | * 解密 54 | * 55 | * @param $encrypted 56 | * @param $corpid 57 | * @return array 58 | */ 59 | public function decrypt($encrypted, $corpid) 60 | { 61 | try { 62 | //解密 63 | $decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $this->key, OPENSSL_ZERO_PADDING, $this->iv); 64 | } catch (\Exception $e) { 65 | return [ErrorCode::$DecryptAESError, null]; 66 | } 67 | try { 68 | //删除PKCS#7填充 69 | $pkc_encoder = new PKCS7Encoder; 70 | $result = $pkc_encoder->decode($decrypted); 71 | if (strlen($result) < 16) { 72 | return []; 73 | } 74 | //拆分 75 | $content = substr($result, 16, strlen($result)); 76 | $len_list = unpack('N', substr($content, 0, 4)); 77 | $xml_len = $len_list[1]; 78 | $xml_content = substr($content, 4, $xml_len); 79 | $from_corpid = substr($content, $xml_len + 4); 80 | } catch (\Exception $e) { 81 | return [ErrorCode::$IllegalBuffer, null]; 82 | } 83 | if ($from_corpid != $corpid) { 84 | return [ErrorCode::$ValidateCorpidError, null]; 85 | } 86 | return [0, $xml_content]; 87 | } 88 | 89 | /** 90 | * 生成随机字符串 91 | * 92 | * @return string 93 | */ 94 | public function getRandomStr() 95 | { 96 | $str = ''; 97 | $str_pol = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyl'; 98 | $max = strlen($str_pol) - 1; 99 | for ($i = 0; $i < 16; $i++) { 100 | $str .= $str_pol[mt_rand(0, $max)]; 101 | } 102 | return $str; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /tests/Api/CheckInTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 30 | 31 | $this->checkIn = new CheckIn(); 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | public function testGetOption() 38 | { 39 | $this->httpClient->shouldReceive('postJson') 40 | ->once() 41 | ->with('checkin/getcheckinoption', ['datetime' => 0, 'useridlist' => ['foo']]) 42 | ->andReturn(['errcode' => 0]); 43 | 44 | $this->checkIn->setHttpClient($this->httpClient); 45 | 46 | $this->assertEquals(['errcode' => 0], $this->checkIn->getOption(0, ['foo'])); 47 | } 48 | 49 | /** 50 | * @return void 51 | */ 52 | public function testGetDataWithCommuteType() 53 | { 54 | $this->httpClient->shouldReceive('postJson') 55 | ->once() 56 | ->with('checkin/getcheckindata', [ 57 | 'opencheckindatatype' => 1, 58 | 'starttime' => 0, 59 | 'endtime' => 1, 60 | 'useridlist' => ['foo'] 61 | ]) 62 | ->andReturn(['errcode' => 0]); 63 | 64 | $this->checkIn->setHttpClient($this->httpClient); 65 | 66 | $this->assertEquals(['errcode' => 0], $this->checkIn->getData(CheckIn::TYPE_COMMUTE, 0, 1, ['foo'])); 67 | } 68 | 69 | /** 70 | * @return void 71 | */ 72 | public function testGetDataWithOutsideType() 73 | { 74 | $this->httpClient->shouldReceive('postJson') 75 | ->once() 76 | ->with('checkin/getcheckindata', [ 77 | 'opencheckindatatype' => 2, 78 | 'starttime' => 0, 79 | 'endtime' => 1, 80 | 'useridlist' => ['foo'] 81 | ]) 82 | ->andReturn(['errcode' => 0]); 83 | 84 | $this->checkIn->setHttpClient($this->httpClient); 85 | 86 | $this->assertEquals(['errcode' => 0], $this->checkIn->getData(CheckIn::TYPE_OUTSIDE, 0, 1, ['foo'])); 87 | } 88 | 89 | /** 90 | * @return void 91 | */ 92 | public function testGetDataWithAllType() 93 | { 94 | $this->httpClient->shouldReceive('postJson') 95 | ->once() 96 | ->with('checkin/getcheckindata', [ 97 | 'opencheckindatatype' => 3, 98 | 'starttime' => 0, 99 | 'endtime' => 1, 100 | 'useridlist' => ['foo'] 101 | ]) 102 | ->andReturn(['errcode' => 0]); 103 | 104 | $this->checkIn->setHttpClient($this->httpClient); 105 | 106 | $this->assertEquals(['errcode' => 0], $this->checkIn->getData(CheckIn::TYPE_ALL, 0, 1, ['foo'])); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/CallbackTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(null, $callback->get('foo')); 22 | } 23 | 24 | /** 25 | * @return void 26 | */ 27 | public function testGet() 28 | { 29 | $request = Request::create('/', 'POST', [], [], [], [], 'content'); 30 | 31 | $crypt = \Mockery::mock(WXBizMsgCrypt::class); 32 | 33 | $crypt->shouldReceive('DecryptMsg') 34 | ->once() 35 | ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::on(function (&$argument) { 36 | $argument = ''; 37 | return true; 38 | })); 39 | 40 | $callback = new Callback($request, $crypt); 41 | 42 | $this->assertEquals(null, $callback->get('foo')); 43 | 44 | $this->assertEquals('bar', $callback->get('Foo')); 45 | } 46 | 47 | /** 48 | * @return void 49 | */ 50 | public function testVerifyURLReply() 51 | { 52 | $request = Request::create('/?echostr=foo'); 53 | 54 | $crypt = \Mockery::mock(WXBizMsgCrypt::class); 55 | 56 | $crypt->shouldReceive('VerifyURL') 57 | ->once() 58 | ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::on(function (&$argument) { 59 | $argument = 'text'; 60 | return true; 61 | })); 62 | 63 | $callback = new Callback($request, $crypt); 64 | 65 | $this->assertEquals('text', $callback->reply(\Mockery::mock(ReplyMessageInterface::class))); 66 | } 67 | 68 | /** 69 | * @return void 70 | */ 71 | public function testReply() 72 | { 73 | $request = Request::create('/'); 74 | 75 | $request->attributes->set('ToUserName', 'toUser'); 76 | $request->attributes->set('FromUserName', 'fromUser'); 77 | $request->attributes->set('CreateTime', 1348831860); 78 | 79 | $replyMessage = \Mockery::mock(ReplyMessageInterface::class); 80 | 81 | $replyMessage->shouldReceive('formatForReply') 82 | ->once() 83 | ->withNoArgs() 84 | ->andReturn(['Foo' => 'bar']); 85 | 86 | $crypt = \Mockery::mock(WXBizMsgCrypt::class); 87 | 88 | $crypt->shouldReceive('EncryptMsg') 89 | ->once() 90 | ->with('1348831860', \Mockery::any(), \Mockery::any(), \Mockery::on(function (&$argument) { 91 | $argument = 'xml'; 92 | return true; 93 | })); 94 | 95 | $callback = new Callback($request, $crypt); 96 | 97 | $this->assertEquals('xml', $callback->reply($replyMessage)); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Api/User.php: -------------------------------------------------------------------------------- 1 | httpClient->postJson('user/create', $json); 20 | } 21 | 22 | /** 23 | * 读取成员 24 | * 25 | * @param string $id 26 | * @return array 27 | */ 28 | public function get(string $id): array 29 | { 30 | return $this->httpClient->get('user/get', ['userid' => $id]); 31 | } 32 | 33 | /** 34 | * 更新成员 35 | * 36 | * @param array $json 37 | * @return array 38 | */ 39 | public function update(array $json): array 40 | { 41 | return $this->httpClient->postJson('user/update', $json); 42 | } 43 | 44 | /** 45 | * 删除成员 46 | * 47 | * @param string $id 48 | * @return array 49 | */ 50 | public function delete(string $id): array 51 | { 52 | return $this->httpClient->get('user/delete', ['userid' => $id]); 53 | } 54 | 55 | /** 56 | * 批量删除成员 57 | * 58 | * @param array $idList 59 | * @return array 60 | */ 61 | public function batchDelete(array $idList): array 62 | { 63 | return $this->httpClient->postJson('user/batchdelete', ['useridlist' => $idList]); 64 | } 65 | 66 | /** 67 | * 获取部门成员 68 | * 69 | * @param int $departmentId 70 | * @param bool $fetchChild 71 | * @param bool $needDetail 72 | * @return array 73 | */ 74 | public function list(int $departmentId, bool $fetchChild = false, bool $needDetail = false): array 75 | { 76 | $uri = 'user/' . ($needDetail ? 'list' : 'simplelist'); 77 | 78 | return $this->httpClient->get($uri, ['department_id' => $departmentId, 'fetch_child' => (int)$fetchChild]); 79 | } 80 | 81 | /** 82 | * userid转openid 83 | * 84 | * @param string $id 85 | * @return array 86 | */ 87 | public function convertIdToOpenid(string $id): array 88 | { 89 | return $this->httpClient->postJson('user/convert_to_openid', ['userid' => $id]); 90 | } 91 | 92 | /** 93 | * openid转userid 94 | * 95 | * @param string $openid 96 | * @return array 97 | */ 98 | public function convertOpenidToUserId(string $openid): array 99 | { 100 | return $this->httpClient->postJson('user/convert_to_userid', compact('openid')); 101 | } 102 | 103 | /** 104 | * 二次验证 105 | * 106 | * @param string $id 107 | * @return array 108 | */ 109 | public function authSuccess(string $id): array 110 | { 111 | return $this->httpClient->get('user/authsucc', ['userid' => $id]); 112 | } 113 | 114 | /** 115 | * 根据code获取成员信息 116 | * 117 | * @param string $code 118 | * @return array 119 | */ 120 | public function getInfo(string $code): array 121 | { 122 | return $this->httpClient->get('user/getuserinfo', compact('code')); 123 | } 124 | 125 | /** 126 | * 使用user_ticket获取成员详情 127 | * 128 | * @param string $ticket 129 | * @return array 130 | */ 131 | public function getDetail(string $ticket): array 132 | { 133 | return $this->httpClient->postJson('user/getuserdetail', ['user_ticket' => $ticket]); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Http/Middleware.php: -------------------------------------------------------------------------------- 1 | withUri(Uri::withQueryValue($request->getUri(), 'access_token', $token->get())); 28 | }); 29 | } 30 | 31 | /** 32 | * @param LoggerInterface $logger 33 | * @return callable 34 | */ 35 | public static function log(LoggerInterface $logger) 36 | { 37 | return \GuzzleHttp\Middleware::log($logger, new MessageFormatter(MessageFormatter::DEBUG), LogLevel::DEBUG); 38 | } 39 | 40 | /** 41 | * @param LoggerInterface $logger 42 | * @return callable 43 | */ 44 | public static function retry(LoggerInterface $logger) 45 | { 46 | return \GuzzleHttp\Middleware::retry(function ( 47 | $retries, 48 | Request $request, 49 | Response $response = null, 50 | RequestException $exception = null 51 | ) use ($logger) { 52 | if ($retries >= self::RETRY_MAX_RETRIES) { 53 | return false; 54 | } 55 | 56 | if (!(self::isServerError($response) || self::isConnectError($exception))) { 57 | return false; 58 | } 59 | 60 | $logger->warning( 61 | sprintf( 62 | 'Retrying %s %s %s/%s, %s', 63 | $request->getMethod(), 64 | $request->getUri(), 65 | $retries + 1, 66 | self::RETRY_MAX_RETRIES, 67 | $response ? 'status code: ' . $response->getStatusCode() : $exception->getMessage() 68 | ), 69 | [$request->getHeader('Host')[0]] 70 | ); 71 | 72 | return true; 73 | }); 74 | } 75 | 76 | /** 77 | * @return callable 78 | */ 79 | public static function response() 80 | { 81 | return \GuzzleHttp\Middleware::mapResponse(function (ResponseInterface $response) { 82 | return new Response( 83 | $response->getStatusCode(), 84 | $response->getHeaders(), 85 | $response->getBody(), 86 | $response->getProtocolVersion(), 87 | $response->getReasonPhrase() 88 | ); 89 | }); 90 | } 91 | 92 | /** 93 | * @param Response $response 94 | * 95 | * @return bool 96 | */ 97 | private static function isServerError(Response $response = null) 98 | { 99 | return $response && $response->getStatusCode() >= 500; 100 | } 101 | 102 | /** 103 | * @param RequestException $exception 104 | * 105 | * @return bool 106 | */ 107 | private static function isConnectError(RequestException $exception = null) 108 | { 109 | return $exception instanceof ConnectException; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/Http/HttpClientTest.php: -------------------------------------------------------------------------------- 1 | client = \Mockery::mock(Client::class); 27 | } 28 | 29 | /** 30 | * @return void 31 | */ 32 | public function testGet() 33 | { 34 | $response = \Mockery::mock(); 35 | 36 | $response->shouldReceive('toArray') 37 | ->once() 38 | ->withNoArgs() 39 | ->andReturn(['foo' => 'bar']); 40 | 41 | $this->client->shouldReceive('get') 42 | ->once() 43 | ->with('foo', ['query' => ['bar' => 'baz']]) 44 | ->andReturn($response); 45 | 46 | $httpClient = new HttpClient($this->client); 47 | 48 | $this->assertEquals(['foo' => 'bar'], $httpClient->get('foo', ['bar' => 'baz'])); 49 | } 50 | 51 | /** 52 | * @return void 53 | */ 54 | public function testGetStream() 55 | { 56 | $response = \Mockery::mock(); 57 | 58 | $response->shouldReceive('getBody') 59 | ->once() 60 | ->withNoArgs() 61 | ->andReturn(\Mockery::mock(StreamInterface::class)); 62 | 63 | $this->client->shouldReceive('get') 64 | ->once() 65 | ->with('foo', ['query' => ['bar' => 'baz']]) 66 | ->andReturn($response); 67 | 68 | $httpClient = new HttpClient($this->client); 69 | 70 | $this->assertInstanceOf(StreamInterface::class, $httpClient->getStream('foo', ['bar' => 'baz'])); 71 | } 72 | 73 | /** 74 | * @return void 75 | */ 76 | public function testPostJson() 77 | { 78 | $response = \Mockery::mock(); 79 | 80 | $response->shouldReceive('toArray') 81 | ->once() 82 | ->withNoArgs() 83 | ->andReturn(['foo' => 'bar']); 84 | 85 | $this->client->shouldReceive('post') 86 | ->once() 87 | ->with('foo', ['json' => ['bar'], 'query' => ['baz']]) 88 | ->andReturn($response); 89 | 90 | $httpClient = new HttpClient($this->client); 91 | 92 | $this->assertEquals(['foo' => 'bar'], $httpClient->postJson('foo', ['bar'], ['baz'])); 93 | } 94 | 95 | /** 96 | * @return void 97 | */ 98 | public function testPostFile() 99 | { 100 | $path = vfsStream::setup('root', null, ['foo.png' => 'bar'])->getChild('foo.png')->url(); 101 | 102 | $response = \Mockery::mock(); 103 | 104 | $response->shouldReceive('toArray') 105 | ->once() 106 | ->withNoArgs() 107 | ->andReturn(['foo' => 'bar']); 108 | 109 | $this->client->shouldReceive('post') 110 | ->once() 111 | ->with('foo', \Mockery::on(function ($argument) { 112 | return $argument['multipart'][0]['name'] === 'media' && is_resource($argument['multipart'][0]['contents']) && $argument['query'] === ['bar' => 'baz']; 113 | })) 114 | ->andReturn($response); 115 | 116 | $httpClient = new HttpClient($this->client); 117 | 118 | $this->assertEquals(['foo' => 'bar'], $httpClient->postFile('foo', $path, ['bar' => 'baz'])); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tests/Crypt/WXBizMsgCryptTest.php: -------------------------------------------------------------------------------- 1 | wxcpt = new WXBizMsgCrypt($token, $encodingAesKey, $corpId); 28 | } 29 | 30 | /** 31 | * @return void 32 | */ 33 | public function testVerifyURL() 34 | { 35 | $sVerifyMsgSig = "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3"; 36 | $sVerifyTimeStamp = "1409659589"; 37 | $sVerifyNonce = "263014780"; 38 | $sVerifyEchoStr = "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ=="; 39 | 40 | $sEchoStr = ''; 41 | 42 | $this->wxcpt->VerifyURL($sVerifyMsgSig, $sVerifyTimeStamp, $sVerifyNonce, $sVerifyEchoStr, $sEchoStr); 43 | 44 | $this->assertEquals('1616140317555161061', $sEchoStr); 45 | } 46 | 47 | /** 48 | * @return void 49 | */ 50 | public function testDecryptMsg() 51 | { 52 | $sReqMsgSig = "477715d11cdb4164915debcba66cb864d751f3e6"; 53 | $sReqTimeStamp = "1409659813"; 54 | $sReqNonce = "1372623149"; 55 | $sReqData = ""; 56 | 57 | $sMsg = ''; // 解析之后的明文 58 | 59 | $this->wxcpt->DecryptMsg($sReqMsgSig, $sReqTimeStamp, $sReqNonce, $sReqData, $sMsg); 60 | 61 | $this->assertEquals(" 62 | 63 | 1409659813 64 | 65 | 66 | 4561255354251345929 67 | 218 68 | ", $sMsg); 69 | } 70 | 71 | /** 72 | * @return void 73 | */ 74 | public function testEncryptMsg() 75 | { 76 | $sReqTimeStamp = "1409659813"; 77 | $sReqNonce = "1372623149"; 78 | 79 | // 需要发送的明文 80 | $sRespData = "13488318601234567890123456128"; 81 | 82 | $sEncryptMsg = ''; //xml格式的密文 83 | 84 | $this->wxcpt->EncryptMsg($sRespData, $sReqTimeStamp, $sReqNonce, $sEncryptMsg); 85 | 86 | $xml = new DOMDocument(); 87 | $xml->loadXML($sEncryptMsg); 88 | $sReqMsgSig = $xml->getElementsByTagName('MsgSignature')->item(0)->nodeValue; 89 | 90 | $sMsg = ''; // 解析之后的明文 91 | 92 | $this->wxcpt->DecryptMsg($sReqMsgSig, $sReqTimeStamp, $sReqNonce, $sEncryptMsg, $sMsg); 93 | 94 | $this->assertEquals($sRespData, $sMsg); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/Api/TagTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 30 | 31 | $this->tag = new Tag(); 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | public function testCreateWithNoId() 38 | { 39 | $this->httpClient->shouldReceive('postJson') 40 | ->once() 41 | ->with('tag/create', ['tagname' => 'foo']) 42 | ->andReturn(['errcode' => 0]); 43 | 44 | $this->tag->setHttpClient($this->httpClient); 45 | 46 | $this->assertEquals(['errcode' => 0], $this->tag->create('foo')); 47 | } 48 | 49 | /** 50 | * @param string $name 51 | * @param string $id 52 | * @param array $json 53 | * @return void 54 | * 55 | * @dataProvider createProvider 56 | */ 57 | public function testCreateWithId($name, $id, $json) 58 | { 59 | $this->httpClient->shouldReceive('postJson') 60 | ->once() 61 | ->with('tag/create', $json) 62 | ->andReturn(['errcode' => 0]); 63 | 64 | $this->tag->setHttpClient($this->httpClient); 65 | 66 | $this->assertEquals(['errcode' => 0], $this->tag->create($name, $id)); 67 | } 68 | 69 | /** 70 | * @return array 71 | */ 72 | public function createProvider() 73 | { 74 | return [ 75 | ['foo', 0, ['tagname' => 'foo']], 76 | ['bar', 1, ['tagname' => 'bar', 'tagid' => 1]] 77 | ]; 78 | } 79 | 80 | /** 81 | * @return void 82 | */ 83 | public function testUpdate() 84 | { 85 | $this->httpClient->shouldReceive('postJson') 86 | ->once() 87 | ->with('tag/update', ['tagid' => 1, 'tagname' => 'foo']) 88 | ->andReturn(['errcode' => 0]); 89 | 90 | $this->tag->setHttpClient($this->httpClient); 91 | 92 | $this->assertEquals(['errcode' => 0], $this->tag->update(1, 'foo')); 93 | } 94 | 95 | /** 96 | * @return void 97 | */ 98 | public function testDelete() 99 | { 100 | $this->httpClient->shouldReceive('get') 101 | ->once() 102 | ->with('tag/delete', ['tagid' => 1]) 103 | ->andReturn(['errcode' => 0]); 104 | 105 | $this->tag->setHttpClient($this->httpClient); 106 | 107 | $this->assertEquals(['errcode' => 0], $this->tag->delete(1)); 108 | } 109 | 110 | /** 111 | * @return void 112 | */ 113 | public function testGet() 114 | { 115 | $this->httpClient->shouldReceive('get') 116 | ->once() 117 | ->with('tag/get', ['tagid' => 1]) 118 | ->andReturn(['errcode' => 0]); 119 | 120 | $this->tag->setHttpClient($this->httpClient); 121 | 122 | $this->assertEquals(['errcode' => 0], $this->tag->get(1)); 123 | } 124 | 125 | /** 126 | * @return void 127 | */ 128 | public function testAddUsers() 129 | { 130 | $this->httpClient->shouldReceive('postJson') 131 | ->once() 132 | ->with('tag/addtagusers', ['foo' => 'bar']) 133 | ->andReturn(['errcode' => 0]); 134 | 135 | $this->tag->setHttpClient($this->httpClient); 136 | 137 | $this->assertEquals(['errcode' => 0], $this->tag->addUsers(['foo' => 'bar'])); 138 | } 139 | 140 | /** 141 | * @return void 142 | */ 143 | public function testDelUsers() 144 | { 145 | $this->httpClient->shouldReceive('postJson') 146 | ->once() 147 | ->with('tag/deltagusers', ['foo' => 'bar']) 148 | ->andReturn(['errcode' => 0]); 149 | 150 | $this->tag->setHttpClient($this->httpClient); 151 | 152 | $this->assertEquals(['errcode' => 0], $this->tag->delUsers(['foo' => 'bar'])); 153 | } 154 | 155 | /** 156 | * @return void 157 | */ 158 | public function testList() 159 | { 160 | $this->httpClient->shouldReceive('get') 161 | ->once() 162 | ->with('tag/list') 163 | ->andReturn(['errcode' => 0]); 164 | 165 | $this->tag->setHttpClient($this->httpClient); 166 | 167 | $this->assertEquals(['errcode' => 0], $this->tag->list()); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/Callback.php: -------------------------------------------------------------------------------- 1 | request = $request; 29 | $this->crypt = $crypt; 30 | } 31 | 32 | /** 33 | * @param string $key 34 | * @return mixed 35 | */ 36 | public function get(string $key) 37 | { 38 | if (!$this->request->getContent()) { 39 | return null; 40 | } 41 | 42 | if (!$this->request->attributes->has($key)) { 43 | $this->initializeAttribute(); 44 | } 45 | 46 | return $this->request->attributes->get($key); 47 | } 48 | 49 | /** 50 | * @return void 51 | */ 52 | private function initializeAttribute(): void 53 | { 54 | $data = ''; 55 | 56 | $this->crypt->DecryptMsg( 57 | $this->request->query->get('msg_signature'), 58 | $this->request->query->get('timestamp'), 59 | $this->request->query->get('nonce'), 60 | $this->request->getContent(), 61 | $data 62 | ); 63 | 64 | if ($data) { 65 | $xml = new SimpleXMLElement($data); 66 | 67 | foreach ($xml as $key => $value) { 68 | $this->request->attributes->set("$key", "$value"); 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * @return string 75 | */ 76 | private function decryptEchoStr(): string 77 | { 78 | $plainText = ''; 79 | 80 | $this->crypt->VerifyURL( 81 | $this->request->query->get('msg_signature'), 82 | $this->request->query->get('timestamp'), 83 | $this->request->query->get('nonce'), 84 | $this->request->query->get('echostr'), 85 | $plainText 86 | ); 87 | 88 | return $plainText; 89 | } 90 | 91 | /** 92 | * @param ReplyMessageInterface $replyMessage 93 | * @return string 94 | */ 95 | public function reply(ReplyMessageInterface $replyMessage): string 96 | { 97 | if ($this->request->query->has('echostr')) { 98 | return $this->decryptEchoStr(); 99 | } else { 100 | return $this->encryptReply($this->buildReply($replyMessage)); 101 | } 102 | } 103 | 104 | /** 105 | * @param string $reply 106 | * @return string 107 | */ 108 | private function encryptReply(string $reply): string 109 | { 110 | $cipherText = ''; 111 | 112 | $this->crypt->EncryptMsg( 113 | $reply, 114 | $this->request->query->get('timestamp'), 115 | $this->request->query->get('nonce'), 116 | $cipherText 117 | ); 118 | 119 | return $cipherText; 120 | } 121 | 122 | /** 123 | * @param ReplyMessageInterface $replyMessage 124 | * @return string 125 | */ 126 | private function buildReply(ReplyMessageInterface $replyMessage): string 127 | { 128 | $reply = $replyMessage->formatForReply(); 129 | 130 | $reply['ToUserName'] = $this->request->attributes->get('FromUserName'); 131 | $reply['FromUserName'] = $this->request->attributes->get('ToUserName'); 132 | $reply['CreateTime'] = (int)$this->request->attributes->get('CreateTime'); 133 | 134 | $element = new SimpleXMLElement(''); 135 | 136 | $this->arrayToXml($reply, $element); 137 | 138 | $dom = dom_import_simplexml($element); 139 | 140 | return $dom->ownerDocument->saveXML($dom->ownerDocument->documentElement); 141 | } 142 | 143 | /** 144 | * @param array $data 145 | * @param SimpleXMLElement $element 146 | * @return void 147 | */ 148 | private function arrayToXml(array $data, SimpleXMLElement &$element): void 149 | { 150 | foreach ($data as $key => $value) { 151 | if (is_numeric($key)) { 152 | $key = 'item'; 153 | } 154 | 155 | if (is_array($value)) { 156 | $subNode = $element->addChild($key); 157 | $this->arrayToXml($value, $subNode); 158 | } elseif (is_string($value)) { 159 | $node = dom_import_simplexml($element->addChild($key)); 160 | $no = $node->ownerDocument; 161 | $node->appendChild($no->createCDATASection($value)); 162 | } else { 163 | $element->addChild($key, $value); 164 | } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/Crypt/WXBizMsgCrypt.php: -------------------------------------------------------------------------------- 1 | m_sToken = $token; 35 | $this->m_sEncodingAesKey = $encodingAesKey; 36 | $this->m_sCorpid = $Corpid; 37 | } 38 | 39 | /** 40 | * 验证URL 41 | * 42 | * @param string $sMsgSignature 签名串,对应URL参数的msg_signature 43 | * @param string $sTimeStamp 时间戳,对应URL参数的timestamp 44 | * @param string $sNonce 随机串,对应URL参数的nonce 45 | * @param string $sEchoStr 随机串,对应URL参数的echostr 46 | * @param string $sReplyEchoStr 解密之后的echostr,当return返回0时有效 47 | * @return int 成功0,失败返回对应的错误码 48 | */ 49 | public function VerifyURL($sMsgSignature, $sTimeStamp, $sNonce, $sEchoStr, &$sReplyEchoStr) 50 | { 51 | if (strlen($this->m_sEncodingAesKey) != 43) { 52 | return ErrorCode::$IllegalAesKey; 53 | } 54 | 55 | $pc = new PrpCrypt($this->m_sEncodingAesKey); 56 | //verify msg_signature 57 | $sha1 = new SHA1; 58 | $array = $sha1->getSHA1($this->m_sToken, $sTimeStamp, $sNonce, $sEchoStr); 59 | $ret = $array[0]; 60 | 61 | if ($ret != 0) { 62 | return $ret; 63 | } 64 | 65 | $signature = $array[1]; 66 | if ($signature != $sMsgSignature) { 67 | return ErrorCode::$ValidateSignatureError; 68 | } 69 | 70 | $result = $pc->decrypt($sEchoStr, $this->m_sCorpid); 71 | if ($result[0] != 0) { 72 | return $result[0]; 73 | } 74 | $sReplyEchoStr = $result[1]; 75 | 76 | return ErrorCode::$OK; 77 | } 78 | 79 | /** 80 | * 将公众平台回复用户的消息加密打包. 81 | *
      82 | *
    1. 对要发送的消息进行AES-CBC加密
    2. 83 | *
    3. 生成安全签名
    4. 84 | *
    5. 将消息密文和安全签名打包成xml格式
    6. 85 | *
    86 | * 87 | * @param string $sReplyMsg 公众平台待回复用户的消息,xml格式的字符串 88 | * @param string $sTimeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp 89 | * @param string $sNonce 随机串,可以自己生成,也可以用URL参数的nonce 90 | * @param string &$sEncryptMsg 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串, 91 | * 当return返回0时有效 92 | * 93 | * @return int 成功0,失败返回对应的错误码 94 | */ 95 | public function EncryptMsg($sReplyMsg, $sTimeStamp, $sNonce, &$sEncryptMsg) 96 | { 97 | $pc = new PrpCrypt($this->m_sEncodingAesKey); 98 | 99 | //加密 100 | $array = $pc->encrypt($sReplyMsg, $this->m_sCorpid); 101 | $ret = $array[0]; 102 | if ($ret != 0) { 103 | return $ret; 104 | } 105 | 106 | if ($sTimeStamp == null) { 107 | $sTimeStamp = time(); 108 | } 109 | $encrypt = $array[1]; 110 | 111 | //生成安全签名 112 | $sha1 = new SHA1; 113 | $array = $sha1->getSHA1($this->m_sToken, $sTimeStamp, $sNonce, $encrypt); 114 | $ret = $array[0]; 115 | if ($ret != 0) { 116 | return $ret; 117 | } 118 | $signature = $array[1]; 119 | 120 | //生成发送的xml 121 | $xmlparse = new XMLParse; 122 | $sEncryptMsg = $xmlparse->generate($encrypt, $signature, $sTimeStamp, $sNonce); 123 | return ErrorCode::$OK; 124 | } 125 | 126 | 127 | /** 128 | * 检验消息的真实性,并且获取解密后的明文. 129 | *
      130 | *
    1. 利用收到的密文生成安全签名,进行签名验证
    2. 131 | *
    3. 若验证通过,则提取xml中的加密消息
    4. 132 | *
    5. 对消息进行解密
    6. 133 | *
    134 | * 135 | * @param string $sMsgSignature 签名串,对应URL参数的msg_signature 136 | * @param string $sTimeStamp 时间戳 对应URL参数的timestamp 137 | * @param string $sNonce 随机串,对应URL参数的nonce 138 | * @param string $sPostData 密文,对应POST请求的数据 139 | * @param string &$sMsg 解密后的原文,当return返回0时有效 140 | * 141 | * @return int 成功0,失败返回对应的错误码 142 | */ 143 | public function DecryptMsg($sMsgSignature, $sTimeStamp, $sNonce, $sPostData, &$sMsg) 144 | { 145 | if (strlen($this->m_sEncodingAesKey) != 43) { 146 | return ErrorCode::$IllegalAesKey; 147 | } 148 | 149 | $pc = new PrpCrypt($this->m_sEncodingAesKey); 150 | 151 | //提取密文 152 | $xmlparse = new XMLParse; 153 | $array = $xmlparse->extract($sPostData); 154 | $ret = $array[0]; 155 | 156 | if ($ret != 0) { 157 | return $ret; 158 | } 159 | 160 | if ($sTimeStamp == null) { 161 | $sTimeStamp = time(); 162 | } 163 | 164 | $encrypt = $array[1]; 165 | 166 | //验证安全签名 167 | $sha1 = new SHA1; 168 | $array = $sha1->getSHA1($this->m_sToken, $sTimeStamp, $sNonce, $encrypt); 169 | $ret = $array[0]; 170 | 171 | if ($ret != 0) { 172 | return $ret; 173 | } 174 | 175 | $signature = $array[1]; 176 | if ($signature != $sMsgSignature) { 177 | return ErrorCode::$ValidateSignatureError; 178 | } 179 | 180 | $result = $pc->decrypt($encrypt, $this->m_sCorpid); 181 | if ($result[0] != 0) { 182 | return $result[0]; 183 | } 184 | $sMsg = $result[1]; 185 | 186 | return ErrorCode::$OK; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /tests/Api/UserTest.php: -------------------------------------------------------------------------------- 1 | httpClient = \Mockery::mock(HttpClientInterface::class); 30 | 31 | $this->user = new User(); 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | public function testCreate() 38 | { 39 | $this->httpClient->shouldReceive('postJson') 40 | ->once() 41 | ->with('user/create', ['foo' => 'bar']) 42 | ->andReturn(['errcode' => 0]); 43 | 44 | $this->user->setHttpClient($this->httpClient); 45 | 46 | $this->assertEquals(['errcode' => 0], $this->user->create(['foo' => 'bar'])); 47 | } 48 | 49 | /** 50 | * @return void 51 | */ 52 | public function testGet() 53 | { 54 | $this->httpClient->shouldReceive('get') 55 | ->once() 56 | ->with('user/get', ['userid' => 'foo']) 57 | ->andReturn(['errcode' => 0]); 58 | 59 | $this->user->setHttpClient($this->httpClient); 60 | 61 | $this->assertEquals(['errcode' => 0], $this->user->get('foo')); 62 | } 63 | 64 | /** 65 | * @return void 66 | */ 67 | public function testUpdate() 68 | { 69 | $this->httpClient->shouldReceive('postJson') 70 | ->once() 71 | ->with('user/update', ['foo' => 'bar']) 72 | ->andReturn(['errcode' => 0]); 73 | 74 | $this->user->setHttpClient($this->httpClient); 75 | 76 | $this->assertEquals(['errcode' => 0], $this->user->update(['foo' => 'bar'])); 77 | } 78 | 79 | /** 80 | * @return void 81 | */ 82 | public function testDelete() 83 | { 84 | $this->httpClient->shouldReceive('get') 85 | ->once() 86 | ->with('user/delete', ['userid' => 'foo']) 87 | ->andReturn(['errcode' => 0]); 88 | 89 | $this->user->setHttpClient($this->httpClient); 90 | 91 | $this->assertEquals(['errcode' => 0], $this->user->delete('foo')); 92 | } 93 | 94 | /** 95 | * @return void 96 | */ 97 | public function testBatchDelete() 98 | { 99 | $this->httpClient->shouldReceive('postJson') 100 | ->once() 101 | ->with('user/batchdelete', ['useridlist' => ['foo', 'bar']]) 102 | ->andReturn(['errcode' => 0]); 103 | 104 | $this->user->setHttpClient($this->httpClient); 105 | 106 | $this->assertEquals(['errcode' => 0], $this->user->batchDelete(['foo', 'bar'])); 107 | } 108 | 109 | /** 110 | * @param int $departmentId 111 | * @param bool $fetchChild 112 | * @param bool $needDetail 113 | * @param string $uri 114 | * @param array $query 115 | * @return void 116 | * 117 | * @dataProvider listProvider 118 | */ 119 | public function testList($departmentId, $fetchChild, $needDetail, $uri, $query) 120 | { 121 | $this->httpClient->shouldReceive('get') 122 | ->once() 123 | ->with($uri, $query) 124 | ->andReturn(['errcode' => 0]); 125 | 126 | $this->user->setHttpClient($this->httpClient); 127 | 128 | $this->assertEquals(['errcode' => 0], $this->user->list($departmentId, $fetchChild, $needDetail)); 129 | } 130 | 131 | /** 132 | * @return array 133 | */ 134 | public function listProvider() 135 | { 136 | return [ 137 | [1, false, false, 'user/simplelist', ['department_id' => 1, 'fetch_child' => 0]], 138 | [2, false, true, 'user/list', ['department_id' => 2, 'fetch_child' => 0]], 139 | [3, true, false, 'user/simplelist', ['department_id' => 3, 'fetch_child' => 1]], 140 | [4, true, true, 'user/list', ['department_id' => 4, 'fetch_child' => 1]] 141 | ]; 142 | } 143 | 144 | /** 145 | * @return void 146 | */ 147 | public function testConvertIdToOpenid() 148 | { 149 | $this->httpClient->shouldReceive('postJson') 150 | ->once() 151 | ->with('user/convert_to_openid', ['userid' => 'foo']) 152 | ->andReturn(['errcode' => 0]); 153 | 154 | $this->user->setHttpClient($this->httpClient); 155 | 156 | $this->assertEquals(['errcode' => 0], $this->user->convertIdToOpenid('foo')); 157 | } 158 | 159 | /** 160 | * @return void 161 | */ 162 | public function testConvertOpenidToUserId() 163 | { 164 | $this->httpClient->shouldReceive('postJson') 165 | ->once() 166 | ->with('user/convert_to_userid', ['openid' => 'foo']) 167 | ->andReturn(['errcode' => 0]); 168 | 169 | $this->user->setHttpClient($this->httpClient); 170 | 171 | $this->assertEquals(['errcode' => 0], $this->user->convertOpenidToUserId('foo')); 172 | } 173 | 174 | /** 175 | * @return void 176 | */ 177 | public function testAuthSuccess() 178 | { 179 | $this->httpClient->shouldReceive('get') 180 | ->once() 181 | ->with('user/authsucc', ['userid' => 'foo']) 182 | ->andReturn(['errcode' => 0]); 183 | 184 | $this->user->setHttpClient($this->httpClient); 185 | 186 | $this->assertEquals(['errcode' => 0], $this->user->authSuccess('foo')); 187 | } 188 | 189 | /** 190 | * @return void 191 | */ 192 | public function testGetInfo() 193 | { 194 | $this->httpClient->shouldReceive('get') 195 | ->once() 196 | ->with('user/getuserinfo', ['code' => 'foo']) 197 | ->andReturn(['errcode' => 0]); 198 | 199 | $this->user->setHttpClient($this->httpClient); 200 | 201 | $this->assertEquals(['errcode' => 0], $this->user->getInfo('foo')); 202 | } 203 | 204 | /** 205 | * @return void 206 | */ 207 | public function testGetDetail() 208 | { 209 | $this->httpClient->shouldReceive('postJson') 210 | ->once() 211 | ->with('user/getuserdetail', ['user_ticket' => 'foo']) 212 | ->andReturn(['errcode' => 0]); 213 | 214 | $this->user->setHttpClient($this->httpClient); 215 | 216 | $this->assertEquals(['errcode' => 0], $this->user->getDetail('foo')); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/App.php: -------------------------------------------------------------------------------- 1 | Api\Agent::class, 35 | 'appChat' => Api\AppChat::class, 36 | 'batch' => Api\Batch::class, 37 | 'checkIn' => Api\CheckIn::class, 38 | 'corp' => Api\Corp::class, 39 | 'crm' => Api\CRM::class, 40 | 'department' => Api\Department::class, 41 | 'invoice' => Api\Invoice::class, 42 | 'media' => Api\Media::class, 43 | 'menu' => Api\Menu::class, 44 | 'message' => Api\Message::class, 45 | 'tag' => Api\Tag::class, 46 | 'user' => Api\User::class, 47 | ]; 48 | 49 | /** 50 | * @param array $config 51 | */ 52 | public function __construct(array $config) 53 | { 54 | parent::__construct(); 55 | 56 | $this->config = new ArrayCollection($config); 57 | 58 | $this->registerServices(); 59 | } 60 | 61 | /** 62 | * @return void 63 | */ 64 | private function registerServices(): void 65 | { 66 | $this->registerLogger(); 67 | $this->registerHttpClient(); 68 | $this->registerCache(); 69 | $this->registerToken(); 70 | $this->registerCallback(); 71 | $this->registerHttpClientWithToken(); 72 | 73 | foreach ($this->apiServices as $id => $class) { 74 | $this->registerApi($id, $class); 75 | } 76 | 77 | $this->registerJsApiTicket(); 78 | $this->registerTicket(); 79 | $this->registerJssdk(); 80 | } 81 | 82 | /** 83 | * @return void 84 | */ 85 | private function registerLogger(): void 86 | { 87 | $log = $this->config->get('log'); 88 | 89 | if (is_subclass_of($log, LoggerInterface::class)) { 90 | $this->register('logger', $log); 91 | } elseif ($log) { 92 | $this->register('logger_handler', StreamHandler::class) 93 | ->setArguments([$log['file'], isset($log['level']) ? $log['level'] : 'debug']); 94 | $this->registerMonolog(); 95 | } else { 96 | $this->register('logger_handler', NullHandler::class); 97 | $this->registerMonolog(); 98 | } 99 | } 100 | 101 | /** 102 | * @return void 103 | */ 104 | private function registerMonolog(): void 105 | { 106 | $this->register('logger', Logger::class) 107 | ->addArgument('WeWork') 108 | ->addMethodCall('setTimezone', [new \DateTimeZone('PRC')]) 109 | ->addMethodCall('pushHandler', [new Reference('logger_handler')]); 110 | } 111 | 112 | /** 113 | * @return void 114 | */ 115 | private function registerHttpClient(): void 116 | { 117 | $this->register('client', Client::class) 118 | ->addArgument(new Reference('logger')) 119 | ->setFactory([ClientFactory::class, 'create']); 120 | 121 | $this->register('http_client', HttpClient::class) 122 | ->addArgument(new Reference('client')); 123 | } 124 | 125 | /** 126 | * @return void 127 | */ 128 | private function registerCache(): void 129 | { 130 | $cache = $this->config->get('cache'); 131 | 132 | if (is_subclass_of($cache, CacheInterface::class)) { 133 | $this->register('cache', $cache); 134 | } else { 135 | $service = $this->register('cache', FilesystemCache::class); 136 | 137 | if ($cache && isset($cache['path'])) { 138 | $service->setArguments(['', 0, $cache['path']]); 139 | } 140 | } 141 | } 142 | 143 | /** 144 | * @return void 145 | */ 146 | private function registerToken(): void 147 | { 148 | $this->register('token', Token::class) 149 | ->addMethodCall('setCorpId', [$this->config->get('corp_id')]) 150 | ->addMethodCall('setSecret', [$this->config->get('secret')]) 151 | ->addMethodCall('setCache', [new Reference('cache')]) 152 | ->addMethodCall('setHttpClient', [new Reference('http_client')]); 153 | } 154 | 155 | /** 156 | * @return void 157 | */ 158 | private function registerCallback(): void 159 | { 160 | $this->register('request', Request::class) 161 | ->setFactory([Request::class, 'createFromGlobals']); 162 | 163 | $this->register('crypt', WXBizMsgCrypt::class) 164 | ->setArguments([$this->config->get('token'), $this->config->get('aes_key'), $this->config->get('corp_id')]); 165 | 166 | $this->register('callback', Callback::class) 167 | ->setArguments([new Reference('request'), new Reference('crypt')]); 168 | } 169 | 170 | /** 171 | * @return void 172 | */ 173 | private function registerHttpClientWithToken(): void 174 | { 175 | $this->register('client_with_token', Client::class) 176 | ->setArguments([new Reference('logger'), new Reference('token')]) 177 | ->setFactory([ClientFactory::class, 'create']); 178 | 179 | $this->register('http_client_with_token', HttpClient::class) 180 | ->addArgument(new Reference('client_with_token')); 181 | } 182 | 183 | /** 184 | * @param string $id 185 | * @param string $class 186 | * 187 | * @return void 188 | */ 189 | private function registerApi(string $id, string $class): void 190 | { 191 | $api = $this->register($id, $class) 192 | ->addMethodCall('setHttpClient', [new Reference('http_client_with_token')]); 193 | 194 | if (in_array($id, ['agent', 'menu', 'message'])) { 195 | $api->addMethodCall('setAgentId', [$this->config->get('agent_id')]); 196 | } 197 | } 198 | 199 | /** 200 | * @return void 201 | */ 202 | private function registerJsApiTicket(): void 203 | { 204 | $this->register('jsApiTicket', JsApiTicket::class) 205 | ->addMethodCall('setSecret', [$this->config->get('secret')]) 206 | ->addMethodCall('setCache', [new Reference('cache')]) 207 | ->addMethodCall('setHttpClient', [new Reference('http_client_with_token')]); 208 | } 209 | 210 | /** 211 | * @return void 212 | */ 213 | private function registerTicket(): void 214 | { 215 | $this->register('ticket', Ticket::class) 216 | ->addMethodCall('setCache', [new Reference('cache')]) 217 | ->addMethodCall('setHttpClient', [new Reference('http_client_with_token')]); 218 | } 219 | 220 | /** 221 | * @return void 222 | */ 223 | private function registerJssdk(): void 224 | { 225 | $this->register('jssdk', JSSdk::class) 226 | ->addMethodCall('setCorpId', [$this->config->get('corp_id')]) 227 | ->addMethodCall('setJsApiTicket', [new Reference('jsApiTicket')]) 228 | ->addMethodCall('setTicket', [new Reference('ticket')]); 229 | } 230 | } 231 | --------------------------------------------------------------------------------