Skip to main content

wxpay_rs/
error.rs

1//! 微信支付 SDK 错误类型定义
2//!
3//! 定义了 SDK 中所有可能的错误类型,使用 thiserror 进行派生。
4
5use serde_json::Value;
6use thiserror::Error;
7
8/// 微信支付 SDK 结果类型别名
9pub type WxPayResult<T> = Result<T, WxPayError>;
10
11/// 微信支付错误码分类
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum WxPayErrorKind {
14    /// 参数错误
15    InvalidParameter,
16    /// 鉴权失败
17    Authentication,
18    /// 签名或验签异常
19    Signature,
20    /// 资源不存在
21    ResourceNotFound,
22    /// 超频/限流
23    RateLimited,
24    /// 业务受限
25    BusinessBlocked,
26    /// 系统内部错误
27    Internal,
28    /// 未知错误码
29    Unknown,
30}
31
32/// 告警级别(用于日志告警策略)
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum WxPayAlertLevel {
35    /// 观察
36    Low,
37    /// 注意
38    Medium,
39    /// 严重
40    High,
41    /// 紧急
42    Critical,
43}
44
45impl WxPayAlertLevel {
46    /// 转字符串
47    pub fn as_str(&self) -> &'static str {
48        match self {
49            Self::Low => "low",
50            Self::Medium => "medium",
51            Self::High => "high",
52            Self::Critical => "critical",
53        }
54    }
55}
56
57impl WxPayErrorKind {
58    /// 转字符串(用于告警/指标标签)
59    pub const fn as_str(&self) -> &'static str {
60        match self {
61            Self::InvalidParameter => "invalid_parameter",
62            Self::Authentication => "authentication",
63            Self::Signature => "signature",
64            Self::ResourceNotFound => "resource_not_found",
65            Self::RateLimited => "rate_limited",
66            Self::BusinessBlocked => "business_blocked",
67            Self::Internal => "internal",
68            Self::Unknown => "unknown",
69        }
70    }
71
72    /// 从微信错误码映射到统一错误分类
73    pub fn from_code(code: &str) -> Self {
74        match code {
75            "PARAM_ERROR" | "INVALID_REQUEST" | "INVALID_PARAMETER" => Self::InvalidParameter,
76            "NO_AUTH" | "SIGN_ERROR" | "INVALID_SIGN" | "PERMISSION_DENIED" | "AUTH_ERROR"
77            | "INVALID_CREDENTIAL" => Self::Authentication,
78            "SIGNATURE_ERROR" | "VERIFY_SIGNATURE_ERROR" => Self::Signature,
79            "ORDER_NOT_EXIST" | "NOT_FOUND" | "RESOURCE_NOT_FOUND" => Self::ResourceNotFound,
80            "FREQ_LIMIT" | "RATE_LIMIT" | "V2_API_DISABLED" => Self::RateLimited,
81            "NO_AUTHORITY" | "NOT_PERMIT" | "ILLEGAL_REQUEST" => Self::BusinessBlocked,
82            "SYSTEM_ERROR" | "SERVICE_UNAVAILABLE" => Self::Internal,
83            _ => Self::Unknown,
84        }
85    }
86}
87
88impl From<WxPayErrorKind> for WxPayAlertLevel {
89    fn from(kind: WxPayErrorKind) -> Self {
90        match kind {
91            WxPayErrorKind::Authentication | WxPayErrorKind::Signature => Self::Critical,
92            WxPayErrorKind::RateLimited | WxPayErrorKind::BusinessBlocked => Self::High,
93            WxPayErrorKind::Internal => Self::High,
94            WxPayErrorKind::ResourceNotFound => Self::Medium,
95            WxPayErrorKind::InvalidParameter | WxPayErrorKind::Unknown => Self::Low,
96        }
97    }
98}
99
100/// 微信支付 SDK 错误类型
101#[derive(Error, Debug)]
102pub enum WxPayError {
103    // ========== 配置错误 ==========
104    /// 配置错误
105    #[error("配置错误:{message}")]
106    ConfigError { message: String },
107
108    /// 无效的私钥
109    #[error("无效的私钥:{0}")]
110    InvalidPrivateKey(String),
111
112    /// 无效的证书
113    #[error("无效的证书:{0}")]
114    InvalidCertificate(String),
115
116    /// 缺少必填配置项
117    #[error("缺少必填配置项:{field}")]
118    MissingConfig { field: String },
119
120    // ========== 签名与验签错误 ==========
121    /// 签名生成失败
122    #[error("签名生成失败:{0}")]
123    SignError(String),
124
125    /// 签名验证失败
126    #[error("签名验证失败")]
127    SignatureVerificationFailed,
128
129    /// 无效的签名格式
130    #[error("无效的签名格式:{0}")]
131    InvalidSignatureFormat(String),
132
133    // ========== 加解密错误 ==========
134    /// 加密失败
135    #[error("加密失败:{0}")]
136    EncryptionError(String),
137
138    /// 解密失败
139    #[error("解密失败:{0}")]
140    DecryptionError(String),
141
142    /// 无效的密钥
143    #[error("无效的密钥:{0}")]
144    InvalidKey(String),
145
146    /// 无效的密文格式
147    #[error("无效的密文格式:{0}")]
148    InvalidCiphertext(String),
149
150    // ========== 证书错误 ==========
151    /// 证书下载失败
152    #[error("证书下载失败:{0}")]
153    CertificateDownloadError(String),
154
155    /// 证书解析失败
156    #[error("证书解析失败:{0}")]
157    CertificateParseError(String),
158
159    /// 证书已过期
160    #[error("证书已过期")]
161    CertificateExpired,
162
163    /// 证书验证失败
164    #[error("证书验证失败:{0}")]
165    CertificateVerificationError(String),
166
167    /// 找不到匹配的证书
168    #[error("找不到匹配的证书:serial_number={0}")]
169    CertificateNotFound(String),
170
171    // ========== HTTP 错误 ==========
172    /// 网络错误
173    #[error("网络错误:{0}")]
174    NetworkError(#[from] reqwest::Error),
175
176    /// HTTP 请求构建失败
177    #[error("HTTP 请求构建失败:{0}")]
178    RequestBuildError(String),
179
180    /// HTTP 响应解析失败
181    #[error("HTTP 响应解析失败:{0}")]
182    ResponseParseError(String),
183
184    /// 请求超时
185    #[error("请求超时")]
186    Timeout,
187
188    // ========== API 错误 ==========
189    /// 微信支付 API 错误
190    #[error("API 错误:code={code}, message={message}")]
191    ApiError {
192        /// 错误码
193        code: String,
194        /// 错误信息
195        message: String,
196    },
197
198    /// API 返回了意外的状态码
199    #[error("意外的 HTTP 状态码:{0}")]
200    UnexpectedStatusCode(u16),
201
202    /// 业务逻辑错误
203    #[error("业务错误:{0}")]
204    BusinessError(String),
205
206    // ========== 通知错误 ==========
207    /// 通知签名验证失败
208    #[error("通知签名验证失败")]
209    NotifySignatureVerificationFailed,
210
211    /// 通知解密失败
212    #[error("通知解密失败:{0}")]
213    NotifyDecryptionError(String),
214
215    /// 无效的通知格式
216    #[error("无效的通知格式:{0}")]
217    InvalidNotifyFormat(String),
218
219    /// 无效的通知类型
220    #[error("无效的通知类型:{0}")]
221    InvalidNotifyType(String),
222
223    // ========== 序列化错误 ==========
224    /// JSON 序列化/反序列化错误
225    #[error("JSON 错误:{0}")]
226    JsonError(#[from] serde_json::Error),
227
228    /// URL 编码错误
229    #[error("URL 编码错误:{0}")]
230    UrlEncodeError(String),
231
232    /// URL 解析错误
233    #[error("URL 解析错误:{0}")]
234    UrlParseError(#[from] url::ParseError),
235
236    // ========== 其他错误 ==========
237    /// 内部错误
238    #[error("内部错误:{0}")]
239    InternalError(String),
240
241    /// 不支持的操作
242    #[error("不支持的操作:{0}")]
243    UnsupportedOperation(String),
244
245    /// 参数错误
246    #[error("参数错误:{0}")]
247    InvalidParameter(String),
248}
249
250/// HTTP 错误响应
251#[derive(Debug, Clone, serde::Deserialize)]
252pub struct ErrorResponse {
253    /// 错误码
254    pub code: String,
255    /// 错误信息
256    pub message: String,
257    /// 透传错误明细
258    pub detail: Option<Value>,
259}
260
261impl WxPayError {
262    /// 创建配置错误
263    pub fn config(message: impl Into<String>) -> Self {
264        Self::ConfigError {
265            message: message.into(),
266        }
267    }
268
269    /// 创建缺少配置项错误
270    pub fn missing_config(field: impl Into<String>) -> Self {
271        Self::MissingConfig {
272            field: field.into(),
273        }
274    }
275
276    /// 创建签名错误
277    pub fn sign(message: impl Into<String>) -> Self {
278        Self::SignError(message.into())
279    }
280
281    /// 创建加密错误
282    pub fn encryption(message: impl Into<String>) -> Self {
283        Self::EncryptionError(message.into())
284    }
285
286    /// 创建解密错误
287    pub fn decryption(message: impl Into<String>) -> Self {
288        Self::DecryptionError(message.into())
289    }
290
291    /// 创建证书错误
292    pub fn certificate_parse(message: impl Into<String>) -> Self {
293        Self::CertificateParseError(message.into())
294    }
295
296    /// 创建证书下载错误
297    pub fn certificate_download(message: impl Into<String>) -> Self {
298        Self::CertificateDownloadError(message.into())
299    }
300
301    /// 创建证书验证错误
302    pub fn certificate_verification(message: impl Into<String>) -> Self {
303        Self::CertificateVerificationError(message.into())
304    }
305
306    /// 创建 API 错误
307    pub fn api(code: impl Into<String>, message: impl Into<String>) -> Self {
308        Self::ApiError {
309            code: code.into(),
310            message: message.into(),
311        }
312    }
313
314    /// 创建内部错误
315    pub fn internal(message: impl Into<String>) -> Self {
316        Self::InternalError(message.into())
317    }
318
319    /// 创建参数错误
320    pub fn invalid_parameter(message: impl Into<String>) -> Self {
321        Self::InvalidParameter(message.into())
322    }
323
324    /// 创建业务错误
325    pub fn business(message: impl Into<String>) -> Self {
326        Self::BusinessError(message.into())
327    }
328
329    /// 获取 API 错误分类
330    pub fn api_kind(&self) -> Option<WxPayErrorKind> {
331        match self {
332            Self::ApiError { code, .. } => Some(WxPayErrorKind::from_code(code)),
333            _ => None,
334        }
335    }
336
337    /// 获取 API 错误码
338    pub fn api_code(&self) -> Option<&str> {
339        match self {
340            Self::ApiError { code, .. } => Some(code.as_str()),
341            _ => None,
342        }
343    }
344
345    /// 告警级别(用于结构化日志告警策略)
346    pub fn alert_level(&self) -> WxPayAlertLevel {
347        match self {
348            Self::NetworkError(_) | Self::Timeout => WxPayAlertLevel::Critical,
349            Self::ApiError { code, .. } => WxPayErrorKind::from_code(code).into(),
350            Self::CertificateExpired
351            | Self::CertificateVerificationError(_)
352            | Self::CertificateDownloadError(_)
353            | Self::CertificateNotFound(_)
354            | Self::CertificateParseError(_)
355            | Self::SignatureVerificationFailed => WxPayAlertLevel::High,
356            Self::UnexpectedStatusCode(status) => {
357                if *status >= 500 {
358                    WxPayAlertLevel::High
359                } else {
360                    WxPayAlertLevel::Medium
361                }
362            }
363            Self::SignError(_)
364            | Self::InvalidSignatureFormat(_)
365            | Self::RequestBuildError(_)
366            | Self::ResponseParseError(_)
367            | Self::JsonError(_)
368            | Self::BusinessError(_) => WxPayAlertLevel::High,
369            Self::InternalError(_) | Self::EncryptionError(_) | Self::DecryptionError(_) => {
370                WxPayAlertLevel::Medium
371            }
372            _ => WxPayAlertLevel::Low,
373        }
374    }
375
376    /// 告警策略键(用于指标/告警策略路由)
377    pub fn alert_policy(&self) -> &'static str {
378        match self {
379            Self::ApiError { code, .. } => match WxPayErrorKind::from_code(code) {
380                WxPayErrorKind::Authentication => "security.auth",
381                WxPayErrorKind::Signature => "security.signature",
382                WxPayErrorKind::RateLimited => "business.ratelimit",
383                WxPayErrorKind::ResourceNotFound => "business.notfound",
384                WxPayErrorKind::BusinessBlocked => "business.blocked",
385                WxPayErrorKind::InvalidParameter => "params.invalid",
386                WxPayErrorKind::Internal => "system.internal",
387                WxPayErrorKind::Unknown => "unknown",
388            },
389            Self::NetworkError(_) | Self::Timeout => "network",
390            Self::SignatureVerificationFailed
391            | Self::SignError(_)
392            | Self::InvalidSignatureFormat(_) => "security.signature",
393            Self::CertificateExpired
394            | Self::CertificateVerificationError(_)
395            | Self::CertificateParseError(_)
396            | Self::CertificateDownloadError(_)
397            | Self::CertificateNotFound(_) => "certificate",
398            Self::UnexpectedStatusCode(status) if *status >= 500 => "system.internal",
399            Self::UnexpectedStatusCode(status) if *status >= 400 => "business.http",
400            Self::UnexpectedStatusCode(_) => "business.http",
401            Self::InternalError(_) => "system.internal",
402            _ => "unknown",
403        }
404    }
405
406    /// 是否建议重试
407    pub fn should_retry(&self) -> bool {
408        match self {
409            Self::NetworkError(_) | Self::Timeout => true,
410            Self::UnexpectedStatusCode(status) if *status >= 500 => true,
411            Self::ApiError { code, .. } => matches!(
412                WxPayErrorKind::from_code(code),
413                WxPayErrorKind::RateLimited | WxPayErrorKind::Internal
414            ),
415            _ => false,
416        }
417    }
418
419    /// 判断是否为网络错误
420    pub fn is_network_error(&self) -> bool {
421        matches!(self, Self::NetworkError(_) | Self::Timeout)
422    }
423
424    /// 判断是否为 API 错误
425    pub fn is_api_error(&self) -> bool {
426        matches!(self, Self::ApiError { .. })
427    }
428
429    /// 判断是否为鉴权相关错误
430    pub fn is_auth_error(&self) -> bool {
431        matches!(
432            self.api_kind(),
433            Some(WxPayErrorKind::Authentication | WxPayErrorKind::Signature)
434        )
435    }
436
437    /// 判断是否为签名/验签错误
438    pub fn is_signature_error(&self) -> bool {
439        matches!(
440            self,
441            Self::SignError(_)
442                | Self::SignatureVerificationFailed
443                | Self::InvalidSignatureFormat(_)
444        )
445    }
446
447    /// 判断是否为证书错误
448    pub fn is_certificate_error(&self) -> bool {
449        matches!(
450            self,
451            Self::CertificateExpired
452                | Self::CertificateNotFound(_)
453                | Self::CertificateParseError(_)
454                | Self::CertificateDownloadError(_)
455                | Self::CertificateVerificationError(_)
456        )
457    }
458}
459
460/// 从 base64 错误转换
461impl From<base64::DecodeError> for WxPayError {
462    fn from(err: base64::DecodeError) -> Self {
463        Self::InternalError(format!("Base64 解码错误:{}", err))
464    }
465}
466
467/// 从 RSA 错误转换
468impl From<rsa::Error> for WxPayError {
469    fn from(err: rsa::Error) -> Self {
470        Self::SignError(format!("RSA 错误:{}", err))
471    }
472}
473
474/// 从 PKCS 错误转换
475impl From<pkcs8::Error> for WxPayError {
476    fn from(err: pkcs8::Error) -> Self {
477        Self::InvalidPrivateKey(format!("PKCS8 错误:{}", err))
478    }
479}
480
481/// 从 DER 错误转换
482impl From<der::Error> for WxPayError {
483    fn from(err: der::Error) -> Self {
484        Self::CertificateParseError(format!("DER 解码错误:{}", err))
485    }
486}
487
488/// 从时间解析错误转换
489impl From<chrono::ParseError> for WxPayError {
490    fn from(err: chrono::ParseError) -> Self {
491        Self::InternalError(format!("时间解析错误:{}", err))
492    }
493}
494
495#[cfg(test)]
496mod tests {
497    use super::*;
498
499    #[test]
500    fn test_error_display() {
501        let err = WxPayError::config("missing app_id");
502        assert_eq!(err.to_string(), "配置错误:missing app_id");
503
504        let err = WxPayError::api("PARAM_ERROR", "参数错误");
505        assert_eq!(
506            err.to_string(),
507            "API 错误:code=PARAM_ERROR, message=参数错误"
508        );
509    }
510
511    #[test]
512    fn test_error_classification() {
513        let err = WxPayError::Timeout;
514        assert!(err.is_network_error());
515        assert!(!err.is_api_error());
516        assert!(matches!(err.alert_level(), WxPayAlertLevel::Critical));
517        assert_eq!(err.alert_policy(), "network");
518        assert!(err.should_retry());
519
520        let err = WxPayError::api("ERROR", "msg");
521        assert!(err.is_api_error());
522        assert!(!err.is_network_error());
523        assert_eq!(err.alert_policy(), "unknown");
524
525        let err = WxPayError::SignatureVerificationFailed;
526        assert!(err.is_signature_error());
527        assert_eq!(err.alert_policy(), "security.signature");
528
529        let err = WxPayError::CertificateExpired;
530        assert!(err.is_certificate_error());
531    }
532}