ruipay/wechat/cp/tp/
mod.rs

1use bytes::Bytes;
2use serde::{Serialize, Deserialize};
3use serde_json::{json, Value};
4
5use crate::{session::SessionStore, request::{RequestType}, WechatCommonResponse, LabradorResult, WechatCrypto, current_timestamp, LabraError, JsapiTicket, JsapiSignature, get_timestamp, get_nonce_str, APIClient, WechatRequest, LabraResponse, LabraRequest, SimpleStorage, WechatCpProviderToken};
6use crate::wechat::cp::constants::{ACCESS_TOKEN, ACCESS_TOKEN_KEY, AGENT_CONFIG, AUTH_URL_INSTALL, PROVIDER_ACCESS_TOKEN, SUITE_ACCESS_TOKEN, TYPE};
7use crate::wechat::cp::method::WechatCpMethod;
8use crate::wechat::cp::AccessTokenResponse;
9
10mod tag;
11mod license;
12mod media;
13mod department;
14mod user;
15mod order;
16mod agent;
17mod auth;
18
19pub use tag::*;
20pub use license::*;
21pub use media::*;
22pub use department::*;
23pub use user::*;
24pub use order::*;
25pub use agent::*;
26use crate::wechat::cp::tp::auth::WechatCpTpAuth;
27
28
29/// 企业微信第三方应用API
30#[allow(unused)]
31#[derive(Debug, Clone)]
32pub struct WechatCpTpClient<T: SessionStore> {
33    token: Option<String>,
34    /// 企微服务商企业ID,来自于企微配置
35    corp_id: String,
36    /// 第三方应用的EncodingAESKey,用来检查签名
37    aes_key: Option<String>,
38    /// 服务商secret
39    provider_secret: Option<String>,
40    agent_id: Option<i32>,
41    /// 第三方应用的其他配置
42    suite_id: Option<String>,
43    suite_secret: Option<String>,
44    client: APIClient<T>,
45}
46
47#[allow(unused)]
48impl<T: SessionStore> WechatCpTpClient<T> {
49    fn from_client(client: APIClient<T>) -> WechatCpTpClient<T> {
50        WechatCpTpClient {
51            corp_id: client.app_key.to_owned(),
52            token: None,
53            aes_key: None,
54            agent_id: None,
55            suite_id: None,
56            suite_secret: None,
57            client,
58            provider_secret: None,
59        }
60    }
61
62    pub fn aes_key(mut self, aes_key: &str) -> Self {
63        self.aes_key = aes_key.to_string().into();
64        self
65    }
66
67    pub fn token(mut self, token: &str) -> Self {
68        self.token = token.to_string().into();
69        self
70    }
71
72    pub fn agent_id(mut self, agent_id: i32) -> Self {
73        self.agent_id = agent_id.into();
74        self
75    }
76
77    pub fn provider_secret(mut self, provider_secret: &str) -> Self {
78        self.provider_secret = provider_secret.to_string().into();
79        self
80    }
81
82    pub fn suite_id(mut self, suite_id: &str) -> Self {
83        self.suite_id = suite_id.to_string().into();
84        self
85    }
86
87    pub fn suite_secret(mut self, suite_secret: &str) -> Self {
88        self.suite_secret = suite_secret.to_string().into();
89        self
90    }
91
92    pub fn get_corpid(&self) -> &str {
93        &self.corp_id
94    }
95
96    fn key_with_prefix(&self, key: &str) -> String {
97        format!("cp:{}:{}", self.suite_id.to_owned().unwrap_or_default(), key)
98    }
99
100    /// get the wechat client
101    pub fn new<S: Into<String>>(crop_id: S) -> WechatCpTpClient<SimpleStorage> {
102        let client = APIClient::<SimpleStorage>::from_session(crop_id.into(), "", "https://qyapi.weixin.qq.com", SimpleStorage::new());
103        WechatCpTpClient::<SimpleStorage>::from_client(client)
104    }
105
106    /// get the wechat client
107    pub fn from_session<S: Into<String>>(crop_id: S, session: T) -> WechatCpTpClient<T> {
108        let client = APIClient::from_session(crop_id.into(), "", "https://qyapi.weixin.qq.com", session);
109        Self::from_client(client)
110    }
111
112    /// 授权企业的access token相关
113    fn get_access_token(&self, auth_corp_id: &str) -> String {
114        let session = self.client.session();
115        session.get::<_, String>(self.key_with_prefix(auth_corp_id) + ACCESS_TOKEN_KEY, None).unwrap_or(None).unwrap_or_default()
116    }
117
118    /// <pre>
119    /// 验证推送过来的消息的正确性
120    /// 详情请见: <a href="https://work.weixin.qq.com/api/doc#90000/90139/90968/消息体签名校验">文档</a>
121    /// </pre>
122    pub fn check_signature(&self, signature: &str, timestamp: i64, nonce: &str, data: &str) -> LabradorResult<bool> {
123        let crp = WechatCrypto::new(&self.aes_key.to_owned().unwrap_or_default()).token(&self.token.to_owned().unwrap_or_default());
124        let _ = crp.check_signature(signature, timestamp, nonce, data)?;
125        Ok(true)
126    }
127
128    /// 获取加密工具
129    pub fn get_crypto(&self) -> WechatCrypto {
130        let crp = WechatCrypto::new(&self.aes_key.to_owned().unwrap_or_default()).token(&self.token.to_owned().unwrap_or_default());
131        crp
132    }
133
134    /// 获得suite_ticket,不强制刷新suite_ticket
135    /// 由微信服务器推送
136    pub fn get_suite_ticket(&self) -> LabradorResult<String> {
137        let session = self.client.session();
138        let token_key = format!("{}_suite_ticket_key_cp", self.corp_id);
139        let expires_key = format!("{}_suite_ticket_expires_at_cp", self.corp_id);
140        let token: String = session.get(&token_key, Some("".to_owned()))?.unwrap_or_default();
141        let timestamp = current_timestamp();
142        let expires_at: i64 = session.get(&expires_key, Some(timestamp))?.unwrap_or_default();
143        if expires_at <= timestamp {
144            return Err(LabraError::ApiError("invaild suite ticket".to_string()));
145        }
146        Ok(token)
147    }
148
149    /// 获得suite_ticket,不强制刷新suite_ticket
150    /// 由微信服务器推送
151    pub fn set_suite_ticket_expire(&self, suite_ticket: &str, expire_second: i64) -> LabradorResult<()> {
152        let expires_at = current_timestamp() + expire_second;
153        let session = self.client.session();
154        let token_key = format!("{}_suite_ticket_key_cp", self.corp_id);
155        let expires_key = format!("{}_suite_ticket_expires_at_cp", self.corp_id);
156        session.set(token_key.to_string(), suite_ticket, Some(expire_second as usize))?;
157        session.set(expires_key, expires_at, Some(expire_second as usize))?;
158        Ok(())
159    }
160
161    /// <pre>
162    /// 保存企业微信定时推送的suite_ticket,(每10分钟)
163    /// 详情请见:<a href="https://work.weixin.qq.com/api/doc#90001/90143/90628">文档</a>
164    ///
165    /// 注意:微信不是固定10分钟推送suite_ticket的, 且suite_ticket的有效期为30分钟
166    /// <a href="https://work.weixin.qq.com/api/doc/10975#%E8%8E%B7%E5%8F%96%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%94%E7%94%A8%E5%87%AD%E8%AF%81">文档</a>
167    /// </pre>
168    pub fn set_suite_ticket(&self, suite_ticket: &str) -> LabradorResult<()> {
169        self.set_suite_ticket_expire(suite_ticket, 28 * 60)
170    }
171
172    /// <pre>
173    /// 获取suite_access_token,本方法线程安全
174    /// 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限
175    /// 另:本service的所有方法都会在suite_access_token过期是调用此方法
176    /// 程序员在非必要情况下尽量不要主动调用此方法
177    /// 详情请见: <a href="https://work.weixin.qq.com/api/doc#90001/90143/90600">文档</a>
178    /// </pre>
179    pub async fn get_suite_access_token(&self) -> LabradorResult<String> {
180        self.get_suite_access_token_force(false).await
181    }
182
183    /// <pre>
184    /// 获取suite_access_token,本方法线程安全
185    /// 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限
186    /// 另:本service的所有方法都会在suite_access_token过期是调用此方法
187    /// 详情请见: <a href="https://work.weixin.qq.com/api/doc#90001/90143/90600">文档</a>
188    /// </pre>
189    pub async fn get_suite_access_token_force(&self, force_refresh: bool) -> LabradorResult<String> {
190        let session = self.client.session();
191        let token_key = format!("{}_suite_access_token_cp", self.corp_id);
192        let expires_key = format!("{}_suite_access_token_expires_at_cp", self.corp_id);
193        let token: String = session.get(&token_key, Some("".to_owned()))?.unwrap_or_default();
194        let timestamp = current_timestamp();
195        let expires_at: i64 = session.get(&expires_key, Some(timestamp))?.unwrap_or_default();
196        if expires_at <= timestamp || force_refresh {
197            let suite_ticket = self.get_suite_ticket().unwrap_or_default();
198            let req = json!({
199                "suite_id": self.suite_id,
200                "suite_secret": self.suite_secret,
201                "suite_ticket": suite_ticket
202            });
203            let result = self.client.post(WechatCpMethod::GetSuiteToken, vec![], req, RequestType::Json).await?.json::<Value>()?;
204            let result = WechatCommonResponse::parse::<WechatCpSuiteAccessTokenResponse>(result)?;
205            let token = result.suite_access_token;
206            let expires_in = result.expires_in;
207            // 预留200秒的时间
208            let expires_at = current_timestamp() + expires_in - 200;
209            session.set(&token_key, token.to_owned(), Some(expires_in as usize));
210            session.set(&expires_key, expires_at, Some(expires_in as usize));
211            Ok(token.to_string())
212        } else {
213            Ok(token)
214        }
215    }
216
217    ///
218    /// <pre>
219    /// 获取应用的 jsapi ticket
220    /// </pre>
221    pub async fn get_suite_jsapi_ticket(&self, auth_corp_id: &str) -> LabradorResult<String> {
222        self.get_suite_jsapi_ticket_force(auth_corp_id, false).await
223    }
224
225    ///
226    /// <pre>
227    /// 获取应用的 jsapi ticket, 支持强制刷新
228    /// </pre>
229    pub async fn get_suite_jsapi_ticket_force(&self, auth_corp_id: &str, force_refresh: bool) -> LabradorResult<String> {
230        let mut session = self.client.session();
231        let ticket_key = format!("{}_suite_jsapi_ticket_cp", self.corp_id);
232        let expires_key = format!("{}_suite_jsapi_ticket_expires_at_cp", self.corp_id);
233        let ticket: String = session.get(&ticket_key, Some("".to_string()))?.unwrap_or_default();
234        let timestamp = current_timestamp();
235        let expires_at: i64 = session.get(&expires_key, Some(timestamp))?.unwrap_or_default();
236        if expires_at <= timestamp || force_refresh {
237            let v = self.client.get(WechatCpMethod::GetSuiteJsapiTicket, vec![(TYPE.to_string(), AGENT_CONFIG.to_string()), (ACCESS_TOKEN.to_string(), self.get_access_token(auth_corp_id))], RequestType::Json).await?.json::<Value>()?;
238            let res = WechatCommonResponse::parse::<JsapiTicket>(v)?;
239            let ticket = res.ticket;
240            let expires_in = res.expires_in;
241            // 预留200秒的时间
242            let expires_at = current_timestamp() + expires_in - 200;
243            session.set(&ticket_key, ticket.to_string(), Some(expires_in as usize));
244            session.set(&expires_key, expires_at, Some(expires_in as usize));
245            Ok(ticket.to_string())
246        } else {
247            Ok(ticket)
248        }
249    }
250
251    ///
252    /// <pre>
253    /// 获取授权企业的 jsapi ticket
254    /// </pre>
255    pub async fn get_auth_corp_jsapi_ticket(&self, auth_corp_id: &str) -> LabradorResult<String> {
256        self.get_auth_corp_jsapi_ticket_force(auth_corp_id, false).await
257    }
258
259    ///
260    /// <pre>
261    /// 获取授权企业的 jsapi ticket, 支持强制刷新
262    /// </pre>
263    pub async fn get_auth_corp_jsapi_ticket_force(&self, auth_corp_id: &str, force_refresh: bool) -> LabradorResult<String> {
264        let mut session = self.client.session();
265        let ticket_key = format!("{}_auth_corp_jsapi_ticket_cp", self.corp_id);
266        let expires_key = format!("{}_auth_corp_jsapi_ticket_expires_at_cp", self.corp_id);
267        let ticket: String = session.get(&ticket_key, Some("".to_string()))?.unwrap_or_default();
268        let timestamp = current_timestamp();
269        let expires_at: i64 = session.get(&expires_key, Some(timestamp))?.unwrap_or_default();
270        if expires_at <= timestamp || force_refresh {
271            let res = self.client.get(WechatCpMethod::GetJsapiTicket, vec![(ACCESS_TOKEN.to_string(), self.get_access_token(auth_corp_id))], RequestType::Json).await?.json::<JsapiTicket>()?;
272            let ticket = res.ticket;
273            let expires_in = res.expires_in;
274            // 预留200秒的时间
275            let expires_at = current_timestamp() + expires_in - 200;
276            session.set(&ticket_key, ticket.to_string(), Some(expires_in as usize));
277            session.set(&expires_key, expires_at, Some(expires_in as usize));
278            Ok(ticket.to_string())
279        } else {
280            Ok(ticket)
281        }
282    }
283
284
285    /// <pre>
286    /// 获取企业凭证
287    /// </pre>
288    pub async fn get_corp_token(&self, auth_corpid: &str, permanent_code: &str) -> LabradorResult<AccessTokenResponse> {
289        self.get_corp_token_force(auth_corpid, permanent_code, false).await
290    }
291
292    /// <pre>
293    /// 获取企业凭证, 支持强制刷新
294    /// </pre>
295    pub async fn get_corp_token_force(&self, auth_corpid: &str, permanent_code: &str, force_refresh: bool) -> LabradorResult<AccessTokenResponse> {
296        let session = self.client.session();
297        let token_key = format!("{}_corp_access_token_cp", auth_corpid);
298        let expires_key = format!("{}_corp_access_token_expires_at_cp", auth_corpid);
299        let token: String = session.get(&token_key, Some("".to_owned()))?.unwrap_or_default();
300        let timestamp = get_timestamp();
301        let expires_at: i64 = session.get(&expires_key, Some(timestamp))?.unwrap_or_default();
302        if expires_at <= timestamp || force_refresh {
303            let suite_ticket = self.get_suite_ticket()?;
304            let req = json!({
305                "auth_corpid": auth_corpid,
306                "permanent_code": permanent_code,
307            });
308            let v = self.client.post(WechatCpMethod::GetCorpToken, vec![], req, RequestType::Json).await?.json::<Value>()?;
309            let result = WechatCommonResponse::parse::<AccessTokenResponse>(v)?;
310            let token = result.access_token.to_string();
311            let expires_in = result.expires_in;
312            // 预留200秒的时间
313            let expires_at = get_timestamp() + expires_in - 200;
314            session.set(&token_key, token.to_owned(), Some(expires_in as usize));
315            session.set(&expires_key, expires_at, Some(expires_in as usize));
316            Ok(result)
317        } else {
318            Ok(AccessTokenResponse { access_token: token.to_string(), expires_in: expires_at })
319        }
320    }
321
322    /// <pre>
323    /// 获取服务商providerToken
324    /// </pre>
325    pub async fn get_wechat_provider_token(&self) -> LabradorResult<String> {
326        let session = self.client.session();
327        let token_key = format!("{}_provider_access_token_cp", self.corp_id);
328        let expires_key = format!("{}_provider_access_token_expires_at_cp", self.corp_id);
329        let token: String = session.get(&token_key, Some("".to_owned()))?.unwrap_or_default();
330        let timestamp = get_timestamp();
331        let expires_at: i64 = session.get(&expires_key, Some(timestamp))?.unwrap_or_default();
332        if expires_at <= timestamp {
333            let req = json!({
334                "corpid": self.corp_id,
335                "provider_secret": self.provider_secret,
336            });
337            let v = self.client.post(WechatCpMethod::GetProviderToken, vec![], req, RequestType::Json).await?.json::<Value>()?;
338            let result = WechatCommonResponse::parse::<WechatCpProviderToken>(v)?;
339            let token = result.provider_access_token.to_string();
340            let expires_in = result.expires_in;
341            // 预留200秒的时间
342            let expires_at = get_timestamp() + expires_in - 200;
343            session.set(&token_key, token.to_owned(), Some(expires_in as usize));
344            session.set(&expires_key, expires_at, Some(expires_in as usize));
345            Ok(token)
346        } else {
347            Ok(token)
348        }
349    }
350
351    /// <pre>
352    /// 获取企业永久授权码信息
353    /// </pre>
354    pub async fn get_permanent_code_info(&self, auth_code: &str) -> LabradorResult<WechatCpThirdPermanentCodeInfo> {
355        let req = json!({
356            "auth_code": auth_code,
357        });
358        let result = self.post(WechatCpMethod::GetPermanentCode, vec![], req, RequestType::Json).await?.json::<Value>()?;
359        WechatCommonResponse::parse::<WechatCpThirdPermanentCodeInfo>(result)
360    }
361
362    /// <pre>
363    /// 获取预授权链接
364    /// </pre>
365    pub async fn get_pre_auth_url(&self, redirect_uri: &str, state: Option<&str>) -> LabradorResult<String> {
366        let result = self.get(WechatCpMethod::GetPreAuthCode, vec![], RequestType::Json).await?.json::<WechatCpThirdPreauthCode>()?;
367        let mut pre_auth_url = format!("{}?suite_id={}&pre_auth_code={}&redirect_uri={}", AUTH_URL_INSTALL, self.suite_id.to_owned().unwrap_or_default(), result.pre_auth_code, urlencoding::encode(redirect_uri));
368        if let Some(state) = state {
369            pre_auth_url.push_str(&format!("&state={}", state));
370        }
371        Ok(pre_auth_url)
372    }
373
374    /// <pre>
375    /// 设置授权配置
376    /// </pre>
377    pub async fn set_session_info(&self, pre_auth_code: &str, app_ids: Vec<&str>, auth_type: u8) -> LabradorResult<WechatCommonResponse> {
378        let req = json!({
379            "pre_auth_code": pre_auth_code,
380            "session_info":
381            {
382                "appid": app_ids,
383                "auth_type": auth_type
384            }
385        });
386        self.post(WechatCpMethod::SetSessionInfo, vec![], req, RequestType::Json).await?.json::<WechatCommonResponse>()
387    }
388
389    /// <pre>
390    /// 获取企业的授权信息
391    /// </pre>
392    pub async fn get_auth_info(&self, auth_corp_id: &str, permanent_code: &str) -> LabradorResult<WechatCpThirdAuthInfo> {
393        let req = json!({
394           "auth_corpid": auth_corp_id,
395           "permanent_code": permanent_code
396        });
397        let result = self.client.post(WechatCpMethod::GetAuthInfo, vec![], req, RequestType::Json).await?.json::<Value>()?;
398        WechatCommonResponse::parse::<WechatCpThirdAuthInfo>(result)
399    }
400
401
402    /// <pre>
403    /// 获取应用的管理员列表
404    /// 第三方服务商可以用此接口获取授权企业中某个第三方应用的管理员列表(不包括外部管理员),以便服务商在用户进入应用主页之后根据是否管理员身份做权限的区分。
405    /// </pre>
406    pub async fn get_admin_info(&self, auth_corp_id: &str, agent_id: i32) -> LabradorResult<Vec<AdminUserInfo>> {
407        let req = json!({
408           "auth_corpid": auth_corp_id,
409           "agentid": agent_id
410        });
411        let result = self.client.post(WechatCpMethod::GetAdminInfo, vec![], req, RequestType::Json).await?.json::<Value>()?;
412        let v = WechatCommonResponse::parse::<Value>(result)?;
413        serde_json::from_value::<Vec<AdminUserInfo>>(v["admin"].to_owned()).map_err(LabraError::from)
414    }
415
416
417    /// <pre>
418    /// 获取应用二维码
419    /// 用于获取第三方应用二维码。
420    /// </pre>
421    pub async fn get_app_qrcode_buffer(&self, suite_id: &str, appid: Option<i32>, state: Option<&str>, style: Option<u8>) -> LabradorResult<Bytes> {
422        let req = json!({
423           "suite_id": suite_id,
424            "appid": appid,
425            "state": state,
426            "style": style,
427            "result_type": 1
428        });
429        self.client.post(WechatCpMethod::GetAppQrcode, vec![], req, RequestType::Json).await?.bytes()
430    }
431
432    /// <pre>
433    /// 获取应用二维码
434    /// 用于获取第三方应用二维码。
435    /// </pre>
436    pub async fn get_app_qrcode_url(&self, suite_id: &str, appid: Option<i32>, state: Option<&str>, style: Option<u8>, result_type: Option<u8>) -> LabradorResult<String> {
437        let req = json!({
438           "suite_id": suite_id,
439            "appid": appid,
440            "state": state,
441            "style": style,
442            "result_type": 2
443        });
444        let v = self.client.post(WechatCpMethod::GetAppQrcode, vec![], req, RequestType::Json).await?.json::<Value>()?;
445        let v = WechatCommonResponse::parse::<Value>(v)?;
446        let qrcode = v["qrcode"].as_str().unwrap_or_default();
447        Ok(qrcode.to_string())
448    }
449
450    /// <pre>
451    /// 明文corpid转换为加密corpid
452    /// 为更好地保护企业与用户的数据,第三方应用获取的corpid不再是明文的corpid,将升级为第三方服务商级别的加密corpid(了解更多)。第三方可以将已有的明文corpid转换为第三方的加密corpid。。
453    /// </pre>
454    pub async fn corpid_to_opencorpid(&self, corpid: &str) -> LabradorResult<String> {
455        let req = json!({
456           "corpid": corpid,
457        });
458        let access_token = self.get_wechat_provider_token().await?;
459        let query = vec![(PROVIDER_ACCESS_TOKEN.to_string(), access_token)];
460        let v = self.client.post(WechatCpMethod::CorpToOpenCorpid, query, req, RequestType::Json).await?.json::<Value>()?;
461        let v = WechatCommonResponse::parse::<Value>(v)?;
462        let qrcode = v["open_corpid"].as_str().unwrap_or_default();
463        Ok(qrcode.to_string())
464    }
465
466    ///
467    /// <pre>
468    /// 创建机构级jsApiTicket签名
469    /// 详情参见企业微信第三方应用开发文档[请见](https://work.weixin.qq.com/api/doc/90001/90144/90539)
470    /// </pre>
471    pub async fn create_auth_corp_jsapi_signature(&self, url: &str, auth_corp_id: &str) -> LabradorResult<JsapiSignature> {
472        Ok(self.created_wechat_jsapi_signature(url, auth_corp_id, &self.get_auth_corp_jsapi_ticket(auth_corp_id).await?))
473    }
474
475    ///
476    /// <pre>
477    /// 创建应用级jsapiTicket签名
478    /// 详情参见企业微信第三方应用开发文档[请见](https://work.weixin.qq.com/api/doc/90001/90144/90539)
479    /// </pre>
480    pub async fn create_suite_jsapi_signature(&self, url: &str, auth_corp_id: &str) -> LabradorResult<JsapiSignature> {
481        Ok(self.created_wechat_jsapi_signature(url, auth_corp_id, &self.get_suite_jsapi_ticket(auth_corp_id).await?))
482    }
483
484    fn created_wechat_jsapi_signature(&self, url: &str, auth_corp_id: &str, jsapi_ticket: &str) -> JsapiSignature {
485        let timestamp = get_timestamp() / 1000;
486        let noncestr = get_nonce_str();
487        let signature = WechatCrypto::get_sha1_sign(&vec!["jsapi_ticket=".to_string() + &jsapi_ticket,
488                                                          "noncestr=".to_string() + &noncestr,
489                                                          "timestamp=".to_string() + &timestamp.to_string(), "url=".to_string() + &url].join("&"));
490        JsapiSignature {
491            app_id: auth_corp_id.to_string(),
492            nonce_str: noncestr,
493            url: url.to_string(),
494            signature,
495            timestamp,
496        }
497    }
498
499    ///<pre>
500    /// Service没有实现某个API的时候,可以用这个,
501    /// 比 get 和 post 方法更灵活,可以自己构造用来处理不同的参数和不同的返回类型。
502    /// </pre>
503    async fn execute<D: WechatRequest, B: Serialize>(&self, request: D, corp_id: Option<&str>) -> LabradorResult<LabraResponse> {
504        let mut querys = request.get_query_params();
505        if request.is_need_token() {
506            if let Some(corp_id) = corp_id {
507                let access_token = self.get_access_token(corp_id);
508                if !access_token.is_empty() {
509                    querys.insert(ACCESS_TOKEN.to_string(), access_token);
510                }
511            }
512        }
513        let params = querys.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect::<Vec<(String, String)>>();
514        let mut req = LabraRequest::<B>::new().url(request.get_api_method_name())
515            .params(params).method(request.get_request_method()).req_type(request.get_request_type()).body(request.get_request_body::<B>());
516        self.client.request(req).await
517    }
518
519    /// 发送POST请求
520    async fn post<D: Serialize>(&self, method: WechatCpMethod, mut querys: Vec<(String, String)>, data: D, request_type: RequestType) -> LabradorResult<LabraResponse> {
521        if method.need_token() {
522            let token = self.get_suite_access_token_force(false).await?;
523            querys.push((SUITE_ACCESS_TOKEN.to_string(), token));
524        }
525        self.client.post(method, querys, data, request_type).await
526    }
527
528    /// 发送GET请求
529    async fn get(&self, method: WechatCpMethod, mut params: Vec<(String, String)>, request_type: RequestType) -> LabradorResult<LabraResponse> {
530        if method.need_token() {
531            let token = self.get_suite_access_token_force(false).await?.to_string();
532            params.push((SUITE_ACCESS_TOKEN.to_string(), token));
533        }
534        self.client.get(method, params, request_type).await
535    }
536
537    /// 部门
538    pub fn department(&self) -> WechatCpTpDepartment<T> {
539        WechatCpTpDepartment::new(self)
540    }
541
542    /// 接口调用许可
543    pub fn license(&self) -> WechatCpTpLicense<T> {
544        WechatCpTpLicense::new(self)
545    }
546
547    /// 媒体
548    pub fn media(&self) -> WechatCpTpMedia<T> {
549        WechatCpTpMedia::new(self)
550    }
551
552    /// 订单
553    pub fn order(&self) -> WechatCpTpOrder<T> {
554        WechatCpTpOrder::new(self)
555    }
556
557    /// 标签
558    pub fn tag(&self) -> WechatCpTpTag<T> {
559        WechatCpTpTag::new(self)
560    }
561
562    /// 用户
563    pub fn user(&self) -> WechatCpTpUser<T> {
564        WechatCpTpUser::new(self)
565    }
566
567    /// 第三方应用
568    pub fn agent(&self) -> WechatCpTpAgent<T> {
569        WechatCpTpAgent::new(self)
570    }
571
572    /// 身份
573    pub fn auth(&self) -> WechatCpTpAuth<T> {
574        WechatCpTpAuth::new(self)
575    }
576}
577
578//----------------------------------------------------------------------------------------------------------------------------
579
580#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
581pub struct WechatCpSuiteAccessTokenResponse {
582    pub suite_access_token: String,
583    pub expires_in: i64,
584}
585
586/// 服务商模式获取永久授权码信息
587#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
588pub struct WechatCpThirdPermanentCodeInfo {
589    pub access_token: Option<String>,
590    pub permanent_code: String,
591    /// 授权企业信息
592    pub auth_corp_info: AuthCorpInfo,
593    /// 授权信息。如果是通讯录应用,且没开启实体应用,是没有该项的。通讯录应用拥有企业通讯录的全部信息读写权限
594    pub auth_info: Option<AuthInfo>,
595    /// 授权用户信息
596    pub auth_user_info: Option<AuthUserInfo>,
597    /// 企业当前生效的版本信息
598    pub edition_info: Option<EditionInfo>,
599    pub expires_in: Option<i64>,
600    /// 安装应用时,扫码或者授权链接中带的state值。详见state说明
601    pub state: Option<String>,
602}
603
604
605#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
606pub struct AuthCorpInfo {
607    pub corpid: String,
608    pub corp_name: String,
609    pub corp_type: Option<String>,
610    pub corp_square_logo_url: Option<String>,
611    pub corp_round_logo_url: Option<String>,
612    pub corp_user_max: Option<i32>,
613    pub corp_agent_max: Option<i32>,
614    /// 所绑定的企业微信主体名称(仅认证过的企业有)
615    pub corp_full_name: Option<String>,
616    /// 授权企业在微工作台(原企业号)的二维码,可用于关注微工作台
617    pub corp_wxqrcode: Option<String>,
618    pub corp_scale: Option<String>,
619    pub corp_industry: Option<String>,
620    pub corp_sub_industry: Option<String>,
621    pub location: Option<String>,
622    /// 认证到期时间
623    pub verified_end_time: Option<i64>,
624    /// 企业类型,1. 企业; 2. 政府以及事业单位; 3. 其他组织, 4.团队号
625    pub subject_type: Option<u8>,
626}
627
628
629/// 企业当前生效的版本信息
630#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
631pub struct EditionInfo {
632    pub agent: Option<Vec<Agent>>,
633}
634
635
636/// 授权人员信息
637#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
638pub struct AuthUserInfo {
639    pub userid: Option<String>,
640    pub name: Option<String>,
641    pub avatar: Option<String>,
642    /// 授权管理员的open_userid,可能为空
643    pub open_userid: Option<String>,
644}
645
646
647/// 管理员信息
648#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
649pub struct AdminUserInfo {
650    pub userid: Option<String>,
651    /// 管理员的open_userid,可能为空
652    pub open_userid: Option<String>,
653    /// 该管理员对应用的权限:0=发消息权限,1=管理权限
654    pub auth_type: Option<u8>,
655}
656
657
658/// 授权信息
659#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
660pub struct AuthInfo {
661    /// 授权的应用信息,注意是一个数组,但仅旧的多应用套件授权时会返回多个agent,对新的单应用授权,永远只返回一个agent
662    pub agent: Vec<Agent>,
663}
664
665#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
666pub struct Agent {
667    pub agentid: i32,
668    pub name: String,
669    pub round_logo_url: Option<String>,
670    pub square_logo_url: Option<String>,
671    /// 版本id
672    pub edition_id: Option<String>,
673    /// 版本名称
674    pub edition_name: Option<String>,
675    /// 付费状态
676    ///<br/>
677    ///<ul>
678    ///  <li>0-没有付费;</li>
679    ///  <li>1-限时试用;</li>
680    ///  <li>2-试用过期;</li>
681    ///  <li>3-购买期内;</li>
682    ///  <li>4-购买过期;</li>
683    ///  <li>5-不限时试用;</li>
684    ///  <li>6-购买期内,但是人数超标, 注意,超标后还可以用7天;</li>
685    ///  <li>7-购买期内,但是人数超标, 且已经超标试用7天</li>
686    ///</ul>
687    pub app_status: Option<u8>,
688    /// 授权模式,0为管理员授权;1为成员授权
689    pub auth_mode: Option<u8>,
690    /// 是否为代开发自建应用
691    pub is_customized_app: Option<bool>,
692    /// 是否虚拟版本
693    pub is_virtual_version: Option<bool>,
694    /// 是否由互联企业分享安装。详见 <a href='https://developer.work.weixin.qq.com/document/path/93360#24909'>企业互联</a>
695    pub is_shared_from_other_corp: Option<bool>,
696    /// 用户上限。
697    /// <p>特别注意, 以下情况该字段无意义,可以忽略:</p>
698    /// <ul>
699    ///   <li>1. 固定总价购买</li>
700    ///   <li>2. app_status = 限时试用/试用过期/不限时试用</li>
701    ///   <li>3. 在第2条“app_status=不限时试用”的情况下,如果该应用的配置为“小企业无使用限制”,user_limit有效,且为限制的人数</li>
702    /// </ul>
703    pub user_limit: Option<i32>,
704    /// 版本到期时间, 秒级时间戳, 根据需要自行乘以1000(根据购买版本,可能是试用到期时间或付费使用到期时间)。
705    /// <p>特别注意,以下情况该字段无意义,可以忽略:</p>
706    /// <ul>
707    ///   <li>1. app_status = 不限时试用</li>
708    /// </ul>
709    pub expired_time: Option<i64>,
710    /// 应用权限
711    pub privilege: Option<Privilege>,
712}
713
714/// 应用对应的权限
715#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
716pub struct Privilege {
717    /// 权限等级。
718    /// 1:通讯录基本信息只读
719    /// 2:通讯录全部信息只读
720    /// 3:通讯录全部信息读写
721    /// 4:单个基本信息只读
722    /// 5:通讯录全部信息只写
723    pub level: Option<u8>,
724    pub allow_party: Option<Vec<String>>,
725    pub allow_user: Option<Vec<i32>>,
726    pub extra_party: Option<Vec<i32>>,
727    pub extra_tag: Option<Vec<i32>>,
728    pub extra_user: Option<Vec<String>>,
729}
730
731
732/// 预授权码返回
733#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
734pub struct WechatCpThirdPreauthCode {
735    pub pre_auth_code: String,
736    pub expires_in: i64,
737}
738
739
740/// 服务商模式获取授权信息
741#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
742pub struct WechatCpThirdAuthInfo {
743    /// 服务商信息
744    pub dealer_corp_info: Option<DealerCorpInfo>,
745    /// 授权企业信息
746    pub auth_corp_info: Option<AuthCorpInfo>,
747    /// 授权信息。如果是通讯录应用,且没开启实体应用,是没有该项的。通讯录应用拥有企业通讯录的全部信息读写权限
748    pub auth_info: Option<AuthInfo>,
749    /// 企业当前生效的版本信息
750    pub edition_info: Option<EditionInfo>,
751}
752
753
754/// 服务商模式获取授权信息
755#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
756pub struct DealerCorpInfo {
757    pub corpid: Option<String>,
758    pub corp_name: Option<String>,
759}