1use parking_lot::Mutex;
33use std::collections::HashMap;
34use std::sync::Arc;
35use thiserror::Error;
36
37#[derive(Debug, Error)]
43pub enum PayError {
44 #[error("支付配置错误: {0}")]
46 Config(String),
47 #[error("支付字段缺失: {0}")]
49 MissingField(String),
50 #[error("支付请求失败: {0}")]
52 RequestFailed(String),
53 #[error("HTTP 传输失败: {0}")]
55 HttpTransport(String),
56 #[error("序列化失败: {0}")]
58 Serialize(String),
59 #[error("签名验证失败: {0}")]
61 VerifyFailed(String),
62 #[error("退款失败: {0}")]
64 RefundFailed(String),
65 #[error("查询失败: {0}")]
67 QueryFailed(String),
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
76pub enum PayPlatform {
77 #[default]
79 Alipay,
80 WechatPay,
82 Other,
84}
85
86impl PayPlatform {
87 pub fn as_str(self) -> &'static str {
89 match self {
90 Self::Alipay => "alipay",
91 Self::WechatPay => "wechatpay",
92 Self::Other => "other",
93 }
94 }
95}
96
97impl std::fmt::Display for PayPlatform {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.write_str(self.as_str())
100 }
101}
102
103impl std::str::FromStr for PayPlatform {
104 type Err = PayError;
105
106 fn from_str(s: &str) -> Result<Self, Self::Err> {
107 match s.to_lowercase().as_str() {
108 "alipay" | "ali" => Ok(Self::Alipay),
109 "wechatpay" | "wechat" => Ok(Self::WechatPay),
110 "other" => Ok(Self::Other),
111 other => Err(PayError::Config(format!("未知支付平台: {other}"))),
112 }
113 }
114}
115
116#[derive(Debug, Clone, Default)]
150pub struct PayOrder {
151 pub out_trade_no: String,
153 pub total_amount: i64,
155 pub subject: String,
157 pub body: Option<String>,
159 pub notify_url: Option<String>,
161 pub return_url: Option<String>,
163 pub timeout_express: Option<i64>,
165 pub passback_params: Option<String>,
167 pub extra: serde_json::Value,
169}
170
171impl PayOrder {
172 pub fn new() -> Self {
174 Self::default()
175 }
176
177 pub fn out_trade_no(mut self, out_trade_no: impl Into<String>) -> Self {
179 self.out_trade_no = out_trade_no.into();
180 self
181 }
182
183 pub fn total_amount(mut self, total_amount: i64) -> Self {
185 self.total_amount = total_amount;
186 self
187 }
188
189 pub fn subject(mut self, subject: impl Into<String>) -> Self {
191 self.subject = subject.into();
192 self
193 }
194
195 pub fn body(mut self, body: impl Into<String>) -> Self {
197 self.body = Some(body.into());
198 self
199 }
200
201 pub fn notify_url(mut self, notify_url: impl Into<String>) -> Self {
203 self.notify_url = Some(notify_url.into());
204 self
205 }
206
207 pub fn return_url(mut self, return_url: impl Into<String>) -> Self {
209 self.return_url = Some(return_url.into());
210 self
211 }
212
213 pub fn timeout_express(mut self, timeout_express: i64) -> Self {
215 self.timeout_express = Some(timeout_express);
216 self
217 }
218
219 pub fn passback_params(mut self, passback_params: impl Into<String>) -> Self {
221 self.passback_params = Some(passback_params.into());
222 self
223 }
224
225 pub fn extra(mut self, extra: serde_json::Value) -> Self {
227 self.extra = extra;
228 self
229 }
230
231 pub fn validate(&self) -> Result<(), PayError> {
239 if self.out_trade_no.is_empty() {
240 return Err(PayError::MissingField("out_trade_no".into()));
241 }
242 if self.total_amount <= 0 {
243 return Err(PayError::MissingField("total_amount".into()));
244 }
245 if self.subject.is_empty() {
246 return Err(PayError::MissingField("subject".into()));
247 }
248 Ok(())
249 }
250}
251
252#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
260pub struct PayResult {
261 pub trade_no: String,
263 pub out_trade_no: String,
265 pub total_amount: i64,
267 pub trade_status: String,
269 pub raw: serde_json::Value,
271}
272
273#[derive(Debug, Clone, Default)]
281pub struct RefundOrder {
282 pub out_trade_no: String,
284 pub refund_amount: i64,
286 pub out_request_no: String,
288 pub reason: Option<String>,
290}
291
292impl RefundOrder {
293 pub fn new() -> Self {
295 Self::default()
296 }
297
298 pub fn out_trade_no(mut self, out_trade_no: impl Into<String>) -> Self {
300 self.out_trade_no = out_trade_no.into();
301 self
302 }
303
304 pub fn refund_amount(mut self, refund_amount: i64) -> Self {
306 self.refund_amount = refund_amount;
307 self
308 }
309
310 pub fn out_request_no(mut self, out_request_no: impl Into<String>) -> Self {
312 self.out_request_no = out_request_no.into();
313 self
314 }
315
316 pub fn reason(mut self, reason: impl Into<String>) -> Self {
318 self.reason = Some(reason.into());
319 self
320 }
321
322 pub fn validate(&self) -> Result<(), PayError> {
330 if self.out_trade_no.is_empty() {
331 return Err(PayError::MissingField("out_trade_no".into()));
332 }
333 if self.refund_amount <= 0 {
334 return Err(PayError::MissingField("refund_amount".into()));
335 }
336 if self.out_request_no.is_empty() {
337 return Err(PayError::MissingField("out_request_no".into()));
338 }
339 Ok(())
340 }
341}
342
343#[derive(Clone)]
351pub struct PayConfig {
352 pub platform: PayPlatform,
354 pub app_id: String,
356 pub merchant_private_key: String,
361 pub platform_public_key: String,
363 pub notify_url: String,
365 pub return_url: Option<String>,
367 pub sandbox: bool,
369 pub mode: String,
371}
372
373impl std::fmt::Debug for PayConfig {
374 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
375 f.debug_struct("PayConfig")
376 .field("platform", &self.platform)
377 .field("app_id", &self.app_id)
378 .field("merchant_private_key", &"<redacted>")
379 .field("platform_public_key", &self.platform_public_key)
380 .field("notify_url", &self.notify_url)
381 .field("return_url", &self.return_url)
382 .field("sandbox", &self.sandbox)
383 .field("mode", &self.mode)
384 .finish()
385 }
386}
387
388impl PayConfig {
389 pub fn new(platform: PayPlatform, app_id: impl Into<String>) -> Self {
396 Self {
397 platform,
398 app_id: app_id.into(),
399 merchant_private_key: String::new(),
400 platform_public_key: String::new(),
401 notify_url: String::new(),
402 return_url: None,
403 sandbox: false,
404 mode: "web".to_string(),
405 }
406 }
407
408 pub fn with_merchant_private_key(mut self, key: impl Into<String>) -> Self {
410 self.merchant_private_key = key.into();
411 self
412 }
413
414 pub fn with_platform_public_key(mut self, key: impl Into<String>) -> Self {
416 self.platform_public_key = key.into();
417 self
418 }
419
420 pub fn with_notify_url(mut self, notify_url: impl Into<String>) -> Self {
422 self.notify_url = notify_url.into();
423 self
424 }
425
426 pub fn with_return_url(mut self, return_url: impl Into<String>) -> Self {
428 self.return_url = Some(return_url.into());
429 self
430 }
431
432 pub fn with_sandbox(mut self, sandbox: bool) -> Self {
434 self.sandbox = sandbox;
435 self
436 }
437
438 pub fn with_mode(mut self, mode: impl Into<String>) -> Self {
440 self.mode = mode.into();
441 self
442 }
443
444 pub fn validate(&self) -> Result<(), PayError> {
453 if self.app_id.is_empty() {
454 return Err(PayError::Config("app_id".into()));
455 }
456 if self.merchant_private_key.is_empty() {
457 return Err(PayError::Config("merchant_private_key".into()));
458 }
459 if self.platform_public_key.is_empty() {
460 return Err(PayError::Config("platform_public_key".into()));
461 }
462 if self.notify_url.is_empty() {
463 return Err(PayError::Config("notify_url".into()));
464 }
465 Ok(())
466 }
467}
468
469pub trait PayProvider: Send + Sync {
490 fn pay(&self, order: PayOrder) -> Result<PayResult, PayError>;
500
501 fn query(&self, out_trade_no: &str) -> Result<PayResult, PayError>;
511
512 fn close(&self, out_trade_no: &str) -> Result<(), PayError>;
522
523 fn refund(&self, refund: RefundOrder) -> Result<(), PayError>;
533
534 fn verify_notify(&self, params: &serde_json::Value) -> Result<PayResult, PayError>;
544}
545
546#[derive(Debug, Default)]
574pub struct MemoryPayProvider {
575 orders: Arc<Mutex<HashMap<String, PayResult>>>,
577 refunds: Arc<Mutex<Vec<RefundOrder>>>,
579 query_result: Arc<Mutex<Option<PayResult>>>,
581}
582
583impl MemoryPayProvider {
584 pub fn new() -> Self {
586 Self::default()
587 }
588
589 pub fn orders(&self) -> Vec<PayResult> {
591 self.orders.lock().values().cloned().collect()
592 }
593
594 pub fn refunds(&self) -> Vec<RefundOrder> {
596 self.refunds.lock().clone()
597 }
598
599 pub fn set_query_result(&self, result: PayResult) {
604 *self.query_result.lock() = Some(result);
605 }
606
607 pub fn clear(&self) {
609 self.orders.lock().clear();
610 self.refunds.lock().clear();
611 *self.query_result.lock() = None;
612 }
613}
614
615impl PayProvider for MemoryPayProvider {
616 fn pay(&self, order: PayOrder) -> Result<PayResult, PayError> {
617 order.validate()?;
619
620 let mut orders = self.orders.lock();
622 if orders.contains_key(&order.out_trade_no) {
623 return Err(PayError::RequestFailed(format!(
624 "订单号已存在: {}",
625 order.out_trade_no
626 )));
627 }
628
629 let trade_no = format!("MEM{}", order.out_trade_no);
631 let raw = serde_json::json!({
632 "out_trade_no": order.out_trade_no,
633 "total_amount": order.total_amount,
634 "subject": order.subject,
635 "trade_no": trade_no,
636 });
637 let result = PayResult {
638 trade_no,
639 out_trade_no: order.out_trade_no.clone(),
640 total_amount: order.total_amount,
641 trade_status: "WAIT_BUYER_PAY".to_string(),
642 raw,
643 };
644
645 orders.insert(order.out_trade_no.clone(), result.clone());
647 Ok(result)
648 }
649
650 fn query(&self, out_trade_no: &str) -> Result<PayResult, PayError> {
651 if let Some(result) = self.query_result.lock().clone() {
653 return Ok(result);
654 }
655
656 self.orders
658 .lock()
659 .get(out_trade_no)
660 .cloned()
661 .ok_or_else(|| PayError::QueryFailed(format!("订单不存在: {out_trade_no}")))
662 }
663
664 fn close(&self, out_trade_no: &str) -> Result<(), PayError> {
665 let mut orders = self.orders.lock();
666 if let Some(result) = orders.get_mut(out_trade_no) {
667 result.trade_status = "CLOSED".to_string();
669 Ok(())
670 } else {
671 Err(PayError::RequestFailed(format!(
672 "订单不存在: {out_trade_no}"
673 )))
674 }
675 }
676
677 fn refund(&self, refund: RefundOrder) -> Result<(), PayError> {
678 refund.validate()?;
680
681 if !self.orders.lock().contains_key(&refund.out_trade_no) {
683 return Err(PayError::RefundFailed(format!(
684 "原订单不存在: {}",
685 refund.out_trade_no
686 )));
687 }
688
689 self.refunds.lock().push(refund);
691 Ok(())
692 }
693
694 fn verify_notify(&self, params: &serde_json::Value) -> Result<PayResult, PayError> {
695 let out_trade_no = params
697 .get("out_trade_no")
698 .and_then(|v| v.as_str())
699 .ok_or_else(|| PayError::VerifyFailed("缺少 out_trade_no".into()))?;
700
701 let trade_no = params
703 .get("trade_no")
704 .and_then(|v| v.as_str())
705 .unwrap_or("")
706 .to_string();
707 let total_amount = params
708 .get("total_amount")
709 .and_then(|v| v.as_i64())
710 .unwrap_or(0);
711 let trade_status = params
712 .get("trade_status")
713 .and_then(|v| v.as_str())
714 .unwrap_or("TRADE_SUCCESS")
715 .to_string();
716
717 Ok(PayResult {
718 trade_no,
719 out_trade_no: out_trade_no.to_string(),
720 total_amount,
721 trade_status,
722 raw: params.clone(),
723 })
724 }
725}
726
727pub trait PayHttpTransport: Send + Sync {
740 fn post_json(&self, url: &str, body: &str) -> Result<String, PayError>;
751
752 fn get(&self, url: &str) -> Result<String, PayError>;
762}
763
764#[derive(Debug, Default)]
773pub struct MemoryPayHttpTransport {
774 responses: Mutex<Vec<String>>,
776 requests: Mutex<Vec<(String, String, String)>>,
778}
779
780impl MemoryPayHttpTransport {
781 pub fn new() -> Self {
783 Self::default()
784 }
785
786 pub fn push_response(&self, response: impl Into<String>) {
788 self.responses.lock().push(response.into());
789 }
790
791 pub fn request_count(&self) -> usize {
793 self.requests.lock().len()
794 }
795
796 pub fn requests(&self) -> Vec<(String, String, String)> {
800 self.requests.lock().clone()
801 }
802
803 pub fn clear(&self) {
805 self.responses.lock().clear();
806 self.requests.lock().clear();
807 }
808
809 fn next_response(&self) -> Result<String, PayError> {
811 let mut responses = self.responses.lock();
812 if responses.is_empty() {
813 Err(PayError::HttpTransport("无可用预置响应".into()))
814 } else {
815 Ok(responses.remove(0))
816 }
817 }
818}
819
820impl PayHttpTransport for MemoryPayHttpTransport {
821 fn post_json(&self, url: &str, body: &str) -> Result<String, PayError> {
822 let response = self.next_response()?;
823 self.requests
824 .lock()
825 .push(("POST".to_string(), url.to_string(), body.to_string()));
826 Ok(response)
827 }
828
829 fn get(&self, url: &str) -> Result<String, PayError> {
830 let response = self.next_response()?;
831 self.requests
832 .lock()
833 .push(("GET".to_string(), url.to_string(), String::new()));
834 Ok(response)
835 }
836}
837
838#[cfg(test)]
843mod tests {
844 use super::*;
845
846 #[test]
852 fn test_pay_platform() {
853 assert_eq!(PayPlatform::Alipay.as_str(), "alipay");
855 assert_eq!(PayPlatform::WechatPay.as_str(), "wechatpay");
856 assert_eq!(PayPlatform::Other.as_str(), "other");
857
858 assert_eq!(PayPlatform::default(), PayPlatform::Alipay);
860
861 assert_eq!(format!("{}", PayPlatform::Alipay), "alipay");
863 assert_eq!(format!("{}", PayPlatform::WechatPay), "wechatpay");
864 assert_eq!(format!("{}", PayPlatform::Other), "other");
865
866 assert_eq!(
868 "alipay".parse::<PayPlatform>().unwrap(),
869 PayPlatform::Alipay
870 );
871 assert_eq!(
872 "wechatpay".parse::<PayPlatform>().unwrap(),
873 PayPlatform::WechatPay
874 );
875 assert_eq!("other".parse::<PayPlatform>().unwrap(), PayPlatform::Other);
876
877 assert_eq!("ali".parse::<PayPlatform>().unwrap(), PayPlatform::Alipay);
879 assert_eq!(
880 "wechat".parse::<PayPlatform>().unwrap(),
881 PayPlatform::WechatPay
882 );
883 assert_eq!(
884 "ALIPAY".parse::<PayPlatform>().unwrap(),
885 PayPlatform::Alipay
886 );
887
888 assert!("unknown".parse::<PayPlatform>().is_err());
890
891 let set = std::collections::HashSet::from([PayPlatform::Alipay, PayPlatform::WechatPay]);
893 assert!(set.contains(&PayPlatform::Alipay));
894 assert!(!set.contains(&PayPlatform::Other));
895 }
896
897 #[test]
903 fn test_pay_config_builder() {
904 let config = PayConfig::new(PayPlatform::Alipay, "2021001")
905 .with_merchant_private_key("MIIEvQIBADANB")
906 .with_platform_public_key("MIIBIjANBgkqh")
907 .with_notify_url("https://example.com/notify")
908 .with_return_url("https://example.com/return")
909 .with_sandbox(true)
910 .with_mode("app");
911
912 assert_eq!(config.platform, PayPlatform::Alipay);
913 assert_eq!(config.app_id, "2021001");
914 assert_eq!(config.merchant_private_key, "MIIEvQIBADANB");
915 assert_eq!(config.platform_public_key, "MIIBIjANBgkqh");
916 assert_eq!(config.notify_url, "https://example.com/notify");
917 assert_eq!(
918 config.return_url.as_deref(),
919 Some("https://example.com/return")
920 );
921 assert!(config.sandbox);
922 assert_eq!(config.mode, "app");
923
924 assert!(config.validate().is_ok());
926
927 let debug_output = format!("{:?}", config);
929 assert!(
930 !debug_output.contains("MIIEvQIBADANB"),
931 "P7-DES-01: Debug 输出泄漏商户私钥明文: {}",
932 debug_output
933 );
934 assert!(
935 debug_output.contains("<redacted>"),
936 "P7-DES-01: Debug 输出应包含脱敏标记 <redacted>"
937 );
938
939 let minimal = PayConfig::new(PayPlatform::WechatPay, "wx123");
941 assert_eq!(minimal.platform, PayPlatform::WechatPay);
942 assert_eq!(minimal.app_id, "wx123");
943 assert!(minimal.merchant_private_key.is_empty());
944 assert!(minimal.platform_public_key.is_empty());
945 assert!(minimal.notify_url.is_empty());
946 assert!(minimal.return_url.is_none());
947 assert!(!minimal.sandbox);
948 assert_eq!(minimal.mode, "web");
949
950 let bad = PayConfig::new(PayPlatform::Alipay, "");
952 let err = bad.validate().unwrap_err();
953 match err {
954 PayError::Config(field) => assert_eq!(field, "app_id"),
955 other => panic!("期望 Config, 实际 {other:?}"),
956 }
957
958 let bad = PayConfig::new(PayPlatform::Alipay, "app1");
960 let err = bad.validate().unwrap_err();
961 match err {
962 PayError::Config(field) => assert_eq!(field, "merchant_private_key"),
963 other => panic!("期望 Config, 实际 {other:?}"),
964 }
965
966 let bad = PayConfig::new(PayPlatform::Alipay, "app1")
968 .with_merchant_private_key("k1")
969 .with_platform_public_key("k2");
970 let err = bad.validate().unwrap_err();
971 match err {
972 PayError::Config(field) => assert_eq!(field, "notify_url"),
973 other => panic!("期望 Config, 实际 {other:?}"),
974 }
975 }
976
977 #[test]
983 fn test_pay_order_builder() {
984 let order = PayOrder::new()
985 .out_trade_no("202401010001")
986 .total_amount(8800)
987 .subject("鲜视达商品")
988 .body("新鲜蔬菜套餐")
989 .notify_url("https://example.com/notify")
990 .return_url("https://example.com/return")
991 .timeout_express(1800)
992 .passback_params("merchant_extra")
993 .extra(serde_json::json!({"channel": "alipay_app"}));
994
995 assert_eq!(order.out_trade_no, "202401010001");
996 assert_eq!(order.total_amount, 8800);
997 assert_eq!(order.subject, "鲜视达商品");
998 assert_eq!(order.body.as_deref(), Some("新鲜蔬菜套餐"));
999 assert_eq!(
1000 order.notify_url.as_deref(),
1001 Some("https://example.com/notify")
1002 );
1003 assert_eq!(
1004 order.return_url.as_deref(),
1005 Some("https://example.com/return")
1006 );
1007 assert_eq!(order.timeout_express, Some(1800));
1008 assert_eq!(order.passback_params.as_deref(), Some("merchant_extra"));
1009 assert_eq!(order.extra["channel"], "alipay_app");
1010
1011 assert!(order.validate().is_ok());
1013 }
1014
1015 #[test]
1017 fn test_pay_order_validate() {
1018 let order = PayOrder::new()
1020 .out_trade_no("202401010001")
1021 .total_amount(100)
1022 .subject("标题");
1023 assert!(order.validate().is_ok());
1024
1025 let order = PayOrder::new().total_amount(100).subject("标题");
1027 let err = order.validate().unwrap_err();
1028 match err {
1029 PayError::MissingField(field) => assert_eq!(field, "out_trade_no"),
1030 other => panic!("期望 MissingField, 实际 {other:?}"),
1031 }
1032
1033 let order = PayOrder::new()
1035 .out_trade_no("202401010001")
1036 .total_amount(0)
1037 .subject("标题");
1038 let err = order.validate().unwrap_err();
1039 match err {
1040 PayError::MissingField(field) => assert_eq!(field, "total_amount"),
1041 other => panic!("期望 MissingField, 实际 {other:?}"),
1042 }
1043
1044 let order = PayOrder::new()
1046 .out_trade_no("202401010001")
1047 .total_amount(-1)
1048 .subject("标题");
1049 let err = order.validate().unwrap_err();
1050 match err {
1051 PayError::MissingField(field) => assert_eq!(field, "total_amount"),
1052 other => panic!("期望 MissingField, 实际 {other:?}"),
1053 }
1054
1055 let order = PayOrder::new()
1057 .out_trade_no("202401010001")
1058 .total_amount(100);
1059 let err = order.validate().unwrap_err();
1060 match err {
1061 PayError::MissingField(field) => assert_eq!(field, "subject"),
1062 other => panic!("期望 MissingField, 实际 {other:?}"),
1063 }
1064
1065 let err = PayOrder::default().validate().unwrap_err();
1067 match err {
1068 PayError::MissingField(field) => assert_eq!(field, "out_trade_no"),
1069 other => panic!("期望 MissingField, 实际 {other:?}"),
1070 }
1071 }
1072
1073 #[test]
1079 fn test_refund_order_builder() {
1080 let refund = RefundOrder::new()
1081 .out_trade_no("202401010001")
1082 .refund_amount(5000)
1083 .out_request_no("R202401010001")
1084 .reason("用户申请退款");
1085
1086 assert_eq!(refund.out_trade_no, "202401010001");
1087 assert_eq!(refund.refund_amount, 5000);
1088 assert_eq!(refund.out_request_no, "R202401010001");
1089 assert_eq!(refund.reason.as_deref(), Some("用户申请退款"));
1090
1091 assert!(refund.validate().is_ok());
1093
1094 let refund = RefundOrder::new().refund_amount(5000).out_request_no("R1");
1096 let err = refund.validate().unwrap_err();
1097 match err {
1098 PayError::MissingField(field) => assert_eq!(field, "out_trade_no"),
1099 other => panic!("期望 MissingField, 实际 {other:?}"),
1100 }
1101
1102 let refund = RefundOrder::new()
1104 .out_trade_no("T1")
1105 .refund_amount(0)
1106 .out_request_no("R1");
1107 let err = refund.validate().unwrap_err();
1108 match err {
1109 PayError::MissingField(field) => assert_eq!(field, "refund_amount"),
1110 other => panic!("期望 MissingField, 实际 {other:?}"),
1111 }
1112
1113 let refund = RefundOrder::new().out_trade_no("T1").refund_amount(100);
1115 let err = refund.validate().unwrap_err();
1116 match err {
1117 PayError::MissingField(field) => assert_eq!(field, "out_request_no"),
1118 other => panic!("期望 MissingField, 实际 {other:?}"),
1119 }
1120 }
1121
1122 #[test]
1128 fn test_pay_result_default() {
1129 let result = PayResult::default();
1130 assert!(result.trade_no.is_empty());
1131 assert!(result.out_trade_no.is_empty());
1132 assert_eq!(result.total_amount, 0);
1133 assert!(result.trade_status.is_empty());
1134 assert!(result.raw.is_null());
1135
1136 let result = PayResult {
1138 trade_no: "2024MEM001".to_string(),
1139 out_trade_no: "ORD001".to_string(),
1140 total_amount: 8800,
1141 trade_status: "TRADE_SUCCESS".to_string(),
1142 raw: serde_json::json!({"code": "00"}),
1143 };
1144 let json = serde_json::to_string(&result).expect("序列化失败");
1145 let back: PayResult = serde_json::from_str(&json).expect("反序列化失败");
1146 assert_eq!(back.trade_no, "2024MEM001");
1147 assert_eq!(back.out_trade_no, "ORD001");
1148 assert_eq!(back.total_amount, 8800);
1149 assert_eq!(back.trade_status, "TRADE_SUCCESS");
1150 assert_eq!(back.raw["code"], "00");
1151 }
1152
1153 #[test]
1159 fn test_memory_pay_provider_pay() {
1160 let provider = MemoryPayProvider::new();
1161 let order = PayOrder::new()
1162 .out_trade_no("202401010001")
1163 .total_amount(8800)
1164 .subject("鲜视达商品")
1165 .body("新鲜蔬菜");
1166
1167 let result = provider.pay(order).expect("支付应成功");
1168
1169 assert_eq!(result.out_trade_no, "202401010001");
1171 assert_eq!(result.total_amount, 8800);
1172 assert_eq!(result.trade_status, "WAIT_BUYER_PAY");
1173 assert!(result.trade_no.starts_with("MEM"));
1174 assert_eq!(result.trade_no, "MEM202401010001");
1175 assert_eq!(result.raw["out_trade_no"], "202401010001");
1177 assert_eq!(result.raw["total_amount"], 8800);
1178 assert_eq!(result.raw["subject"], "鲜视达商品");
1179
1180 assert_eq!(provider.orders().len(), 1);
1182
1183 let dup = PayOrder::new()
1185 .out_trade_no("202401010001")
1186 .total_amount(100)
1187 .subject("重复订单");
1188 let err = provider.pay(dup).unwrap_err();
1189 match err {
1190 PayError::RequestFailed(msg) => assert!(msg.contains("订单号已存在")),
1191 other => panic!("期望 RequestFailed, 实际 {other:?}"),
1192 }
1193 assert_eq!(provider.orders().len(), 1);
1195 }
1196
1197 #[test]
1199 fn test_memory_pay_provider_query() {
1200 let provider = MemoryPayProvider::new();
1201
1202 let err = provider.query("NOT_EXIST").unwrap_err();
1204 match err {
1205 PayError::QueryFailed(msg) => assert!(msg.contains("订单不存在")),
1206 other => panic!("期望 QueryFailed, 实际 {other:?}"),
1207 }
1208
1209 let order = PayOrder::new()
1211 .out_trade_no("Q001")
1212 .total_amount(1000)
1213 .subject("查询测试");
1214 provider.pay(order).expect("支付应成功");
1215
1216 let result = provider.query("Q001").expect("查询应成功");
1217 assert_eq!(result.out_trade_no, "Q001");
1218 assert_eq!(result.total_amount, 1000);
1219 assert_eq!(result.trade_no, "MEMQ001");
1220
1221 let preset = PayResult {
1223 trade_no: "PRESET001".to_string(),
1224 out_trade_no: "ANY".to_string(),
1225 total_amount: 9999,
1226 trade_status: "TRADE_SUCCESS".to_string(),
1227 raw: serde_json::json!({"preset": true}),
1228 };
1229 provider.set_query_result(preset);
1230
1231 let result = provider.query("NOT_EXIST").expect("应返回预置结果");
1233 assert_eq!(result.trade_no, "PRESET001");
1234 assert_eq!(result.total_amount, 9999);
1235 assert_eq!(result.trade_status, "TRADE_SUCCESS");
1236 assert_eq!(result.raw["preset"], true);
1237
1238 provider.clear();
1240 let err = provider.query("NOT_EXIST").unwrap_err();
1241 match err {
1242 PayError::QueryFailed(_) => {}
1243 other => panic!("期望 QueryFailed, 实际 {other:?}"),
1244 }
1245 }
1246
1247 #[test]
1249 fn test_memory_pay_provider_close() {
1250 let provider = MemoryPayProvider::new();
1251
1252 let err = provider.close("NOT_EXIST").unwrap_err();
1254 match err {
1255 PayError::RequestFailed(msg) => assert!(msg.contains("订单不存在")),
1256 other => panic!("期望 RequestFailed, 实际 {other:?}"),
1257 }
1258
1259 let order = PayOrder::new()
1261 .out_trade_no("C001")
1262 .total_amount(500)
1263 .subject("关闭测试");
1264 provider.pay(order).expect("支付应成功");
1265
1266 provider.close("C001").expect("关闭应成功");
1268
1269 let result = provider.query("C001").expect("查询应成功");
1271 assert_eq!(result.trade_status, "CLOSED");
1272 }
1273
1274 #[test]
1276 fn test_memory_pay_provider_refund() {
1277 let provider = MemoryPayProvider::new();
1278
1279 let refund = RefundOrder::new()
1281 .out_trade_no("NOT_EXIST")
1282 .refund_amount(100)
1283 .out_request_no("R001");
1284 let err = provider.refund(refund).unwrap_err();
1285 match err {
1286 PayError::RefundFailed(msg) => assert!(msg.contains("原订单不存在")),
1287 other => panic!("期望 RefundFailed, 实际 {other:?}"),
1288 }
1289 assert_eq!(provider.refunds().len(), 0);
1290
1291 let order = PayOrder::new()
1293 .out_trade_no("R001")
1294 .total_amount(1000)
1295 .subject("退款测试");
1296 provider.pay(order).expect("支付应成功");
1297
1298 let refund = RefundOrder::new()
1299 .out_trade_no("R001")
1300 .refund_amount(500)
1301 .out_request_no("RR001")
1302 .reason("商品缺货");
1303 provider.refund(refund).expect("退款应成功");
1304
1305 assert_eq!(provider.refunds().len(), 1);
1307 let stored = &provider.refunds()[0];
1308 assert_eq!(stored.out_trade_no, "R001");
1309 assert_eq!(stored.refund_amount, 500);
1310 assert_eq!(stored.out_request_no, "RR001");
1311 assert_eq!(stored.reason.as_deref(), Some("商品缺货"));
1312
1313 let bad = RefundOrder::new()
1315 .out_trade_no("R001")
1316 .refund_amount(0) .out_request_no("RR002");
1318 let err = provider.refund(bad).unwrap_err();
1319 match err {
1320 PayError::MissingField(field) => assert_eq!(field, "refund_amount"),
1321 other => panic!("期望 MissingField, 实际 {other:?}"),
1322 }
1323 assert_eq!(provider.refunds().len(), 1);
1325 }
1326
1327 #[test]
1329 fn test_memory_pay_provider_verify_notify() {
1330 let provider = MemoryPayProvider::new();
1331
1332 let params = serde_json::json!({
1334 "out_trade_no": "CB001",
1335 "trade_no": "2024ALIPAY001",
1336 "total_amount": 8800,
1337 "trade_status": "TRADE_SUCCESS",
1338 "buyer_id": "2088000000000001"
1339 });
1340 let result = provider.verify_notify(¶ms).expect("验证应成功");
1341 assert_eq!(result.out_trade_no, "CB001");
1342 assert_eq!(result.trade_no, "2024ALIPAY001");
1343 assert_eq!(result.total_amount, 8800);
1344 assert_eq!(result.trade_status, "TRADE_SUCCESS");
1345 assert_eq!(result.raw["buyer_id"], "2088000000000001");
1347
1348 let params = serde_json::json!({
1350 "trade_no": "2024ALIPAY001",
1351 "total_amount": 8800
1352 });
1353 let err = provider.verify_notify(¶ms).unwrap_err();
1354 match err {
1355 PayError::VerifyFailed(msg) => assert!(msg.contains("out_trade_no")),
1356 other => panic!("期望 VerifyFailed, 实际 {other:?}"),
1357 }
1358
1359 let params = serde_json::json!({
1361 "out_trade_no": "CB002",
1362 "trade_no": "T002"
1363 });
1364 let result = provider.verify_notify(¶ms).expect("验证应成功");
1365 assert_eq!(result.out_trade_no, "CB002");
1366 assert_eq!(result.trade_no, "T002");
1367 assert_eq!(result.total_amount, 0); assert_eq!(result.trade_status, "TRADE_SUCCESS"); }
1370
1371 #[test]
1373 fn test_memory_pay_provider_missing_fields() {
1374 let provider = MemoryPayProvider::new();
1375
1376 let order = PayOrder::new().total_amount(100).subject("标题");
1378 let err = provider.pay(order).unwrap_err();
1379 match err {
1380 PayError::MissingField(field) => assert_eq!(field, "out_trade_no"),
1381 other => panic!("期望 MissingField, 实际 {other:?}"),
1382 }
1383 assert_eq!(provider.orders().len(), 0);
1384
1385 let order = PayOrder::new()
1387 .out_trade_no("M001")
1388 .total_amount(0)
1389 .subject("标题");
1390 let err = provider.pay(order).unwrap_err();
1391 match err {
1392 PayError::MissingField(field) => assert_eq!(field, "total_amount"),
1393 other => panic!("期望 MissingField, 实际 {other:?}"),
1394 }
1395 assert_eq!(provider.orders().len(), 0);
1396
1397 let order = PayOrder::new().out_trade_no("M002").total_amount(100);
1399 let err = provider.pay(order).unwrap_err();
1400 match err {
1401 PayError::MissingField(field) => assert_eq!(field, "subject"),
1402 other => panic!("期望 MissingField, 实际 {other:?}"),
1403 }
1404 assert_eq!(provider.orders().len(), 0);
1405
1406 let err = provider.pay(PayOrder::default()).unwrap_err();
1408 match err {
1409 PayError::MissingField(field) => assert_eq!(field, "out_trade_no"),
1410 other => panic!("期望 MissingField, 实际 {other:?}"),
1411 }
1412 assert_eq!(provider.orders().len(), 0);
1413 }
1414
1415 #[test]
1421 fn test_memory_pay_http_transport_post_json() {
1422 let transport = MemoryPayHttpTransport::new();
1423
1424 let err = transport
1426 .post_json("https://api.example.com/pay", "{}")
1427 .unwrap_err();
1428 match err {
1429 PayError::HttpTransport(msg) => assert!(msg.contains("无可用预置响应")),
1430 other => panic!("期望 HttpTransport, 实际 {other:?}"),
1431 }
1432 assert_eq!(transport.request_count(), 0);
1433
1434 transport.push_response(r#"{"code":"00","msg":"success"}"#);
1436 let resp = transport
1437 .post_json("https://api.example.com/pay", r#"{"out_trade_no":"P001"}"#)
1438 .expect("应返回预置响应");
1439 assert_eq!(resp, r#"{"code":"00","msg":"success"}"#);
1440 assert_eq!(transport.request_count(), 1);
1441
1442 let requests = transport.requests();
1444 assert_eq!(requests.len(), 1);
1445 assert_eq!(requests[0].0, "POST");
1446 assert_eq!(requests[0].1, "https://api.example.com/pay");
1447 assert_eq!(requests[0].2, r#"{"out_trade_no":"P001"}"#);
1448
1449 let err = transport.post_json("url", "{}").unwrap_err();
1451 match err {
1452 PayError::HttpTransport(_) => {}
1453 other => panic!("期望 HttpTransport, 实际 {other:?}"),
1454 }
1455 assert_eq!(transport.request_count(), 1);
1457 }
1458
1459 #[test]
1461 fn test_memory_pay_http_transport_get() {
1462 let transport = MemoryPayHttpTransport::new();
1463
1464 let err = transport.get("https://api.example.com/query").unwrap_err();
1466 match err {
1467 PayError::HttpTransport(msg) => assert!(msg.contains("无可用预置响应")),
1468 other => panic!("期望 HttpTransport, 实际 {other:?}"),
1469 }
1470 assert_eq!(transport.request_count(), 0);
1471
1472 transport.push_response(r#"{"trade_status":"TRADE_SUCCESS"}"#);
1474 let resp = transport
1475 .get("https://api.example.com/query?out_trade_no=Q001")
1476 .expect("应返回预置响应");
1477 assert_eq!(resp, r#"{"trade_status":"TRADE_SUCCESS"}"#);
1478 assert_eq!(transport.request_count(), 1);
1479
1480 let requests = transport.requests();
1482 assert_eq!(requests.len(), 1);
1483 assert_eq!(requests[0].0, "GET");
1484 assert_eq!(
1485 requests[0].1,
1486 "https://api.example.com/query?out_trade_no=Q001"
1487 );
1488 assert_eq!(requests[0].2, "");
1489
1490 transport.clear();
1492 assert_eq!(transport.request_count(), 0);
1493 assert!(transport.get("url").is_err());
1494 }
1495
1496 #[test]
1498 fn test_memory_pay_http_transport_queue() {
1499 let transport = MemoryPayHttpTransport::new();
1500
1501 transport.push_response("resp1");
1503 transport.push_response("resp2");
1504 transport.push_response("resp3");
1505
1506 let r1 = transport.post_json("url1", "body1").expect("应返回 resp1");
1508 assert_eq!(r1, "resp1");
1509
1510 let r2 = transport.get("url2").expect("应返回 resp2");
1511 assert_eq!(r2, "resp2");
1512
1513 let r3 = transport.post_json("url3", "body3").expect("应返回 resp3");
1514 assert_eq!(r3, "resp3");
1515
1516 assert!(transport.post_json("url4", "body4").is_err());
1518 assert!(transport.get("url4").is_err());
1519
1520 assert_eq!(transport.request_count(), 3);
1522 let requests = transport.requests();
1523 assert_eq!(
1524 requests[0],
1525 ("POST".to_string(), "url1".to_string(), "body1".to_string())
1526 );
1527 assert_eq!(
1528 requests[1],
1529 ("GET".to_string(), "url2".to_string(), String::new())
1530 );
1531 assert_eq!(
1532 requests[2],
1533 ("POST".to_string(), "url3".to_string(), "body3".to_string())
1534 );
1535 }
1536}