Skip to main content

wechat_oa_sdk/api/
qrcode.rs

1use serde::Deserialize;
2
3use crate::client::WeChatClient;
4use crate::error::{Result, WeChatError};
5use crate::models::qrcode::{QrCodeAction, QrCodeResponse};
6
7const WECHAT_MP_BASE: &str = "https://mp.weixin.qq.com";
8
9impl WeChatClient {
10    /// Create a QR code with a numeric scene value.
11    ///
12    /// - `scene_id`: Scene value (32-bit non-zero integer)
13    /// - `action`: QR code type (temporary or permanent)
14    /// - `expire_seconds`: Expiration time for temporary QR codes (max 2592000 = 30 days)
15    pub async fn create_qrcode(
16        &self,
17        scene_id: u32,
18        action: QrCodeAction,
19        expire_seconds: Option<i64>,
20    ) -> Result<QrCodeResponse> {
21        let mut body = serde_json::json!({
22            "action_name": action.as_str(),
23            "action_info": {
24                "scene": {
25                    "scene_id": scene_id
26                }
27            }
28        });
29
30        if let Some(expire) = expire_seconds {
31            body["expire_seconds"] = serde_json::json!(expire);
32        }
33
34        self.post_json("/qrcode/create", &body).await
35    }
36
37    /// Create a QR code with a string scene value.
38    ///
39    /// - `scene_str`: Scene value string (max 64 characters)
40    /// - `action`: QR code type (use TemporaryStr or PermanentStr)
41    /// - `expire_seconds`: Expiration time for temporary QR codes
42    pub async fn create_qrcode_str(
43        &self,
44        scene_str: &str,
45        action: QrCodeAction,
46        expire_seconds: Option<i64>,
47    ) -> Result<QrCodeResponse> {
48        let mut body = serde_json::json!({
49            "action_name": action.as_str(),
50            "action_info": {
51                "scene": {
52                    "scene_str": scene_str
53                }
54            }
55        });
56
57        if let Some(expire) = expire_seconds {
58            body["expire_seconds"] = serde_json::json!(expire);
59        }
60
61        self.post_json("/qrcode/create", &body).await
62    }
63
64    /// Get the URL to display a QR code image.
65    ///
66    /// The ticket should be URL-encoded when used.
67    pub fn get_qrcode_url(ticket: &str) -> String {
68        format!(
69            "{}/cgi-bin/showqrcode?ticket={}",
70            WECHAT_MP_BASE,
71            urlencoding::encode(ticket)
72        )
73    }
74
75    /// Download QR code image as bytes.
76    pub async fn download_qrcode(&self, ticket: &str) -> Result<Vec<u8>> {
77        let url = Self::get_qrcode_url(ticket);
78        let resp = self.http.get(&url).send().await?;
79
80        if !resp.status().is_success() {
81            return Err(WeChatError::Api {
82                errcode: resp.status().as_u16() as i64,
83                errmsg: "Failed to download QR code".to_string(),
84            });
85        }
86
87        Ok(resp.bytes().await?.to_vec())
88    }
89
90    /// Convert a long URL to a short URL.
91    ///
92    /// Note: This API may be deprecated. Consider using other URL shorteners.
93    pub async fn create_short_url(&self, long_url: &str) -> Result<String> {
94        let body = serde_json::json!({
95            "action": "long2short",
96            "long_url": long_url
97        });
98
99        #[derive(Deserialize)]
100        struct Response {
101            short_url: Option<String>,
102            errcode: Option<i64>,
103            errmsg: Option<String>,
104        }
105
106        let resp: Response = self.post_json("/shorturl", &body).await?;
107
108        if let Some(errcode) = resp.errcode {
109            if errcode != 0 {
110                return Err(WeChatError::Api {
111                    errcode,
112                    errmsg: resp.errmsg.unwrap_or_default(),
113                });
114            }
115        }
116
117        resp.short_url.ok_or(WeChatError::TokenUnavailable)
118    }
119
120    /// Get the WeChat server IP list.
121    ///
122    /// Useful for configuring firewalls to allow WeChat callbacks.
123    pub async fn get_callback_ip_list(&self) -> Result<Vec<String>> {
124        #[derive(Deserialize)]
125        struct Response {
126            ip_list: Vec<String>,
127        }
128
129        let resp: Response = self.get("/getcallbackip", &[]).await?;
130        Ok(resp.ip_list)
131    }
132
133    /// Get the WeChat API server IP list.
134    pub async fn get_api_domain_ip_list(&self) -> Result<Vec<String>> {
135        #[derive(Deserialize)]
136        struct Response {
137            ip_list: Vec<String>,
138        }
139
140        let resp: Response = self.get("/get_api_domain_ip", &[]).await?;
141        Ok(resp.ip_list)
142    }
143
144    /// Check if the current network can access WeChat API.
145    pub async fn check_network(&self) -> Result<bool> {
146        #[derive(Deserialize)]
147        #[allow(dead_code)]
148        struct Response {
149            dns: Option<Vec<DnsInfo>>,
150            ping: Option<Vec<PingInfo>>,
151        }
152
153        #[derive(Deserialize)]
154        #[allow(dead_code)]
155        struct DnsInfo {
156            ip: String,
157            real_operator: String,
158        }
159
160        #[derive(Deserialize)]
161        #[allow(dead_code)]
162        struct PingInfo {
163            ip: String,
164            from_operator: String,
165            package_loss: String,
166            time: String,
167        }
168
169        let body = serde_json::json!({
170            "action": "all",
171            "check_operator": "DEFAULT"
172        });
173
174        let _resp: Response = self.post_json("/callback/check", &body).await?;
175        Ok(true)
176    }
177}