Skip to main content

sz_rust_core/
pay.rs

1//! Pay 模块 — 支付聚合抽象层(对齐 PHP `yansongda/pay`)
2//!
3//! 提供统一的支付抽象,支持多平台(支付宝、微信支付等)扩展。
4//!
5//! ## PHP 对齐
6//!
7//! ### 核心 API 映射
8//!
9//! | PHP 方法 | Rust 方法 | 说明 |
10//! |---------|-----------|------|
11//! | `Pay::alipay()->app($order)` | [`PayProvider::pay`] | 发起支付 |
12//! | `Pay::alipay()->find($order)` | [`PayProvider::query`] | 查询订单 |
13//! | `Pay::alipay()->close($order)` | [`PayProvider::close`] | 关闭订单 |
14//! | `Pay::alipay()->refund($order)` | [`PayProvider::refund`] | 退款 |
15//! | `Pay::alipay()->callback($params)` | [`PayProvider::verify_notify`] | 验证回调 |
16//!
17//! ### PHP 行为对齐
18//!
19//! - **统一 Provider 抽象**:PHP `Yansongda\Pay\Contract\ProviderInterface` 抽象支付提供商。
20//!   Rust 通过 [`PayProvider`] trait 表达。
21//! - **多平台**:PHP 支持支付宝/微信支付。Rust 通过 [`PayPlatform`] 表达。
22//! - **统一订单结构**:PHP `Pay::alipay()->app($order)` 接收订单数组。
23//!   Rust 通过 [`PayOrder`] builder 表达。
24//!
25//! ## 架构说明
26//!
27//! - **PayProvider trait 抽象**:业务方实现具体支付逻辑(支付宝/微信支付/ etc.)
28//! - **MemoryPayProvider**:内置内存实现,暂存支付/退款记录,用于测试和开发环境
29//! - **PayHttpTransport trait**:HTTP 传输抽象,解耦 PayProvider 与具体 HTTP 库
30//! - **MemoryPayHttpTransport**:内存 HTTP 传输实现,支持预置响应队列
31
32use parking_lot::Mutex;
33use std::collections::HashMap;
34use std::sync::Arc;
35use thiserror::Error;
36
37// ============================================================================
38// 错误类型
39// ============================================================================
40
41/// Pay 错误
42#[derive(Debug, Error)]
43pub enum PayError {
44    /// 配置错误
45    #[error("支付配置错误: {0}")]
46    Config(String),
47    /// 缺少必填字段
48    #[error("支付字段缺失: {0}")]
49    MissingField(String),
50    /// 请求失败
51    #[error("支付请求失败: {0}")]
52    RequestFailed(String),
53    /// HTTP 传输失败
54    #[error("HTTP 传输失败: {0}")]
55    HttpTransport(String),
56    /// 序列化失败
57    #[error("序列化失败: {0}")]
58    Serialize(String),
59    /// 签名验证失败
60    #[error("签名验证失败: {0}")]
61    VerifyFailed(String),
62    /// 退款失败
63    #[error("退款失败: {0}")]
64    RefundFailed(String),
65    /// 查询失败
66    #[error("查询失败: {0}")]
67    QueryFailed(String),
68}
69
70// ============================================================================
71// 支付平台
72// ============================================================================
73
74/// 支付平台 — 对齐 PHP `yansongda/pay` 支持的平台
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
76pub enum PayPlatform {
77    /// 支付宝
78    #[default]
79    Alipay,
80    /// 微信支付
81    WechatPay,
82    /// 其他平台(预留扩展)
83    Other,
84}
85
86impl PayPlatform {
87    /// 转换为字符串标识(对齐 PHP 平台名)
88    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// ============================================================================
117// 支付订单(Builder 模式)
118// ============================================================================
119
120/// 支付订单 — 对齐 PHP `Yansongda\Pay\Pay::alipay()->app()` 的订单参数
121///
122/// 使用 Builder 模式构建订单内容,通过 [`PayProvider::pay`] 发起支付。
123///
124/// # PHP 对齐
125///
126/// ```php
127/// // PHP yansongda/pay
128/// $order = [
129///     'out_trade_no' => '202401010001',
130///     'total_amount' => '88.00',
131///     'subject'      => '鲜视达商品',
132/// ];
133/// Pay::alipay()->app($order);
134/// ```
135///
136/// # Rust 用法
137///
138/// ```rust,ignore
139/// use sz_rust_core::pay::{PayOrder, MemoryPayProvider, PayProvider};
140///
141/// let order = PayOrder::new()
142///     .out_trade_no("202401010001")
143///     .total_amount(8800)
144///     .subject("鲜视达商品");
145///
146/// let provider = MemoryPayProvider::new();
147/// let result = provider.pay(order).unwrap();
148/// ```
149#[derive(Debug, Clone, Default)]
150pub struct PayOrder {
151    /// 商户订单号(必填)
152    pub out_trade_no: String,
153    /// 订单总金额(必填,单位:分)
154    pub total_amount: i64,
155    /// 订单标题(必填)
156    pub subject: String,
157    /// 订单描述
158    pub body: Option<String>,
159    /// 异步通知 URL
160    pub notify_url: Option<String>,
161    /// 同步跳转 URL
162    pub return_url: Option<String>,
163    /// 过期时间(秒)
164    pub timeout_express: Option<i64>,
165    /// 附加数据
166    pub passback_params: Option<String>,
167    /// 业务扩展参数
168    pub extra: serde_json::Value,
169}
170
171impl PayOrder {
172    /// 创建空支付订单
173    pub fn new() -> Self {
174        Self::default()
175    }
176
177    /// 设置商户订单号
178    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    /// 设置订单总金额(单位:分)
184    pub fn total_amount(mut self, total_amount: i64) -> Self {
185        self.total_amount = total_amount;
186        self
187    }
188
189    /// 设置订单标题
190    pub fn subject(mut self, subject: impl Into<String>) -> Self {
191        self.subject = subject.into();
192        self
193    }
194
195    /// 设置订单描述
196    pub fn body(mut self, body: impl Into<String>) -> Self {
197        self.body = Some(body.into());
198        self
199    }
200
201    /// 设置异步通知 URL
202    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    /// 设置同步跳转 URL
208    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    /// 设置过期时间(秒)
214    pub fn timeout_express(mut self, timeout_express: i64) -> Self {
215        self.timeout_express = Some(timeout_express);
216        self
217    }
218
219    /// 设置附加数据
220    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    /// 设置业务扩展参数
226    pub fn extra(mut self, extra: serde_json::Value) -> Self {
227        self.extra = extra;
228        self
229    }
230
231    /// 校验必填字段
232    ///
233    /// # 返回
234    ///
235    /// - 商户订单号为空 → [`PayError::MissingField`]("out_trade_no")
236    /// - 订单金额 ≤ 0 → [`PayError::MissingField`]("total_amount")
237    /// - 订单标题为空 → [`PayError::MissingField`]("subject")
238    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// ============================================================================
253// 支付结果
254// ============================================================================
255
256/// 支付结果 — 统一返回格式
257///
258/// 各支付平台的响应统一映射到此结构,便于上层业务处理。
259#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
260pub struct PayResult {
261    /// 支付平台返回的流水号
262    pub trade_no: String,
263    /// 商户订单号
264    pub out_trade_no: String,
265    /// 实际支付金额(分)
266    pub total_amount: i64,
267    /// 交易状态
268    pub trade_status: String,
269    /// 支付平台原始响应
270    pub raw: serde_json::Value,
271}
272
273// ============================================================================
274// 退款订单(Builder 模式)
275// ============================================================================
276
277/// 退款订单 — 对齐 PHP `Pay::alipay()->refund()`
278///
279/// 使用 Builder 模式构建退款内容,通过 [`PayProvider::refund`] 发起退款。
280#[derive(Debug, Clone, Default)]
281pub struct RefundOrder {
282    /// 商户订单号
283    pub out_trade_no: String,
284    /// 退款金额(分)
285    pub refund_amount: i64,
286    /// 退款单号
287    pub out_request_no: String,
288    /// 退款原因
289    pub reason: Option<String>,
290}
291
292impl RefundOrder {
293    /// 创建空退款订单
294    pub fn new() -> Self {
295        Self::default()
296    }
297
298    /// 设置商户订单号
299    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    /// 设置退款金额(单位:分)
305    pub fn refund_amount(mut self, refund_amount: i64) -> Self {
306        self.refund_amount = refund_amount;
307        self
308    }
309
310    /// 设置退款单号
311    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    /// 设置退款原因
317    pub fn reason(mut self, reason: impl Into<String>) -> Self {
318        self.reason = Some(reason.into());
319        self
320    }
321
322    /// 校验必填字段
323    ///
324    /// # 返回
325    ///
326    /// - 商户订单号为空 → [`PayError::MissingField`]("out_trade_no")
327    /// - 退款金额 ≤ 0 → [`PayError::MissingField`]("refund_amount")
328    /// - 退款单号为空 → [`PayError::MissingField`]("out_request_no")
329    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// ============================================================================
344// 支付配置(Builder 模式)
345// ============================================================================
346
347/// 支付配置 — 对齐 PHP `Pay::config(config)` 的配置结构
348///
349/// 使用 Builder 模式构建配置,各支付提供商共享此配置结构。
350#[derive(Debug, Clone)]
351pub struct PayConfig {
352    /// 支付平台
353    pub platform: PayPlatform,
354    /// 应用 ID
355    pub app_id: String,
356    /// 商户私钥(PEM 格式或 PKCS8 字符串)
357    pub merchant_private_key: String,
358    /// 平台公钥
359    pub platform_public_key: String,
360    /// 回调 URL
361    pub notify_url: String,
362    /// 返回 URL
363    pub return_url: Option<String>,
364    /// 沙箱模式
365    pub sandbox: bool,
366    /// 模式(如 web/app/mini/scan)
367    pub mode: String,
368}
369
370impl PayConfig {
371    /// 创建支付配置
372    ///
373    /// # 参数
374    ///
375    /// - `platform`: 支付平台
376    /// - `app_id`: 应用 ID
377    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    /// 设置商户私钥
391    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    /// 设置平台公钥
397    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    /// 设置回调 URL
403    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    /// 设置返回 URL
409    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    /// 设置沙箱模式
415    pub fn with_sandbox(mut self, sandbox: bool) -> Self {
416        self.sandbox = sandbox;
417        self
418    }
419
420    /// 设置模式
421    pub fn with_mode(mut self, mode: impl Into<String>) -> Self {
422        self.mode = mode.into();
423        self
424    }
425
426    /// 校验配置必填字段
427    ///
428    /// # 返回
429    ///
430    /// - 应用 ID 为空 → [`PayError::Config`]("app_id")
431    /// - 商户私钥为空 → [`PayError::Config`]("merchant_private_key")
432    /// - 平台公钥为空 → [`PayError::Config`]("platform_public_key")
433    /// - 回调 URL 为空 → [`PayError::Config`]("notify_url")
434    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
451// ============================================================================
452// PayProvider trait
453// ============================================================================
454
455/// 支付提供商 trait — 对齐 PHP `Yansongda\Pay\Contract\ProviderInterface`
456///
457/// 抽象支付行为,业务方实现具体支付逻辑(支付宝/微信支付/ etc.)。
458///
459/// # PHP 对齐
460///
461/// ```php
462/// // PHP Yansongda\Pay\Contract\ProviderInterface
463/// interface ProviderInterface {
464///     public function pay(array $order): Collection;
465///     public function find(array $order): Collection;
466///     public function close(array $order): void;
467///     public function refund(array $order): Collection;
468///     public function callback(array $params): Collection;
469/// }
470/// ```
471pub trait PayProvider: Send + Sync {
472    /// 发起支付(对齐 `Pay::alipay()->app()` / `Pay::wechat()->app()`)
473    ///
474    /// # 参数
475    ///
476    /// - `order`: 支付订单
477    ///
478    /// # 返回
479    ///
480    /// 成功返回 [`PayResult`],失败返回 [`PayError`]。
481    fn pay(&self, order: PayOrder) -> Result<PayResult, PayError>;
482
483    /// 查询订单(对齐 `Pay::alipay()->find()`)
484    ///
485    /// # 参数
486    ///
487    /// - `out_trade_no`: 商户订单号
488    ///
489    /// # 返回
490    ///
491    /// 成功返回 [`PayResult`],失败返回 [`PayError`]。
492    fn query(&self, out_trade_no: &str) -> Result<PayResult, PayError>;
493
494    /// 关闭订单(对齐 `Pay::alipay()->close()`)
495    ///
496    /// # 参数
497    ///
498    /// - `out_trade_no`: 商户订单号
499    ///
500    /// # 返回
501    ///
502    /// 成功返回 `Ok(())`,失败返回 [`PayError`]。
503    fn close(&self, out_trade_no: &str) -> Result<(), PayError>;
504
505    /// 退款(对齐 `Pay::alipay()->refund()`)
506    ///
507    /// # 参数
508    ///
509    /// - `refund`: 退款订单
510    ///
511    /// # 返回
512    ///
513    /// 成功返回 `Ok(())`,失败返回 [`PayError`]。
514    fn refund(&self, refund: RefundOrder) -> Result<(), PayError>;
515
516    /// 验证回调通知(对齐 `Pay::alipay()->callback()`)
517    ///
518    /// # 参数
519    ///
520    /// - `params`: 回调通知参数(JSON 值)
521    ///
522    /// # 返回
523    ///
524    /// 成功返回 [`PayResult`],失败返回 [`PayError`]。
525    fn verify_notify(&self, params: &serde_json::Value) -> Result<PayResult, PayError>;
526}
527
528// ============================================================================
529// MemoryPayProvider(测试/开发用实现)
530// ============================================================================
531
532/// 内存支付提供商 — 用于测试和开发环境
533///
534/// 不实际调用支付平台 API,而是将支付/退款记录暂存到内存,供测试断言使用。
535///
536/// # 线程安全
537///
538/// 通过 `Arc<Mutex<...>>` 保护内部状态,支持并发访问。
539///
540/// # 用法
541///
542/// ```rust,ignore
543/// use sz_rust_core::pay::{MemoryPayProvider, PayOrder, PayProvider};
544///
545/// let provider = MemoryPayProvider::new();
546/// let order = PayOrder::new()
547///     .out_trade_no("202401010001")
548///     .total_amount(8800)
549///     .subject("鲜视达商品");
550///
551/// let result = provider.pay(order).unwrap();
552/// assert_eq!(result.out_trade_no, "202401010001");
553/// assert_eq!(provider.orders().len(), 1);
554/// ```
555#[derive(Debug, Default)]
556pub struct MemoryPayProvider {
557    /// 已发起的支付订单(按商户订单号索引)
558    orders: Arc<Mutex<HashMap<String, PayResult>>>,
559    /// 已发起的退款记录
560    refunds: Arc<Mutex<Vec<RefundOrder>>>,
561    /// 预置查询结果(用于测试 query 行为)
562    query_result: Arc<Mutex<Option<PayResult>>>,
563}
564
565impl MemoryPayProvider {
566    /// 创建新的内存支付提供商
567    pub fn new() -> Self {
568        Self::default()
569    }
570
571    /// 获取所有已发起的支付订单(快照)
572    pub fn orders(&self) -> Vec<PayResult> {
573        self.orders.lock().values().cloned().collect()
574    }
575
576    /// 获取所有已发起的退款记录(快照)
577    pub fn refunds(&self) -> Vec<RefundOrder> {
578        self.refunds.lock().clone()
579    }
580
581    /// 预置查询结果(query 调用将返回此结果)
582    ///
583    /// 设置后,[`PayProvider::query`] 将直接返回此结果,不再查找已发起订单。
584    /// 传 `None` 清除预置,恢复正常查找逻辑。
585    pub fn set_query_result(&self, result: PayResult) {
586        *self.query_result.lock() = Some(result);
587    }
588
589    /// 清空所有支付/退款记录及预置查询结果
590    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        // 1. 校验订单必填字段
600        order.validate()?;
601
602        // 2. 校验订单未重复
603        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        // 3. 构造支付结果(生成内存流水号)
612        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        // 4. 暂存到内存
628        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        // 1. 若预置查询结果,直接返回
634        if let Some(result) = self.query_result.lock().clone() {
635            return Ok(result);
636        }
637
638        // 2. 否则从已发起订单中查找
639        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            // 标记为已关闭
650            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        // 1. 校验退款订单必填字段
661        refund.validate()?;
662
663        // 2. 校验原订单存在
664        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        // 3. 暂存到内存
672        self.refunds.lock().push(refund);
673        Ok(())
674    }
675
676    fn verify_notify(&self, params: &serde_json::Value) -> Result<PayResult, PayError> {
677        // 1. 提取商户订单号(必填)
678        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        // 2. 提取其他字段(可选,缺省回退)
684        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
709// ============================================================================
710// PayHttpTransport trait(HTTP 传输抽象)
711// ============================================================================
712
713/// 支付 HTTP 传输 trait — 用于解耦 PayProvider 与具体 HTTP 库
714///
715/// 与 [`crate::notify::HttpTransport`] 不同,此 trait 的 `post_json` / `get`
716/// 返回响应体字符串,以便支付提供商解析平台响应。
717///
718/// # 线程安全
719///
720/// 实现者必须保证 `Send + Sync`,因为 PayProvider 通常作为单例在多线程下使用。
721pub trait PayHttpTransport: Send + Sync {
722    /// POST JSON 并返回响应体
723    ///
724    /// # 参数
725    ///
726    /// - `url`: 目标 URL
727    /// - `body`: 请求体(JSON 字符串)
728    ///
729    /// # 返回
730    ///
731    /// 成功返回响应体字符串,失败返回 [`PayError`]。
732    fn post_json(&self, url: &str, body: &str) -> Result<String, PayError>;
733
734    /// GET 并返回响应体
735    ///
736    /// # 参数
737    ///
738    /// - `url`: 目标 URL
739    ///
740    /// # 返回
741    ///
742    /// 成功返回响应体字符串,失败返回 [`PayError`]。
743    fn get(&self, url: &str) -> Result<String, PayError>;
744}
745
746// ============================================================================
747// MemoryPayHttpTransport(测试/开发用 HTTP 传输实现)
748// ============================================================================
749
750/// 内存支付 HTTP 传输 — 用于测试和开发环境
751///
752/// 不实际发送 HTTP 请求,而是从预置响应队列中依次返回响应。
753/// 同时记录所有请求供测试断言使用。
754#[derive(Debug, Default)]
755pub struct MemoryPayHttpTransport {
756    /// 预置响应队列(FIFO)
757    responses: Mutex<Vec<String>>,
758    /// 已"发送"的请求记录(method, url, body)
759    requests: Mutex<Vec<(String, String, String)>>,
760}
761
762impl MemoryPayHttpTransport {
763    /// 创建新的内存支付 HTTP 传输
764    pub fn new() -> Self {
765        Self::default()
766    }
767
768    /// 追加预置响应到队列尾部
769    pub fn push_response(&self, response: impl Into<String>) {
770        self.responses.lock().push(response.into());
771    }
772
773    /// 获取已发送请求数量
774    pub fn request_count(&self) -> usize {
775        self.requests.lock().len()
776    }
777
778    /// 获取所有已发送请求(快照)
779    ///
780    /// 每条记录为 `(method, url, body)` 三元组。
781    pub fn requests(&self) -> Vec<(String, String, String)> {
782        self.requests.lock().clone()
783    }
784
785    /// 清空预置响应和请求记录
786    pub fn clear(&self) {
787        self.responses.lock().clear();
788        self.requests.lock().clear();
789    }
790
791    /// 从队列头部取出下一条响应;队列空时返回错误
792    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// ============================================================================
821// 单元测试
822// ============================================================================
823
824#[cfg(test)]
825mod tests {
826    use super::*;
827
828    // ------------------------------------------------------------------------
829    // PayPlatform 测试
830    // ------------------------------------------------------------------------
831
832    /// 测试 PayPlatform 的 as_str / Default / Display / FromStr
833    #[test]
834    fn test_pay_platform() {
835        // as_str
836        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        // Default
841        assert_eq!(PayPlatform::default(), PayPlatform::Alipay);
842
843        // Display
844        assert_eq!(format!("{}", PayPlatform::Alipay), "alipay");
845        assert_eq!(format!("{}", PayPlatform::WechatPay), "wechatpay");
846        assert_eq!(format!("{}", PayPlatform::Other), "other");
847
848        // FromStr — 标准名
849        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        // FromStr — 别名 + 大小写不敏感
860        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        // FromStr — 未知平台
871        assert!("unknown".parse::<PayPlatform>().is_err());
872
873        // Copy + Eq + Hash 可用
874        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    // ------------------------------------------------------------------------
880    // PayConfig 测试
881    // ------------------------------------------------------------------------
882
883    /// 测试 PayConfig builder 模式
884    #[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        // validate 通过
907        assert!(config.validate().is_ok());
908
909        // 默认值(仅必填项)
910        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        // validate 失败:缺 app_id
921        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        // validate 失败:缺 merchant_private_key
929        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        // validate 失败:缺 notify_url
937        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    // ------------------------------------------------------------------------
948    // PayOrder 测试
949    // ------------------------------------------------------------------------
950
951    /// 测试 PayOrder builder 模式
952    #[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        // validate 通过
982        assert!(order.validate().is_ok());
983    }
984
985    /// 测试 PayOrder::validate 校验必填字段
986    #[test]
987    fn test_pay_order_validate() {
988        // 全部合法
989        let order = PayOrder::new()
990            .out_trade_no("202401010001")
991            .total_amount(100)
992            .subject("标题");
993        assert!(order.validate().is_ok());
994
995        // 缺 out_trade_no
996        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        // total_amount <= 0
1004        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        // 负数金额
1015        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        // 缺 subject
1026        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        // 默认值校验失败(全部为空)
1036        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    // ------------------------------------------------------------------------
1044    // RefundOrder 测试
1045    // ------------------------------------------------------------------------
1046
1047    /// 测试 RefundOrder builder 模式
1048    #[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        // validate 通过
1062        assert!(refund.validate().is_ok());
1063
1064        // validate 失败:缺 out_trade_no
1065        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        // validate 失败:refund_amount <= 0
1073        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        // validate 失败:缺 out_request_no
1084        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    // ------------------------------------------------------------------------
1093    // PayResult 测试
1094    // ------------------------------------------------------------------------
1095
1096    /// 测试 PayResult 默认值
1097    #[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        // serde 序列化/反序列化往返
1107        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    // ------------------------------------------------------------------------
1124    // MemoryPayProvider 测试
1125    // ------------------------------------------------------------------------
1126
1127    /// 测试 MemoryPayProvider 发起支付
1128    #[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        // 验证返回的 PayResult 字段
1140        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        // raw 包含订单信息
1146        assert_eq!(result.raw["out_trade_no"], "202401010001");
1147        assert_eq!(result.raw["total_amount"], 8800);
1148        assert_eq!(result.raw["subject"], "鲜视达商品");
1149
1150        // 已存储到内存
1151        assert_eq!(provider.orders().len(), 1);
1152
1153        // 重复订单号应失败
1154        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        // 不应新增记录
1164        assert_eq!(provider.orders().len(), 1);
1165    }
1166
1167    /// 测试 MemoryPayProvider 查询订单
1168    #[test]
1169    fn test_memory_pay_provider_query() {
1170        let provider = MemoryPayProvider::new();
1171
1172        // 1. 查询不存在的订单应失败
1173        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        // 2. 发起支付后查询
1180        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        // 3. 预置查询结果优先返回
1192        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        // 即使订单不存在,也返回预置结果
1202        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        // clear 后预置结果被清除
1209        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    /// 测试 MemoryPayProvider 关闭订单
1218    #[test]
1219    fn test_memory_pay_provider_close() {
1220        let provider = MemoryPayProvider::new();
1221
1222        // 1. 关闭不存在的订单应失败
1223        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        // 2. 发起支付后关闭
1230        let order = PayOrder::new()
1231            .out_trade_no("C001")
1232            .total_amount(500)
1233            .subject("关闭测试");
1234        provider.pay(order).expect("支付应成功");
1235
1236        // 关闭订单
1237        provider.close("C001").expect("关闭应成功");
1238
1239        // 3. 查询确认状态为 CLOSED
1240        let result = provider.query("C001").expect("查询应成功");
1241        assert_eq!(result.trade_status, "CLOSED");
1242    }
1243
1244    /// 测试 MemoryPayProvider 退款
1245    #[test]
1246    fn test_memory_pay_provider_refund() {
1247        let provider = MemoryPayProvider::new();
1248
1249        // 1. 原订单不存在时退款应失败
1250        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        // 2. 发起支付后退款
1262        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        // 退款记录已存储
1276        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        // 3. 退款订单缺字段应失败
1284        let bad = RefundOrder::new()
1285            .out_trade_no("R001")
1286            .refund_amount(0) // 金额无效
1287            .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        // 不应新增退款记录
1294        assert_eq!(provider.refunds().len(), 1);
1295    }
1296
1297    /// 测试 MemoryPayProvider 验证回调通知
1298    #[test]
1299    fn test_memory_pay_provider_verify_notify() {
1300        let provider = MemoryPayProvider::new();
1301
1302        // 1. 完整回调参数
1303        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(&params).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        // raw 保留原始参数
1316        assert_eq!(result.raw["buyer_id"], "2088000000000001");
1317
1318        // 2. 缺少 out_trade_no 应失败
1319        let params = serde_json::json!({
1320            "trade_no": "2024ALIPAY001",
1321            "total_amount": 8800
1322        });
1323        let err = provider.verify_notify(&params).unwrap_err();
1324        match err {
1325            PayError::VerifyFailed(msg) => assert!(msg.contains("out_trade_no")),
1326            other => panic!("期望 VerifyFailed, 实际 {other:?}"),
1327        }
1328
1329        // 3. 缺省字段回退:trade_status 默认 TRADE_SUCCESS
1330        let params = serde_json::json!({
1331            "out_trade_no": "CB002",
1332            "trade_no": "T002"
1333        });
1334        let result = provider.verify_notify(&params).expect("验证应成功");
1335        assert_eq!(result.out_trade_no, "CB002");
1336        assert_eq!(result.trade_no, "T002");
1337        assert_eq!(result.total_amount, 0); // 缺省 0
1338        assert_eq!(result.trade_status, "TRADE_SUCCESS"); // 缺省值
1339    }
1340
1341    /// 测试 MemoryPayProvider 支付时缺字段返回错误
1342    #[test]
1343    fn test_memory_pay_provider_missing_fields() {
1344        let provider = MemoryPayProvider::new();
1345
1346        // 缺 out_trade_no
1347        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        // total_amount <= 0
1356        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        // 缺 subject
1368        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        // 空订单(全默认值)应失败
1377        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    // ------------------------------------------------------------------------
1386    // MemoryPayHttpTransport 测试
1387    // ------------------------------------------------------------------------
1388
1389    /// 测试 MemoryPayHttpTransport post_json
1390    #[test]
1391    fn test_memory_pay_http_transport_post_json() {
1392        let transport = MemoryPayHttpTransport::new();
1393
1394        // 队列空时返回错误
1395        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        // 预置响应后返回响应并记录请求
1405        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        // 验证请求记录
1413        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        // 再次调用队列空返回错误
1420        let err = transport.post_json("url", "{}").unwrap_err();
1421        match err {
1422            PayError::HttpTransport(_) => {}
1423            other => panic!("期望 HttpTransport, 实际 {other:?}"),
1424        }
1425        // 失败请求不应记录
1426        assert_eq!(transport.request_count(), 1);
1427    }
1428
1429    /// 测试 MemoryPayHttpTransport get
1430    #[test]
1431    fn test_memory_pay_http_transport_get() {
1432        let transport = MemoryPayHttpTransport::new();
1433
1434        // 队列空时返回错误
1435        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        // 预置响应后返回响应并记录请求
1443        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        // 验证请求记录(GET 的 body 为空)
1451        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        // clear 后队列和记录均清空
1461        transport.clear();
1462        assert_eq!(transport.request_count(), 0);
1463        assert!(transport.get("url").is_err());
1464    }
1465
1466    /// 测试 MemoryPayHttpTransport 响应队列 FIFO 顺序
1467    #[test]
1468    fn test_memory_pay_http_transport_queue() {
1469        let transport = MemoryPayHttpTransport::new();
1470
1471        // 预置 3 条响应
1472        transport.push_response("resp1");
1473        transport.push_response("resp2");
1474        transport.push_response("resp3");
1475
1476        // 交替调用 post_json / get,验证 FIFO 顺序
1477        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        // 队列已空
1487        assert!(transport.post_json("url4", "body4").is_err());
1488        assert!(transport.get("url4").is_err());
1489
1490        // 验证请求记录顺序与调用顺序一致
1491        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}