Skip to main content

sz_rust_pay_facade/
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(Clone)]
351pub struct PayConfig {
352    /// 支付平台
353    pub platform: PayPlatform,
354    /// 应用 ID
355    pub app_id: String,
356    /// 商户私钥(PEM 格式或 PKCS8 字符串)
357    ///
358    /// **安全注意**:此字段在 `Debug` 输出中始终脱敏为 `"<redacted>"`,
359    /// 防止日志泄漏。序列化/反序列化不受影响(未派生 `Serialize`)。
360    pub merchant_private_key: String,
361    /// 平台公钥
362    pub platform_public_key: String,
363    /// 回调 URL
364    pub notify_url: String,
365    /// 返回 URL
366    pub return_url: Option<String>,
367    /// 沙箱模式
368    pub sandbox: bool,
369    /// 模式(如 web/app/mini/scan)
370    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    /// 创建支付配置
390    ///
391    /// # 参数
392    ///
393    /// - `platform`: 支付平台
394    /// - `app_id`: 应用 ID
395    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    /// 设置商户私钥
409    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    /// 设置平台公钥
415    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    /// 设置回调 URL
421    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    /// 设置返回 URL
427    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    /// 设置沙箱模式
433    pub fn with_sandbox(mut self, sandbox: bool) -> Self {
434        self.sandbox = sandbox;
435        self
436    }
437
438    /// 设置模式
439    pub fn with_mode(mut self, mode: impl Into<String>) -> Self {
440        self.mode = mode.into();
441        self
442    }
443
444    /// 校验配置必填字段
445    ///
446    /// # 返回
447    ///
448    /// - 应用 ID 为空 → [`PayError::Config`]("app_id")
449    /// - 商户私钥为空 → [`PayError::Config`]("merchant_private_key")
450    /// - 平台公钥为空 → [`PayError::Config`]("platform_public_key")
451    /// - 回调 URL 为空 → [`PayError::Config`]("notify_url")
452    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
469// ============================================================================
470// PayProvider trait
471// ============================================================================
472
473/// 支付提供商 trait — 对齐 PHP `Yansongda\Pay\Contract\ProviderInterface`
474///
475/// 抽象支付行为,业务方实现具体支付逻辑(支付宝/微信支付/ etc.)。
476///
477/// # PHP 对齐
478///
479/// ```php
480/// // PHP Yansongda\Pay\Contract\ProviderInterface
481/// interface ProviderInterface {
482///     public function pay(array $order): Collection;
483///     public function find(array $order): Collection;
484///     public function close(array $order): void;
485///     public function refund(array $order): Collection;
486///     public function callback(array $params): Collection;
487/// }
488/// ```
489pub trait PayProvider: Send + Sync {
490    /// 发起支付(对齐 `Pay::alipay()->app()` / `Pay::wechat()->app()`)
491    ///
492    /// # 参数
493    ///
494    /// - `order`: 支付订单
495    ///
496    /// # 返回
497    ///
498    /// 成功返回 [`PayResult`],失败返回 [`PayError`]。
499    fn pay(&self, order: PayOrder) -> Result<PayResult, PayError>;
500
501    /// 查询订单(对齐 `Pay::alipay()->find()`)
502    ///
503    /// # 参数
504    ///
505    /// - `out_trade_no`: 商户订单号
506    ///
507    /// # 返回
508    ///
509    /// 成功返回 [`PayResult`],失败返回 [`PayError`]。
510    fn query(&self, out_trade_no: &str) -> Result<PayResult, PayError>;
511
512    /// 关闭订单(对齐 `Pay::alipay()->close()`)
513    ///
514    /// # 参数
515    ///
516    /// - `out_trade_no`: 商户订单号
517    ///
518    /// # 返回
519    ///
520    /// 成功返回 `Ok(())`,失败返回 [`PayError`]。
521    fn close(&self, out_trade_no: &str) -> Result<(), PayError>;
522
523    /// 退款(对齐 `Pay::alipay()->refund()`)
524    ///
525    /// # 参数
526    ///
527    /// - `refund`: 退款订单
528    ///
529    /// # 返回
530    ///
531    /// 成功返回 `Ok(())`,失败返回 [`PayError`]。
532    fn refund(&self, refund: RefundOrder) -> Result<(), PayError>;
533
534    /// 验证回调通知(对齐 `Pay::alipay()->callback()`)
535    ///
536    /// # 参数
537    ///
538    /// - `params`: 回调通知参数(JSON 值)
539    ///
540    /// # 返回
541    ///
542    /// 成功返回 [`PayResult`],失败返回 [`PayError`]。
543    fn verify_notify(&self, params: &serde_json::Value) -> Result<PayResult, PayError>;
544}
545
546// ============================================================================
547// MemoryPayProvider(测试/开发用实现)
548// ============================================================================
549
550/// 内存支付提供商 — 用于测试和开发环境
551///
552/// 不实际调用支付平台 API,而是将支付/退款记录暂存到内存,供测试断言使用。
553///
554/// # 线程安全
555///
556/// 通过 `Arc<Mutex<...>>` 保护内部状态,支持并发访问。
557///
558/// # 用法
559///
560/// ```rust,ignore
561/// use sz_rust_core::pay::{MemoryPayProvider, PayOrder, PayProvider};
562///
563/// let provider = MemoryPayProvider::new();
564/// let order = PayOrder::new()
565///     .out_trade_no("202401010001")
566///     .total_amount(8800)
567///     .subject("鲜视达商品");
568///
569/// let result = provider.pay(order).unwrap();
570/// assert_eq!(result.out_trade_no, "202401010001");
571/// assert_eq!(provider.orders().len(), 1);
572/// ```
573#[derive(Debug, Default)]
574pub struct MemoryPayProvider {
575    /// 已发起的支付订单(按商户订单号索引)
576    orders: Arc<Mutex<HashMap<String, PayResult>>>,
577    /// 已发起的退款记录
578    refunds: Arc<Mutex<Vec<RefundOrder>>>,
579    /// 预置查询结果(用于测试 query 行为)
580    query_result: Arc<Mutex<Option<PayResult>>>,
581}
582
583impl MemoryPayProvider {
584    /// 创建新的内存支付提供商
585    pub fn new() -> Self {
586        Self::default()
587    }
588
589    /// 获取所有已发起的支付订单(快照)
590    pub fn orders(&self) -> Vec<PayResult> {
591        self.orders.lock().values().cloned().collect()
592    }
593
594    /// 获取所有已发起的退款记录(快照)
595    pub fn refunds(&self) -> Vec<RefundOrder> {
596        self.refunds.lock().clone()
597    }
598
599    /// 预置查询结果(query 调用将返回此结果)
600    ///
601    /// 设置后,[`PayProvider::query`] 将直接返回此结果,不再查找已发起订单。
602    /// 传 `None` 清除预置,恢复正常查找逻辑。
603    pub fn set_query_result(&self, result: PayResult) {
604        *self.query_result.lock() = Some(result);
605    }
606
607    /// 清空所有支付/退款记录及预置查询结果
608    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        // 1. 校验订单必填字段
618        order.validate()?;
619
620        // 2. 校验订单未重复
621        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        // 3. 构造支付结果(生成内存流水号)
630        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        // 4. 暂存到内存
646        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        // 1. 若预置查询结果,直接返回
652        if let Some(result) = self.query_result.lock().clone() {
653            return Ok(result);
654        }
655
656        // 2. 否则从已发起订单中查找
657        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            // 标记为已关闭
668            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        // 1. 校验退款订单必填字段
679        refund.validate()?;
680
681        // 2. 校验原订单存在
682        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        // 3. 暂存到内存
690        self.refunds.lock().push(refund);
691        Ok(())
692    }
693
694    fn verify_notify(&self, params: &serde_json::Value) -> Result<PayResult, PayError> {
695        // 1. 提取商户订单号(必填)
696        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        // 2. 提取其他字段(可选,缺省回退)
702        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
727// ============================================================================
728// PayHttpTransport trait(HTTP 传输抽象)
729// ============================================================================
730
731/// 支付 HTTP 传输 trait — 用于解耦 PayProvider 与具体 HTTP 库
732///
733/// 与 `sz_rust_state_facade::notify::HttpTransport`(state-facade)不同,此 trait 的 `post_json` / `get`
734/// 返回响应体字符串,以便支付提供商解析平台响应。
735///
736/// # 线程安全
737///
738/// 实现者必须保证 `Send + Sync`,因为 PayProvider 通常作为单例在多线程下使用。
739pub trait PayHttpTransport: Send + Sync {
740    /// POST JSON 并返回响应体
741    ///
742    /// # 参数
743    ///
744    /// - `url`: 目标 URL
745    /// - `body`: 请求体(JSON 字符串)
746    ///
747    /// # 返回
748    ///
749    /// 成功返回响应体字符串,失败返回 [`PayError`]。
750    fn post_json(&self, url: &str, body: &str) -> Result<String, PayError>;
751
752    /// GET 并返回响应体
753    ///
754    /// # 参数
755    ///
756    /// - `url`: 目标 URL
757    ///
758    /// # 返回
759    ///
760    /// 成功返回响应体字符串,失败返回 [`PayError`]。
761    fn get(&self, url: &str) -> Result<String, PayError>;
762}
763
764// ============================================================================
765// MemoryPayHttpTransport(测试/开发用 HTTP 传输实现)
766// ============================================================================
767
768/// 内存支付 HTTP 传输 — 用于测试和开发环境
769///
770/// 不实际发送 HTTP 请求,而是从预置响应队列中依次返回响应。
771/// 同时记录所有请求供测试断言使用。
772#[derive(Debug, Default)]
773pub struct MemoryPayHttpTransport {
774    /// 预置响应队列(FIFO)
775    responses: Mutex<Vec<String>>,
776    /// 已"发送"的请求记录(method, url, body)
777    requests: Mutex<Vec<(String, String, String)>>,
778}
779
780impl MemoryPayHttpTransport {
781    /// 创建新的内存支付 HTTP 传输
782    pub fn new() -> Self {
783        Self::default()
784    }
785
786    /// 追加预置响应到队列尾部
787    pub fn push_response(&self, response: impl Into<String>) {
788        self.responses.lock().push(response.into());
789    }
790
791    /// 获取已发送请求数量
792    pub fn request_count(&self) -> usize {
793        self.requests.lock().len()
794    }
795
796    /// 获取所有已发送请求(快照)
797    ///
798    /// 每条记录为 `(method, url, body)` 三元组。
799    pub fn requests(&self) -> Vec<(String, String, String)> {
800        self.requests.lock().clone()
801    }
802
803    /// 清空预置响应和请求记录
804    pub fn clear(&self) {
805        self.responses.lock().clear();
806        self.requests.lock().clear();
807    }
808
809    /// 从队列头部取出下一条响应;队列空时返回错误
810    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// ============================================================================
839// 单元测试
840// ============================================================================
841
842#[cfg(test)]
843mod tests {
844    use super::*;
845
846    // ------------------------------------------------------------------------
847    // PayPlatform 测试
848    // ------------------------------------------------------------------------
849
850    /// 测试 PayPlatform 的 as_str / Default / Display / FromStr
851    #[test]
852    fn test_pay_platform() {
853        // as_str
854        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        // Default
859        assert_eq!(PayPlatform::default(), PayPlatform::Alipay);
860
861        // Display
862        assert_eq!(format!("{}", PayPlatform::Alipay), "alipay");
863        assert_eq!(format!("{}", PayPlatform::WechatPay), "wechatpay");
864        assert_eq!(format!("{}", PayPlatform::Other), "other");
865
866        // FromStr — 标准名
867        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        // FromStr — 别名 + 大小写不敏感
878        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        // FromStr — 未知平台
889        assert!("unknown".parse::<PayPlatform>().is_err());
890
891        // Copy + Eq + Hash 可用
892        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    // ------------------------------------------------------------------------
898    // PayConfig 测试
899    // ------------------------------------------------------------------------
900
901    /// 测试 PayConfig builder 模式
902    #[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        // validate 通过
925        assert!(config.validate().is_ok());
926
927        // Debug 输出中商户私钥必须脱敏(安全铁律:敏感字段不泄漏)
928        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        // 默认值(仅必填项)
940        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        // validate 失败:缺 app_id
951        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        // validate 失败:缺 merchant_private_key
959        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        // validate 失败:缺 notify_url
967        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    // ------------------------------------------------------------------------
978    // PayOrder 测试
979    // ------------------------------------------------------------------------
980
981    /// 测试 PayOrder builder 模式
982    #[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        // validate 通过
1012        assert!(order.validate().is_ok());
1013    }
1014
1015    /// 测试 PayOrder::validate 校验必填字段
1016    #[test]
1017    fn test_pay_order_validate() {
1018        // 全部合法
1019        let order = PayOrder::new()
1020            .out_trade_no("202401010001")
1021            .total_amount(100)
1022            .subject("标题");
1023        assert!(order.validate().is_ok());
1024
1025        // 缺 out_trade_no
1026        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        // total_amount <= 0
1034        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        // 负数金额
1045        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        // 缺 subject
1056        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        // 默认值校验失败(全部为空)
1066        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    // ------------------------------------------------------------------------
1074    // RefundOrder 测试
1075    // ------------------------------------------------------------------------
1076
1077    /// 测试 RefundOrder builder 模式
1078    #[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        // validate 通过
1092        assert!(refund.validate().is_ok());
1093
1094        // validate 失败:缺 out_trade_no
1095        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        // validate 失败:refund_amount <= 0
1103        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        // validate 失败:缺 out_request_no
1114        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    // ------------------------------------------------------------------------
1123    // PayResult 测试
1124    // ------------------------------------------------------------------------
1125
1126    /// 测试 PayResult 默认值
1127    #[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        // serde 序列化/反序列化往返
1137        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    // ------------------------------------------------------------------------
1154    // MemoryPayProvider 测试
1155    // ------------------------------------------------------------------------
1156
1157    /// 测试 MemoryPayProvider 发起支付
1158    #[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        // 验证返回的 PayResult 字段
1170        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        // raw 包含订单信息
1176        assert_eq!(result.raw["out_trade_no"], "202401010001");
1177        assert_eq!(result.raw["total_amount"], 8800);
1178        assert_eq!(result.raw["subject"], "鲜视达商品");
1179
1180        // 已存储到内存
1181        assert_eq!(provider.orders().len(), 1);
1182
1183        // 重复订单号应失败
1184        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        // 不应新增记录
1194        assert_eq!(provider.orders().len(), 1);
1195    }
1196
1197    /// 测试 MemoryPayProvider 查询订单
1198    #[test]
1199    fn test_memory_pay_provider_query() {
1200        let provider = MemoryPayProvider::new();
1201
1202        // 1. 查询不存在的订单应失败
1203        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        // 2. 发起支付后查询
1210        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        // 3. 预置查询结果优先返回
1222        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        // 即使订单不存在,也返回预置结果
1232        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        // clear 后预置结果被清除
1239        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    /// 测试 MemoryPayProvider 关闭订单
1248    #[test]
1249    fn test_memory_pay_provider_close() {
1250        let provider = MemoryPayProvider::new();
1251
1252        // 1. 关闭不存在的订单应失败
1253        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        // 2. 发起支付后关闭
1260        let order = PayOrder::new()
1261            .out_trade_no("C001")
1262            .total_amount(500)
1263            .subject("关闭测试");
1264        provider.pay(order).expect("支付应成功");
1265
1266        // 关闭订单
1267        provider.close("C001").expect("关闭应成功");
1268
1269        // 3. 查询确认状态为 CLOSED
1270        let result = provider.query("C001").expect("查询应成功");
1271        assert_eq!(result.trade_status, "CLOSED");
1272    }
1273
1274    /// 测试 MemoryPayProvider 退款
1275    #[test]
1276    fn test_memory_pay_provider_refund() {
1277        let provider = MemoryPayProvider::new();
1278
1279        // 1. 原订单不存在时退款应失败
1280        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        // 2. 发起支付后退款
1292        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        // 退款记录已存储
1306        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        // 3. 退款订单缺字段应失败
1314        let bad = RefundOrder::new()
1315            .out_trade_no("R001")
1316            .refund_amount(0) // 金额无效
1317            .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        // 不应新增退款记录
1324        assert_eq!(provider.refunds().len(), 1);
1325    }
1326
1327    /// 测试 MemoryPayProvider 验证回调通知
1328    #[test]
1329    fn test_memory_pay_provider_verify_notify() {
1330        let provider = MemoryPayProvider::new();
1331
1332        // 1. 完整回调参数
1333        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(&params).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        // raw 保留原始参数
1346        assert_eq!(result.raw["buyer_id"], "2088000000000001");
1347
1348        // 2. 缺少 out_trade_no 应失败
1349        let params = serde_json::json!({
1350            "trade_no": "2024ALIPAY001",
1351            "total_amount": 8800
1352        });
1353        let err = provider.verify_notify(&params).unwrap_err();
1354        match err {
1355            PayError::VerifyFailed(msg) => assert!(msg.contains("out_trade_no")),
1356            other => panic!("期望 VerifyFailed, 实际 {other:?}"),
1357        }
1358
1359        // 3. 缺省字段回退:trade_status 默认 TRADE_SUCCESS
1360        let params = serde_json::json!({
1361            "out_trade_no": "CB002",
1362            "trade_no": "T002"
1363        });
1364        let result = provider.verify_notify(&params).expect("验证应成功");
1365        assert_eq!(result.out_trade_no, "CB002");
1366        assert_eq!(result.trade_no, "T002");
1367        assert_eq!(result.total_amount, 0); // 缺省 0
1368        assert_eq!(result.trade_status, "TRADE_SUCCESS"); // 缺省值
1369    }
1370
1371    /// 测试 MemoryPayProvider 支付时缺字段返回错误
1372    #[test]
1373    fn test_memory_pay_provider_missing_fields() {
1374        let provider = MemoryPayProvider::new();
1375
1376        // 缺 out_trade_no
1377        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        // total_amount <= 0
1386        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        // 缺 subject
1398        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        // 空订单(全默认值)应失败
1407        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    // ------------------------------------------------------------------------
1416    // MemoryPayHttpTransport 测试
1417    // ------------------------------------------------------------------------
1418
1419    /// 测试 MemoryPayHttpTransport post_json
1420    #[test]
1421    fn test_memory_pay_http_transport_post_json() {
1422        let transport = MemoryPayHttpTransport::new();
1423
1424        // 队列空时返回错误
1425        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        // 预置响应后返回响应并记录请求
1435        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        // 验证请求记录
1443        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        // 再次调用队列空返回错误
1450        let err = transport.post_json("url", "{}").unwrap_err();
1451        match err {
1452            PayError::HttpTransport(_) => {}
1453            other => panic!("期望 HttpTransport, 实际 {other:?}"),
1454        }
1455        // 失败请求不应记录
1456        assert_eq!(transport.request_count(), 1);
1457    }
1458
1459    /// 测试 MemoryPayHttpTransport get
1460    #[test]
1461    fn test_memory_pay_http_transport_get() {
1462        let transport = MemoryPayHttpTransport::new();
1463
1464        // 队列空时返回错误
1465        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        // 预置响应后返回响应并记录请求
1473        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        // 验证请求记录(GET 的 body 为空)
1481        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        // clear 后队列和记录均清空
1491        transport.clear();
1492        assert_eq!(transport.request_count(), 0);
1493        assert!(transport.get("url").is_err());
1494    }
1495
1496    /// 测试 MemoryPayHttpTransport 响应队列 FIFO 顺序
1497    #[test]
1498    fn test_memory_pay_http_transport_queue() {
1499        let transport = MemoryPayHttpTransport::new();
1500
1501        // 预置 3 条响应
1502        transport.push_response("resp1");
1503        transport.push_response("resp2");
1504        transport.push_response("resp3");
1505
1506        // 交替调用 post_json / get,验证 FIFO 顺序
1507        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        // 队列已空
1517        assert!(transport.post_json("url4", "body4").is_err());
1518        assert!(transport.get("url4").is_err());
1519
1520        // 验证请求记录顺序与调用顺序一致
1521        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}