1use std::path::PathBuf;
6
7use crate::error::{WxPayError, WxPayResult};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum Environment {
12 #[default]
14 Production,
15 Sandbox,
17}
18
19impl Environment {
20 pub fn base_url(&self) -> &str {
22 match self {
23 Self::Production => "https://api.mch.weixin.qq.com",
24 Self::Sandbox => "https://api.mch.weixin.qq.com",
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
31pub struct WxPayConfig {
32 pub app_id: String,
34
35 pub merchant_id: String,
37
38 pub api_v3_key: String,
40
41 pub private_key: Vec<u8>,
43
44 pub cert_serial_number: String,
46
47 pub environment: Environment,
49
50 pub platform_certificates: Vec<Vec<u8>>,
52
53 pub timeout: u64,
55
56 pub max_retries: u32,
58}
59
60#[derive(Debug, Clone)]
62pub struct WxPayConfigBuilder {
63 app_id: Option<String>,
64 merchant_id: Option<String>,
65 api_v3_key: Option<String>,
66 private_key: Option<Vec<u8>>,
67 private_key_path: Option<PathBuf>,
68 cert_serial_number: Option<String>,
69 environment: Environment,
70 platform_certificates: Vec<Vec<u8>>,
71 timeout: u64,
72 max_retries: u32,
73}
74
75impl WxPayConfigBuilder {
76 pub fn new() -> Self {
78 Self {
79 app_id: None,
80 merchant_id: None,
81 api_v3_key: None,
82 private_key: None,
83 private_key_path: None,
84 cert_serial_number: None,
85 environment: Environment::default(),
86 platform_certificates: Vec::new(),
87 timeout: 30,
88 max_retries: 3,
89 }
90 }
91
92 pub fn app_id(mut self, app_id: impl Into<String>) -> Self {
94 self.app_id = Some(app_id.into());
95 self
96 }
97
98 pub fn merchant_id(mut self, merchant_id: impl Into<String>) -> Self {
100 self.merchant_id = Some(merchant_id.into());
101 self
102 }
103
104 pub fn api_v3_key(mut self, api_v3_key: impl Into<String>) -> Self {
106 self.api_v3_key = Some(api_v3_key.into());
107 self
108 }
109
110 pub fn private_key(mut self, private_key: Vec<u8>) -> Self {
112 self.private_key = Some(private_key);
113 self
114 }
115
116 pub fn private_key_from_file(mut self, path: impl Into<PathBuf>) -> Self {
118 self.private_key_path = Some(path.into());
119 self
120 }
121
122 pub fn cert_serial_number(mut self, cert_serial_number: impl Into<String>) -> Self {
124 self.cert_serial_number = Some(cert_serial_number.into());
125 self
126 }
127
128 pub fn environment(mut self, environment: Environment) -> Self {
130 self.environment = environment;
131 self
132 }
133
134 pub fn platform_certificate(mut self, certificate: Vec<u8>) -> Self {
136 self.platform_certificates.push(certificate);
137 self
138 }
139
140 pub fn timeout(mut self, timeout: u64) -> Self {
142 self.timeout = timeout;
143 self
144 }
145
146 pub fn max_retries(mut self, max_retries: u32) -> Self {
148 self.max_retries = max_retries;
149 self
150 }
151
152 pub fn build(self) -> WxPayResult<WxPayConfig> {
154 let app_id = self
155 .app_id
156 .ok_or_else(|| WxPayError::missing_config("app_id"))?;
157
158 let merchant_id = self
159 .merchant_id
160 .ok_or_else(|| WxPayError::missing_config("merchant_id"))?;
161
162 let api_v3_key = self
163 .api_v3_key
164 .ok_or_else(|| WxPayError::missing_config("api_v3_key"))?;
165
166 if api_v3_key.len() != 32 {
168 return Err(WxPayError::invalid_parameter("api_v3_key 必须是 32 个字符"));
169 }
170
171 let private_key = match (self.private_key, self.private_key_path) {
172 (Some(key), _) => key,
173 (None, Some(path)) => std::fs::read(&path)
174 .map_err(|e| WxPayError::config(format!("读取私钥文件失败:{}", e)))?,
175 (None, None) => return Err(WxPayError::missing_config("private_key")),
176 };
177
178 let cert_serial_number = self
179 .cert_serial_number
180 .ok_or_else(|| WxPayError::missing_config("cert_serial_number"))?;
181
182 Ok(WxPayConfig {
183 app_id,
184 merchant_id,
185 api_v3_key,
186 private_key,
187 cert_serial_number,
188 environment: self.environment,
189 platform_certificates: self.platform_certificates,
190 timeout: self.timeout,
191 max_retries: self.max_retries,
192 })
193 }
194}
195
196impl Default for WxPayConfigBuilder {
197 fn default() -> Self {
198 Self::new()
199 }
200}
201
202impl WxPayConfig {
203 pub fn builder() -> WxPayConfigBuilder {
205 WxPayConfigBuilder::new()
206 }
207
208 pub fn base_url(&self) -> &str {
210 self.environment.base_url()
211 }
212
213 pub fn private_key_pem(&self) -> &[u8] {
215 &self.private_key
216 }
217}
218
219#[derive(Debug, Clone)]
221pub struct NotifyConfig {
222 pub api_v3_key: String,
224
225 pub cert_serial_number: String,
227
228 pub platform_certificate: Vec<u8>,
230}
231
232#[derive(Debug, Clone)]
234pub struct NotifyConfigBuilder {
235 api_v3_key: Option<String>,
236 cert_serial_number: Option<String>,
237 platform_certificate: Option<Vec<u8>>,
238}
239
240impl NotifyConfigBuilder {
241 pub fn new() -> Self {
243 Self {
244 api_v3_key: None,
245 cert_serial_number: None,
246 platform_certificate: None,
247 }
248 }
249
250 pub fn api_v3_key(mut self, api_v3_key: impl Into<String>) -> Self {
252 self.api_v3_key = Some(api_v3_key.into());
253 self
254 }
255
256 pub fn cert_serial_number(mut self, cert_serial_number: impl Into<String>) -> Self {
258 self.cert_serial_number = Some(cert_serial_number.into());
259 self
260 }
261
262 pub fn platform_certificate(mut self, certificate: Vec<u8>) -> Self {
264 self.platform_certificate = Some(certificate);
265 self
266 }
267
268 pub fn build(self) -> WxPayResult<NotifyConfig> {
270 let api_v3_key = self
271 .api_v3_key
272 .ok_or_else(|| WxPayError::missing_config("api_v3_key"))?;
273
274 let cert_serial_number = self
275 .cert_serial_number
276 .ok_or_else(|| WxPayError::missing_config("cert_serial_number"))?;
277
278 let platform_certificate = self
279 .platform_certificate
280 .ok_or_else(|| WxPayError::missing_config("platform_certificate"))?;
281
282 Ok(NotifyConfig {
283 api_v3_key,
284 cert_serial_number,
285 platform_certificate,
286 })
287 }
288}
289
290impl Default for NotifyConfigBuilder {
291 fn default() -> Self {
292 Self::new()
293 }
294}
295
296impl NotifyConfig {
297 pub fn builder() -> NotifyConfigBuilder {
299 NotifyConfigBuilder::new()
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn test_config_builder_success() {
309 let config = WxPayConfig::builder()
310 .app_id("wx88888888")
311 .merchant_id("1900000109")
312 .api_v3_key("abcdefghijklmnopqrstuvwxyz123456") .private_key(vec![1, 2, 3, 4])
314 .cert_serial_number("CERT123456")
315 .build();
316
317 assert!(config.is_ok());
318 let config = config.unwrap();
319 assert_eq!(config.app_id, "wx88888888");
320 assert_eq!(config.merchant_id, "1900000109");
321 assert_eq!(config.timeout, 30);
322 assert_eq!(config.max_retries, 3);
323 }
324
325 #[test]
326 fn test_config_builder_missing_app_id() {
327 let result = WxPayConfig::builder()
328 .merchant_id("1900000109")
329 .api_v3_key("abcdefghijklmnopqrstuvwxyz123456")
330 .private_key(vec![1, 2, 3, 4])
331 .cert_serial_number("CERT123456")
332 .build();
333
334 assert!(result.is_err());
335 match result.unwrap_err() {
336 WxPayError::MissingConfig { field } => assert_eq!(field, "app_id"),
337 _ => panic!("Expected MissingConfig error"),
338 }
339 }
340
341 #[test]
342 fn test_config_builder_invalid_api_v3_key() {
343 let result = WxPayConfig::builder()
344 .app_id("wx88888888")
345 .merchant_id("1900000109")
346 .api_v3_key("short_key") .private_key(vec![1, 2, 3, 4])
348 .cert_serial_number("CERT123456")
349 .build();
350
351 assert!(result.is_err());
352 match result.unwrap_err() {
353 WxPayError::InvalidParameter(_) => {}
354 _ => panic!("Expected InvalidParameter error"),
355 }
356 }
357
358 #[test]
359 fn test_environment_base_url() {
360 assert_eq!(
361 Environment::Production.base_url(),
362 "https://api.mch.weixin.qq.com"
363 );
364 assert_eq!(
365 Environment::Sandbox.base_url(),
366 "https://api.mch.weixin.qq.com"
367 );
368 }
369
370 #[test]
371 fn test_notify_config_builder() {
372 let config = NotifyConfig::builder()
373 .api_v3_key("abcdefghijklmnopqrstuvwxyz123456")
374 .cert_serial_number("CERT123456")
375 .platform_certificate(vec![1, 2, 3, 4])
376 .build();
377
378 assert!(config.is_ok());
379 }
380}