Skip to main content

wxpay_rs/
config.rs

1//! 微信支付配置模块
2//!
3//! 定义了微信支付 SDK 的配置结构体和构建器。
4
5use std::path::PathBuf;
6
7use crate::error::{WxPayError, WxPayResult};
8
9/// 微信支付 API 环境
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum Environment {
12    /// 正式环境
13    #[default]
14    Production,
15    /// 沙箱环境
16    Sandbox,
17}
18
19impl Environment {
20    /// 获取 API 基础 URL
21    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/// 微信支付配置
30#[derive(Debug, Clone)]
31pub struct WxPayConfig {
32    /// 应用 ID
33    pub app_id: String,
34
35    /// 商户号
36    pub merchant_id: String,
37
38    /// API v3 密钥
39    pub api_v3_key: String,
40
41    /// 商户私钥(PEM 格式)
42    pub private_key: Vec<u8>,
43
44    /// 商户证书序列号
45    pub cert_serial_number: String,
46
47    /// 环境
48    pub environment: Environment,
49
50    /// 微信支付平台证书(可选,用于验签)
51    pub platform_certificates: Vec<Vec<u8>>,
52
53    /// HTTP 超时时间(秒)
54    pub timeout: u64,
55
56    /// 最大重试次数
57    pub max_retries: u32,
58}
59
60/// 微信支付配置构建器
61#[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    /// 创建新的配置构建器
77    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    /// 设置应用 ID
93    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    /// 设置商户号
99    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    /// 设置 API v3 密钥
105    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    /// 设置商户私钥(PEM 格式字节)
111    pub fn private_key(mut self, private_key: Vec<u8>) -> Self {
112        self.private_key = Some(private_key);
113        self
114    }
115
116    /// 从文件加载商户私钥
117    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    /// 设置商户证书序列号
123    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    /// 设置环境
129    pub fn environment(mut self, environment: Environment) -> Self {
130        self.environment = environment;
131        self
132    }
133
134    /// 添加微信支付平台证书
135    pub fn platform_certificate(mut self, certificate: Vec<u8>) -> Self {
136        self.platform_certificates.push(certificate);
137        self
138    }
139
140    /// 设置 HTTP 超时时间(秒)
141    pub fn timeout(mut self, timeout: u64) -> Self {
142        self.timeout = timeout;
143        self
144    }
145
146    /// 设置最大重试次数
147    pub fn max_retries(mut self, max_retries: u32) -> Self {
148        self.max_retries = max_retries;
149        self
150    }
151
152    /// 构建配置
153    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        // 验证 API v3 密钥长度
167        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    /// 创建配置构建器
204    pub fn builder() -> WxPayConfigBuilder {
205        WxPayConfigBuilder::new()
206    }
207
208    /// 获取 API 基础 URL
209    pub fn base_url(&self) -> &str {
210        self.environment.base_url()
211    }
212
213    /// 获取商户私钥(PEM 格式)
214    pub fn private_key_pem(&self) -> &[u8] {
215        &self.private_key
216    }
217}
218
219/// 微信支付通知配置
220#[derive(Debug, Clone)]
221pub struct NotifyConfig {
222    /// API v3 密钥(用于解密通知数据)
223    pub api_v3_key: String,
224
225    /// 平台证书序列号
226    pub cert_serial_number: String,
227
228    /// 平台证书(用于验证通知签名)
229    pub platform_certificate: Vec<u8>,
230}
231
232/// 微信支付通知配置构建器
233#[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    /// 创建新的通知配置构建器
242    pub fn new() -> Self {
243        Self {
244            api_v3_key: None,
245            cert_serial_number: None,
246            platform_certificate: None,
247        }
248    }
249
250    /// 设置 API v3 密钥
251    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    /// 设置平台证书序列号
257    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    /// 设置平台证书
263    pub fn platform_certificate(mut self, certificate: Vec<u8>) -> Self {
264        self.platform_certificate = Some(certificate);
265        self
266    }
267
268    /// 构建通知配置
269    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    /// 创建通知配置构建器
298    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") // 32 chars
313            .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") // Too short
347            .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}