1use serde_json::Value;
6use thiserror::Error;
7
8pub type WxPayResult<T> = Result<T, WxPayError>;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum WxPayErrorKind {
14 InvalidParameter,
16 Authentication,
18 Signature,
20 ResourceNotFound,
22 RateLimited,
24 BusinessBlocked,
26 Internal,
28 Unknown,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum WxPayAlertLevel {
35 Low,
37 Medium,
39 High,
41 Critical,
43}
44
45impl WxPayAlertLevel {
46 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 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 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#[derive(Error, Debug)]
102pub enum WxPayError {
103 #[error("配置错误:{message}")]
106 ConfigError { message: String },
107
108 #[error("无效的私钥:{0}")]
110 InvalidPrivateKey(String),
111
112 #[error("无效的证书:{0}")]
114 InvalidCertificate(String),
115
116 #[error("缺少必填配置项:{field}")]
118 MissingConfig { field: String },
119
120 #[error("签名生成失败:{0}")]
123 SignError(String),
124
125 #[error("签名验证失败")]
127 SignatureVerificationFailed,
128
129 #[error("无效的签名格式:{0}")]
131 InvalidSignatureFormat(String),
132
133 #[error("加密失败:{0}")]
136 EncryptionError(String),
137
138 #[error("解密失败:{0}")]
140 DecryptionError(String),
141
142 #[error("无效的密钥:{0}")]
144 InvalidKey(String),
145
146 #[error("无效的密文格式:{0}")]
148 InvalidCiphertext(String),
149
150 #[error("证书下载失败:{0}")]
153 CertificateDownloadError(String),
154
155 #[error("证书解析失败:{0}")]
157 CertificateParseError(String),
158
159 #[error("证书已过期")]
161 CertificateExpired,
162
163 #[error("证书验证失败:{0}")]
165 CertificateVerificationError(String),
166
167 #[error("找不到匹配的证书:serial_number={0}")]
169 CertificateNotFound(String),
170
171 #[error("网络错误:{0}")]
174 NetworkError(#[from] reqwest::Error),
175
176 #[error("HTTP 请求构建失败:{0}")]
178 RequestBuildError(String),
179
180 #[error("HTTP 响应解析失败:{0}")]
182 ResponseParseError(String),
183
184 #[error("请求超时")]
186 Timeout,
187
188 #[error("API 错误:code={code}, message={message}")]
191 ApiError {
192 code: String,
194 message: String,
196 },
197
198 #[error("意外的 HTTP 状态码:{0}")]
200 UnexpectedStatusCode(u16),
201
202 #[error("业务错误:{0}")]
204 BusinessError(String),
205
206 #[error("通知签名验证失败")]
209 NotifySignatureVerificationFailed,
210
211 #[error("通知解密失败:{0}")]
213 NotifyDecryptionError(String),
214
215 #[error("无效的通知格式:{0}")]
217 InvalidNotifyFormat(String),
218
219 #[error("无效的通知类型:{0}")]
221 InvalidNotifyType(String),
222
223 #[error("JSON 错误:{0}")]
226 JsonError(#[from] serde_json::Error),
227
228 #[error("URL 编码错误:{0}")]
230 UrlEncodeError(String),
231
232 #[error("URL 解析错误:{0}")]
234 UrlParseError(#[from] url::ParseError),
235
236 #[error("内部错误:{0}")]
239 InternalError(String),
240
241 #[error("不支持的操作:{0}")]
243 UnsupportedOperation(String),
244
245 #[error("参数错误:{0}")]
247 InvalidParameter(String),
248}
249
250#[derive(Debug, Clone, serde::Deserialize)]
252pub struct ErrorResponse {
253 pub code: String,
255 pub message: String,
257 pub detail: Option<Value>,
259}
260
261impl WxPayError {
262 pub fn config(message: impl Into<String>) -> Self {
264 Self::ConfigError {
265 message: message.into(),
266 }
267 }
268
269 pub fn missing_config(field: impl Into<String>) -> Self {
271 Self::MissingConfig {
272 field: field.into(),
273 }
274 }
275
276 pub fn sign(message: impl Into<String>) -> Self {
278 Self::SignError(message.into())
279 }
280
281 pub fn encryption(message: impl Into<String>) -> Self {
283 Self::EncryptionError(message.into())
284 }
285
286 pub fn decryption(message: impl Into<String>) -> Self {
288 Self::DecryptionError(message.into())
289 }
290
291 pub fn certificate_parse(message: impl Into<String>) -> Self {
293 Self::CertificateParseError(message.into())
294 }
295
296 pub fn certificate_download(message: impl Into<String>) -> Self {
298 Self::CertificateDownloadError(message.into())
299 }
300
301 pub fn certificate_verification(message: impl Into<String>) -> Self {
303 Self::CertificateVerificationError(message.into())
304 }
305
306 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 pub fn internal(message: impl Into<String>) -> Self {
316 Self::InternalError(message.into())
317 }
318
319 pub fn invalid_parameter(message: impl Into<String>) -> Self {
321 Self::InvalidParameter(message.into())
322 }
323
324 pub fn business(message: impl Into<String>) -> Self {
326 Self::BusinessError(message.into())
327 }
328
329 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 pub fn api_code(&self) -> Option<&str> {
339 match self {
340 Self::ApiError { code, .. } => Some(code.as_str()),
341 _ => None,
342 }
343 }
344
345 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 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 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 pub fn is_network_error(&self) -> bool {
421 matches!(self, Self::NetworkError(_) | Self::Timeout)
422 }
423
424 pub fn is_api_error(&self) -> bool {
426 matches!(self, Self::ApiError { .. })
427 }
428
429 pub fn is_auth_error(&self) -> bool {
431 matches!(
432 self.api_kind(),
433 Some(WxPayErrorKind::Authentication | WxPayErrorKind::Signature)
434 )
435 }
436
437 pub fn is_signature_error(&self) -> bool {
439 matches!(
440 self,
441 Self::SignError(_)
442 | Self::SignatureVerificationFailed
443 | Self::InvalidSignatureFormat(_)
444 )
445 }
446
447 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
460impl From<base64::DecodeError> for WxPayError {
462 fn from(err: base64::DecodeError) -> Self {
463 Self::InternalError(format!("Base64 解码错误:{}", err))
464 }
465}
466
467impl From<rsa::Error> for WxPayError {
469 fn from(err: rsa::Error) -> Self {
470 Self::SignError(format!("RSA 错误:{}", err))
471 }
472}
473
474impl From<pkcs8::Error> for WxPayError {
476 fn from(err: pkcs8::Error) -> Self {
477 Self::InvalidPrivateKey(format!("PKCS8 错误:{}", err))
478 }
479}
480
481impl From<der::Error> for WxPayError {
483 fn from(err: der::Error) -> Self {
484 Self::CertificateParseError(format!("DER 解码错误:{}", err))
485 }
486}
487
488impl 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}