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(Debug, Clone)]
351pub struct PayConfig {
352 pub platform: PayPlatform,
354 pub app_id: String,
356 pub merchant_private_key: String,
358 pub platform_public_key: String,
360 pub notify_url: String,
362 pub return_url: Option<String>,
364 pub sandbox: bool,
366 pub mode: String,
368}
369
370impl PayConfig {
371 pub fn new(platform: PayPlatform, app_id: impl Into<String>) -> Self {
378 Self {
379 platform,
380 app_id: app_id.into(),
381 merchant_private_key: String::new(),
382 platform_public_key: String::new(),
383 notify_url: String::new(),
384 return_url: None,
385 sandbox: false,
386 mode: "web".to_string(),
387 }
388 }
389
390 pub fn with_merchant_private_key(mut self, key: impl Into<String>) -> Self {
392 self.merchant_private_key = key.into();
393 self
394 }
395
396 pub fn with_platform_public_key(mut self, key: impl Into<String>) -> Self {
398 self.platform_public_key = key.into();
399 self
400 }
401
402 pub fn with_notify_url(mut self, notify_url: impl Into<String>) -> Self {
404 self.notify_url = notify_url.into();
405 self
406 }
407
408 pub fn with_return_url(mut self, return_url: impl Into<String>) -> Self {
410 self.return_url = Some(return_url.into());
411 self
412 }
413
414 pub fn with_sandbox(mut self, sandbox: bool) -> Self {
416 self.sandbox = sandbox;
417 self
418 }
419
420 pub fn with_mode(mut self, mode: impl Into<String>) -> Self {
422 self.mode = mode.into();
423 self
424 }
425
426 pub fn validate(&self) -> Result<(), PayError> {
435 if self.app_id.is_empty() {
436 return Err(PayError::Config("app_id".into()));
437 }
438 if self.merchant_private_key.is_empty() {
439 return Err(PayError::Config("merchant_private_key".into()));
440 }
441 if self.platform_public_key.is_empty() {
442 return Err(PayError::Config("platform_public_key".into()));
443 }
444 if self.notify_url.is_empty() {
445 return Err(PayError::Config("notify_url".into()));
446 }
447 Ok(())
448 }
449}
450
451pub trait PayProvider: Send + Sync {
472 fn pay(&self, order: PayOrder) -> Result<PayResult, PayError>;
482
483 fn query(&self, out_trade_no: &str) -> Result<PayResult, PayError>;
493
494 fn close(&self, out_trade_no: &str) -> Result<(), PayError>;
504
505 fn refund(&self, refund: RefundOrder) -> Result<(), PayError>;
515
516 fn verify_notify(&self, params: &serde_json::Value) -> Result<PayResult, PayError>;
526}
527
528#[derive(Debug, Default)]
556pub struct MemoryPayProvider {
557 orders: Arc<Mutex<HashMap<String, PayResult>>>,
559 refunds: Arc<Mutex<Vec<RefundOrder>>>,
561 query_result: Arc<Mutex<Option<PayResult>>>,
563}
564
565impl MemoryPayProvider {
566 pub fn new() -> Self {
568 Self::default()
569 }
570
571 pub fn orders(&self) -> Vec<PayResult> {
573 self.orders.lock().values().cloned().collect()
574 }
575
576 pub fn refunds(&self) -> Vec<RefundOrder> {
578 self.refunds.lock().clone()
579 }
580
581 pub fn set_query_result(&self, result: PayResult) {
586 *self.query_result.lock() = Some(result);
587 }
588
589 pub fn clear(&self) {
591 self.orders.lock().clear();
592 self.refunds.lock().clear();
593 *self.query_result.lock() = None;
594 }
595}
596
597impl PayProvider for MemoryPayProvider {
598 fn pay(&self, order: PayOrder) -> Result<PayResult, PayError> {
599 order.validate()?;
601
602 let mut orders = self.orders.lock();
604 if orders.contains_key(&order.out_trade_no) {
605 return Err(PayError::RequestFailed(format!(
606 "订单号已存在: {}",
607 order.out_trade_no
608 )));
609 }
610
611 let trade_no = format!("MEM{}", order.out_trade_no);
613 let raw = serde_json::json!({
614 "out_trade_no": order.out_trade_no,
615 "total_amount": order.total_amount,
616 "subject": order.subject,
617 "trade_no": trade_no,
618 });
619 let result = PayResult {
620 trade_no,
621 out_trade_no: order.out_trade_no.clone(),
622 total_amount: order.total_amount,
623 trade_status: "WAIT_BUYER_PAY".to_string(),
624 raw,
625 };
626
627 orders.insert(order.out_trade_no.clone(), result.clone());
629 Ok(result)
630 }
631
632 fn query(&self, out_trade_no: &str) -> Result<PayResult, PayError> {
633 if let Some(result) = self.query_result.lock().clone() {
635 return Ok(result);
636 }
637
638 self.orders
640 .lock()
641 .get(out_trade_no)
642 .cloned()
643 .ok_or_else(|| PayError::QueryFailed(format!("订单不存在: {out_trade_no}")))
644 }
645
646 fn close(&self, out_trade_no: &str) -> Result<(), PayError> {
647 let mut orders = self.orders.lock();
648 if let Some(result) = orders.get_mut(out_trade_no) {
649 result.trade_status = "CLOSED".to_string();
651 Ok(())
652 } else {
653 Err(PayError::RequestFailed(format!(
654 "订单不存在: {out_trade_no}"
655 )))
656 }
657 }
658
659 fn refund(&self, refund: RefundOrder) -> Result<(), PayError> {
660 refund.validate()?;
662
663 if !self.orders.lock().contains_key(&refund.out_trade_no) {
665 return Err(PayError::RefundFailed(format!(
666 "原订单不存在: {}",
667 refund.out_trade_no
668 )));
669 }
670
671 self.refunds.lock().push(refund);
673 Ok(())
674 }
675
676 fn verify_notify(&self, params: &serde_json::Value) -> Result<PayResult, PayError> {
677 let out_trade_no = params
679 .get("out_trade_no")
680 .and_then(|v| v.as_str())
681 .ok_or_else(|| PayError::VerifyFailed("缺少 out_trade_no".into()))?;
682
683 let trade_no = params
685 .get("trade_no")
686 .and_then(|v| v.as_str())
687 .unwrap_or("")
688 .to_string();
689 let total_amount = params
690 .get("total_amount")
691 .and_then(|v| v.as_i64())
692 .unwrap_or(0);
693 let trade_status = params
694 .get("trade_status")
695 .and_then(|v| v.as_str())
696 .unwrap_or("TRADE_SUCCESS")
697 .to_string();
698
699 Ok(PayResult {
700 trade_no,
701 out_trade_no: out_trade_no.to_string(),
702 total_amount,
703 trade_status,
704 raw: params.clone(),
705 })
706 }
707}
708
709pub trait PayHttpTransport: Send + Sync {
722 fn post_json(&self, url: &str, body: &str) -> Result<String, PayError>;
733
734 fn get(&self, url: &str) -> Result<String, PayError>;
744}
745
746#[derive(Debug, Default)]
755pub struct MemoryPayHttpTransport {
756 responses: Mutex<Vec<String>>,
758 requests: Mutex<Vec<(String, String, String)>>,
760}
761
762impl MemoryPayHttpTransport {
763 pub fn new() -> Self {
765 Self::default()
766 }
767
768 pub fn push_response(&self, response: impl Into<String>) {
770 self.responses.lock().push(response.into());
771 }
772
773 pub fn request_count(&self) -> usize {
775 self.requests.lock().len()
776 }
777
778 pub fn requests(&self) -> Vec<(String, String, String)> {
782 self.requests.lock().clone()
783 }
784
785 pub fn clear(&self) {
787 self.responses.lock().clear();
788 self.requests.lock().clear();
789 }
790
791 fn next_response(&self) -> Result<String, PayError> {
793 let mut responses = self.responses.lock();
794 if responses.is_empty() {
795 Err(PayError::HttpTransport("无可用预置响应".into()))
796 } else {
797 Ok(responses.remove(0))
798 }
799 }
800}
801
802impl PayHttpTransport for MemoryPayHttpTransport {
803 fn post_json(&self, url: &str, body: &str) -> Result<String, PayError> {
804 let response = self.next_response()?;
805 self.requests
806 .lock()
807 .push(("POST".to_string(), url.to_string(), body.to_string()));
808 Ok(response)
809 }
810
811 fn get(&self, url: &str) -> Result<String, PayError> {
812 let response = self.next_response()?;
813 self.requests
814 .lock()
815 .push(("GET".to_string(), url.to_string(), String::new()));
816 Ok(response)
817 }
818}
819
820#[cfg(test)]
825mod tests {
826 use super::*;
827
828 #[test]
834 fn test_pay_platform() {
835 assert_eq!(PayPlatform::Alipay.as_str(), "alipay");
837 assert_eq!(PayPlatform::WechatPay.as_str(), "wechatpay");
838 assert_eq!(PayPlatform::Other.as_str(), "other");
839
840 assert_eq!(PayPlatform::default(), PayPlatform::Alipay);
842
843 assert_eq!(format!("{}", PayPlatform::Alipay), "alipay");
845 assert_eq!(format!("{}", PayPlatform::WechatPay), "wechatpay");
846 assert_eq!(format!("{}", PayPlatform::Other), "other");
847
848 assert_eq!(
850 "alipay".parse::<PayPlatform>().unwrap(),
851 PayPlatform::Alipay
852 );
853 assert_eq!(
854 "wechatpay".parse::<PayPlatform>().unwrap(),
855 PayPlatform::WechatPay
856 );
857 assert_eq!("other".parse::<PayPlatform>().unwrap(), PayPlatform::Other);
858
859 assert_eq!("ali".parse::<PayPlatform>().unwrap(), PayPlatform::Alipay);
861 assert_eq!(
862 "wechat".parse::<PayPlatform>().unwrap(),
863 PayPlatform::WechatPay
864 );
865 assert_eq!(
866 "ALIPAY".parse::<PayPlatform>().unwrap(),
867 PayPlatform::Alipay
868 );
869
870 assert!("unknown".parse::<PayPlatform>().is_err());
872
873 let set = std::collections::HashSet::from([PayPlatform::Alipay, PayPlatform::WechatPay]);
875 assert!(set.contains(&PayPlatform::Alipay));
876 assert!(!set.contains(&PayPlatform::Other));
877 }
878
879 #[test]
885 fn test_pay_config_builder() {
886 let config = PayConfig::new(PayPlatform::Alipay, "2021001")
887 .with_merchant_private_key("MIIEvQIBADANB")
888 .with_platform_public_key("MIIBIjANBgkqh")
889 .with_notify_url("https://example.com/notify")
890 .with_return_url("https://example.com/return")
891 .with_sandbox(true)
892 .with_mode("app");
893
894 assert_eq!(config.platform, PayPlatform::Alipay);
895 assert_eq!(config.app_id, "2021001");
896 assert_eq!(config.merchant_private_key, "MIIEvQIBADANB");
897 assert_eq!(config.platform_public_key, "MIIBIjANBgkqh");
898 assert_eq!(config.notify_url, "https://example.com/notify");
899 assert_eq!(
900 config.return_url.as_deref(),
901 Some("https://example.com/return")
902 );
903 assert!(config.sandbox);
904 assert_eq!(config.mode, "app");
905
906 assert!(config.validate().is_ok());
908
909 let minimal = PayConfig::new(PayPlatform::WechatPay, "wx123");
911 assert_eq!(minimal.platform, PayPlatform::WechatPay);
912 assert_eq!(minimal.app_id, "wx123");
913 assert!(minimal.merchant_private_key.is_empty());
914 assert!(minimal.platform_public_key.is_empty());
915 assert!(minimal.notify_url.is_empty());
916 assert!(minimal.return_url.is_none());
917 assert!(!minimal.sandbox);
918 assert_eq!(minimal.mode, "web");
919
920 let bad = PayConfig::new(PayPlatform::Alipay, "");
922 let err = bad.validate().unwrap_err();
923 match err {
924 PayError::Config(field) => assert_eq!(field, "app_id"),
925 other => panic!("期望 Config, 实际 {other:?}"),
926 }
927
928 let bad = PayConfig::new(PayPlatform::Alipay, "app1");
930 let err = bad.validate().unwrap_err();
931 match err {
932 PayError::Config(field) => assert_eq!(field, "merchant_private_key"),
933 other => panic!("期望 Config, 实际 {other:?}"),
934 }
935
936 let bad = PayConfig::new(PayPlatform::Alipay, "app1")
938 .with_merchant_private_key("k1")
939 .with_platform_public_key("k2");
940 let err = bad.validate().unwrap_err();
941 match err {
942 PayError::Config(field) => assert_eq!(field, "notify_url"),
943 other => panic!("期望 Config, 实际 {other:?}"),
944 }
945 }
946
947 #[test]
953 fn test_pay_order_builder() {
954 let order = PayOrder::new()
955 .out_trade_no("202401010001")
956 .total_amount(8800)
957 .subject("鲜视达商品")
958 .body("新鲜蔬菜套餐")
959 .notify_url("https://example.com/notify")
960 .return_url("https://example.com/return")
961 .timeout_express(1800)
962 .passback_params("merchant_extra")
963 .extra(serde_json::json!({"channel": "alipay_app"}));
964
965 assert_eq!(order.out_trade_no, "202401010001");
966 assert_eq!(order.total_amount, 8800);
967 assert_eq!(order.subject, "鲜视达商品");
968 assert_eq!(order.body.as_deref(), Some("新鲜蔬菜套餐"));
969 assert_eq!(
970 order.notify_url.as_deref(),
971 Some("https://example.com/notify")
972 );
973 assert_eq!(
974 order.return_url.as_deref(),
975 Some("https://example.com/return")
976 );
977 assert_eq!(order.timeout_express, Some(1800));
978 assert_eq!(order.passback_params.as_deref(), Some("merchant_extra"));
979 assert_eq!(order.extra["channel"], "alipay_app");
980
981 assert!(order.validate().is_ok());
983 }
984
985 #[test]
987 fn test_pay_order_validate() {
988 let order = PayOrder::new()
990 .out_trade_no("202401010001")
991 .total_amount(100)
992 .subject("标题");
993 assert!(order.validate().is_ok());
994
995 let order = PayOrder::new().total_amount(100).subject("标题");
997 let err = order.validate().unwrap_err();
998 match err {
999 PayError::MissingField(field) => assert_eq!(field, "out_trade_no"),
1000 other => panic!("期望 MissingField, 实际 {other:?}"),
1001 }
1002
1003 let order = PayOrder::new()
1005 .out_trade_no("202401010001")
1006 .total_amount(0)
1007 .subject("标题");
1008 let err = order.validate().unwrap_err();
1009 match err {
1010 PayError::MissingField(field) => assert_eq!(field, "total_amount"),
1011 other => panic!("期望 MissingField, 实际 {other:?}"),
1012 }
1013
1014 let order = PayOrder::new()
1016 .out_trade_no("202401010001")
1017 .total_amount(-1)
1018 .subject("标题");
1019 let err = order.validate().unwrap_err();
1020 match err {
1021 PayError::MissingField(field) => assert_eq!(field, "total_amount"),
1022 other => panic!("期望 MissingField, 实际 {other:?}"),
1023 }
1024
1025 let order = PayOrder::new()
1027 .out_trade_no("202401010001")
1028 .total_amount(100);
1029 let err = order.validate().unwrap_err();
1030 match err {
1031 PayError::MissingField(field) => assert_eq!(field, "subject"),
1032 other => panic!("期望 MissingField, 实际 {other:?}"),
1033 }
1034
1035 let err = PayOrder::default().validate().unwrap_err();
1037 match err {
1038 PayError::MissingField(field) => assert_eq!(field, "out_trade_no"),
1039 other => panic!("期望 MissingField, 实际 {other:?}"),
1040 }
1041 }
1042
1043 #[test]
1049 fn test_refund_order_builder() {
1050 let refund = RefundOrder::new()
1051 .out_trade_no("202401010001")
1052 .refund_amount(5000)
1053 .out_request_no("R202401010001")
1054 .reason("用户申请退款");
1055
1056 assert_eq!(refund.out_trade_no, "202401010001");
1057 assert_eq!(refund.refund_amount, 5000);
1058 assert_eq!(refund.out_request_no, "R202401010001");
1059 assert_eq!(refund.reason.as_deref(), Some("用户申请退款"));
1060
1061 assert!(refund.validate().is_ok());
1063
1064 let refund = RefundOrder::new().refund_amount(5000).out_request_no("R1");
1066 let err = refund.validate().unwrap_err();
1067 match err {
1068 PayError::MissingField(field) => assert_eq!(field, "out_trade_no"),
1069 other => panic!("期望 MissingField, 实际 {other:?}"),
1070 }
1071
1072 let refund = RefundOrder::new()
1074 .out_trade_no("T1")
1075 .refund_amount(0)
1076 .out_request_no("R1");
1077 let err = refund.validate().unwrap_err();
1078 match err {
1079 PayError::MissingField(field) => assert_eq!(field, "refund_amount"),
1080 other => panic!("期望 MissingField, 实际 {other:?}"),
1081 }
1082
1083 let refund = RefundOrder::new().out_trade_no("T1").refund_amount(100);
1085 let err = refund.validate().unwrap_err();
1086 match err {
1087 PayError::MissingField(field) => assert_eq!(field, "out_request_no"),
1088 other => panic!("期望 MissingField, 实际 {other:?}"),
1089 }
1090 }
1091
1092 #[test]
1098 fn test_pay_result_default() {
1099 let result = PayResult::default();
1100 assert!(result.trade_no.is_empty());
1101 assert!(result.out_trade_no.is_empty());
1102 assert_eq!(result.total_amount, 0);
1103 assert!(result.trade_status.is_empty());
1104 assert!(result.raw.is_null());
1105
1106 let result = PayResult {
1108 trade_no: "2024MEM001".to_string(),
1109 out_trade_no: "ORD001".to_string(),
1110 total_amount: 8800,
1111 trade_status: "TRADE_SUCCESS".to_string(),
1112 raw: serde_json::json!({"code": "00"}),
1113 };
1114 let json = serde_json::to_string(&result).expect("序列化失败");
1115 let back: PayResult = serde_json::from_str(&json).expect("反序列化失败");
1116 assert_eq!(back.trade_no, "2024MEM001");
1117 assert_eq!(back.out_trade_no, "ORD001");
1118 assert_eq!(back.total_amount, 8800);
1119 assert_eq!(back.trade_status, "TRADE_SUCCESS");
1120 assert_eq!(back.raw["code"], "00");
1121 }
1122
1123 #[test]
1129 fn test_memory_pay_provider_pay() {
1130 let provider = MemoryPayProvider::new();
1131 let order = PayOrder::new()
1132 .out_trade_no("202401010001")
1133 .total_amount(8800)
1134 .subject("鲜视达商品")
1135 .body("新鲜蔬菜");
1136
1137 let result = provider.pay(order).expect("支付应成功");
1138
1139 assert_eq!(result.out_trade_no, "202401010001");
1141 assert_eq!(result.total_amount, 8800);
1142 assert_eq!(result.trade_status, "WAIT_BUYER_PAY");
1143 assert!(result.trade_no.starts_with("MEM"));
1144 assert_eq!(result.trade_no, "MEM202401010001");
1145 assert_eq!(result.raw["out_trade_no"], "202401010001");
1147 assert_eq!(result.raw["total_amount"], 8800);
1148 assert_eq!(result.raw["subject"], "鲜视达商品");
1149
1150 assert_eq!(provider.orders().len(), 1);
1152
1153 let dup = PayOrder::new()
1155 .out_trade_no("202401010001")
1156 .total_amount(100)
1157 .subject("重复订单");
1158 let err = provider.pay(dup).unwrap_err();
1159 match err {
1160 PayError::RequestFailed(msg) => assert!(msg.contains("订单号已存在")),
1161 other => panic!("期望 RequestFailed, 实际 {other:?}"),
1162 }
1163 assert_eq!(provider.orders().len(), 1);
1165 }
1166
1167 #[test]
1169 fn test_memory_pay_provider_query() {
1170 let provider = MemoryPayProvider::new();
1171
1172 let err = provider.query("NOT_EXIST").unwrap_err();
1174 match err {
1175 PayError::QueryFailed(msg) => assert!(msg.contains("订单不存在")),
1176 other => panic!("期望 QueryFailed, 实际 {other:?}"),
1177 }
1178
1179 let order = PayOrder::new()
1181 .out_trade_no("Q001")
1182 .total_amount(1000)
1183 .subject("查询测试");
1184 provider.pay(order).expect("支付应成功");
1185
1186 let result = provider.query("Q001").expect("查询应成功");
1187 assert_eq!(result.out_trade_no, "Q001");
1188 assert_eq!(result.total_amount, 1000);
1189 assert_eq!(result.trade_no, "MEMQ001");
1190
1191 let preset = PayResult {
1193 trade_no: "PRESET001".to_string(),
1194 out_trade_no: "ANY".to_string(),
1195 total_amount: 9999,
1196 trade_status: "TRADE_SUCCESS".to_string(),
1197 raw: serde_json::json!({"preset": true}),
1198 };
1199 provider.set_query_result(preset);
1200
1201 let result = provider.query("NOT_EXIST").expect("应返回预置结果");
1203 assert_eq!(result.trade_no, "PRESET001");
1204 assert_eq!(result.total_amount, 9999);
1205 assert_eq!(result.trade_status, "TRADE_SUCCESS");
1206 assert_eq!(result.raw["preset"], true);
1207
1208 provider.clear();
1210 let err = provider.query("NOT_EXIST").unwrap_err();
1211 match err {
1212 PayError::QueryFailed(_) => {}
1213 other => panic!("期望 QueryFailed, 实际 {other:?}"),
1214 }
1215 }
1216
1217 #[test]
1219 fn test_memory_pay_provider_close() {
1220 let provider = MemoryPayProvider::new();
1221
1222 let err = provider.close("NOT_EXIST").unwrap_err();
1224 match err {
1225 PayError::RequestFailed(msg) => assert!(msg.contains("订单不存在")),
1226 other => panic!("期望 RequestFailed, 实际 {other:?}"),
1227 }
1228
1229 let order = PayOrder::new()
1231 .out_trade_no("C001")
1232 .total_amount(500)
1233 .subject("关闭测试");
1234 provider.pay(order).expect("支付应成功");
1235
1236 provider.close("C001").expect("关闭应成功");
1238
1239 let result = provider.query("C001").expect("查询应成功");
1241 assert_eq!(result.trade_status, "CLOSED");
1242 }
1243
1244 #[test]
1246 fn test_memory_pay_provider_refund() {
1247 let provider = MemoryPayProvider::new();
1248
1249 let refund = RefundOrder::new()
1251 .out_trade_no("NOT_EXIST")
1252 .refund_amount(100)
1253 .out_request_no("R001");
1254 let err = provider.refund(refund).unwrap_err();
1255 match err {
1256 PayError::RefundFailed(msg) => assert!(msg.contains("原订单不存在")),
1257 other => panic!("期望 RefundFailed, 实际 {other:?}"),
1258 }
1259 assert_eq!(provider.refunds().len(), 0);
1260
1261 let order = PayOrder::new()
1263 .out_trade_no("R001")
1264 .total_amount(1000)
1265 .subject("退款测试");
1266 provider.pay(order).expect("支付应成功");
1267
1268 let refund = RefundOrder::new()
1269 .out_trade_no("R001")
1270 .refund_amount(500)
1271 .out_request_no("RR001")
1272 .reason("商品缺货");
1273 provider.refund(refund).expect("退款应成功");
1274
1275 assert_eq!(provider.refunds().len(), 1);
1277 let stored = &provider.refunds()[0];
1278 assert_eq!(stored.out_trade_no, "R001");
1279 assert_eq!(stored.refund_amount, 500);
1280 assert_eq!(stored.out_request_no, "RR001");
1281 assert_eq!(stored.reason.as_deref(), Some("商品缺货"));
1282
1283 let bad = RefundOrder::new()
1285 .out_trade_no("R001")
1286 .refund_amount(0) .out_request_no("RR002");
1288 let err = provider.refund(bad).unwrap_err();
1289 match err {
1290 PayError::MissingField(field) => assert_eq!(field, "refund_amount"),
1291 other => panic!("期望 MissingField, 实际 {other:?}"),
1292 }
1293 assert_eq!(provider.refunds().len(), 1);
1295 }
1296
1297 #[test]
1299 fn test_memory_pay_provider_verify_notify() {
1300 let provider = MemoryPayProvider::new();
1301
1302 let params = serde_json::json!({
1304 "out_trade_no": "CB001",
1305 "trade_no": "2024ALIPAY001",
1306 "total_amount": 8800,
1307 "trade_status": "TRADE_SUCCESS",
1308 "buyer_id": "2088000000000001"
1309 });
1310 let result = provider.verify_notify(¶ms).expect("验证应成功");
1311 assert_eq!(result.out_trade_no, "CB001");
1312 assert_eq!(result.trade_no, "2024ALIPAY001");
1313 assert_eq!(result.total_amount, 8800);
1314 assert_eq!(result.trade_status, "TRADE_SUCCESS");
1315 assert_eq!(result.raw["buyer_id"], "2088000000000001");
1317
1318 let params = serde_json::json!({
1320 "trade_no": "2024ALIPAY001",
1321 "total_amount": 8800
1322 });
1323 let err = provider.verify_notify(¶ms).unwrap_err();
1324 match err {
1325 PayError::VerifyFailed(msg) => assert!(msg.contains("out_trade_no")),
1326 other => panic!("期望 VerifyFailed, 实际 {other:?}"),
1327 }
1328
1329 let params = serde_json::json!({
1331 "out_trade_no": "CB002",
1332 "trade_no": "T002"
1333 });
1334 let result = provider.verify_notify(¶ms).expect("验证应成功");
1335 assert_eq!(result.out_trade_no, "CB002");
1336 assert_eq!(result.trade_no, "T002");
1337 assert_eq!(result.total_amount, 0); assert_eq!(result.trade_status, "TRADE_SUCCESS"); }
1340
1341 #[test]
1343 fn test_memory_pay_provider_missing_fields() {
1344 let provider = MemoryPayProvider::new();
1345
1346 let order = PayOrder::new().total_amount(100).subject("标题");
1348 let err = provider.pay(order).unwrap_err();
1349 match err {
1350 PayError::MissingField(field) => assert_eq!(field, "out_trade_no"),
1351 other => panic!("期望 MissingField, 实际 {other:?}"),
1352 }
1353 assert_eq!(provider.orders().len(), 0);
1354
1355 let order = PayOrder::new()
1357 .out_trade_no("M001")
1358 .total_amount(0)
1359 .subject("标题");
1360 let err = provider.pay(order).unwrap_err();
1361 match err {
1362 PayError::MissingField(field) => assert_eq!(field, "total_amount"),
1363 other => panic!("期望 MissingField, 实际 {other:?}"),
1364 }
1365 assert_eq!(provider.orders().len(), 0);
1366
1367 let order = PayOrder::new().out_trade_no("M002").total_amount(100);
1369 let err = provider.pay(order).unwrap_err();
1370 match err {
1371 PayError::MissingField(field) => assert_eq!(field, "subject"),
1372 other => panic!("期望 MissingField, 实际 {other:?}"),
1373 }
1374 assert_eq!(provider.orders().len(), 0);
1375
1376 let err = provider.pay(PayOrder::default()).unwrap_err();
1378 match err {
1379 PayError::MissingField(field) => assert_eq!(field, "out_trade_no"),
1380 other => panic!("期望 MissingField, 实际 {other:?}"),
1381 }
1382 assert_eq!(provider.orders().len(), 0);
1383 }
1384
1385 #[test]
1391 fn test_memory_pay_http_transport_post_json() {
1392 let transport = MemoryPayHttpTransport::new();
1393
1394 let err = transport
1396 .post_json("https://api.example.com/pay", "{}")
1397 .unwrap_err();
1398 match err {
1399 PayError::HttpTransport(msg) => assert!(msg.contains("无可用预置响应")),
1400 other => panic!("期望 HttpTransport, 实际 {other:?}"),
1401 }
1402 assert_eq!(transport.request_count(), 0);
1403
1404 transport.push_response(r#"{"code":"00","msg":"success"}"#);
1406 let resp = transport
1407 .post_json("https://api.example.com/pay", r#"{"out_trade_no":"P001"}"#)
1408 .expect("应返回预置响应");
1409 assert_eq!(resp, r#"{"code":"00","msg":"success"}"#);
1410 assert_eq!(transport.request_count(), 1);
1411
1412 let requests = transport.requests();
1414 assert_eq!(requests.len(), 1);
1415 assert_eq!(requests[0].0, "POST");
1416 assert_eq!(requests[0].1, "https://api.example.com/pay");
1417 assert_eq!(requests[0].2, r#"{"out_trade_no":"P001"}"#);
1418
1419 let err = transport.post_json("url", "{}").unwrap_err();
1421 match err {
1422 PayError::HttpTransport(_) => {}
1423 other => panic!("期望 HttpTransport, 实际 {other:?}"),
1424 }
1425 assert_eq!(transport.request_count(), 1);
1427 }
1428
1429 #[test]
1431 fn test_memory_pay_http_transport_get() {
1432 let transport = MemoryPayHttpTransport::new();
1433
1434 let err = transport.get("https://api.example.com/query").unwrap_err();
1436 match err {
1437 PayError::HttpTransport(msg) => assert!(msg.contains("无可用预置响应")),
1438 other => panic!("期望 HttpTransport, 实际 {other:?}"),
1439 }
1440 assert_eq!(transport.request_count(), 0);
1441
1442 transport.push_response(r#"{"trade_status":"TRADE_SUCCESS"}"#);
1444 let resp = transport
1445 .get("https://api.example.com/query?out_trade_no=Q001")
1446 .expect("应返回预置响应");
1447 assert_eq!(resp, r#"{"trade_status":"TRADE_SUCCESS"}"#);
1448 assert_eq!(transport.request_count(), 1);
1449
1450 let requests = transport.requests();
1452 assert_eq!(requests.len(), 1);
1453 assert_eq!(requests[0].0, "GET");
1454 assert_eq!(
1455 requests[0].1,
1456 "https://api.example.com/query?out_trade_no=Q001"
1457 );
1458 assert_eq!(requests[0].2, "");
1459
1460 transport.clear();
1462 assert_eq!(transport.request_count(), 0);
1463 assert!(transport.get("url").is_err());
1464 }
1465
1466 #[test]
1468 fn test_memory_pay_http_transport_queue() {
1469 let transport = MemoryPayHttpTransport::new();
1470
1471 transport.push_response("resp1");
1473 transport.push_response("resp2");
1474 transport.push_response("resp3");
1475
1476 let r1 = transport.post_json("url1", "body1").expect("应返回 resp1");
1478 assert_eq!(r1, "resp1");
1479
1480 let r2 = transport.get("url2").expect("应返回 resp2");
1481 assert_eq!(r2, "resp2");
1482
1483 let r3 = transport.post_json("url3", "body3").expect("应返回 resp3");
1484 assert_eq!(r3, "resp3");
1485
1486 assert!(transport.post_json("url4", "body4").is_err());
1488 assert!(transport.get("url4").is_err());
1489
1490 assert_eq!(transport.request_count(), 3);
1492 let requests = transport.requests();
1493 assert_eq!(
1494 requests[0],
1495 ("POST".to_string(), "url1".to_string(), "body1".to_string())
1496 );
1497 assert_eq!(
1498 requests[1],
1499 ("GET".to_string(), "url2".to_string(), String::new())
1500 );
1501 assert_eq!(
1502 requests[2],
1503 ("POST".to_string(), "url3".to_string(), "body3".to_string())
1504 );
1505 }
1506}