use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::auth::Signer;
use crate::config::WxPayConfig;
use crate::error::WxPayResult;
use crate::http::{HttpClient, HttpMethod};
use crate::services::transport::{ServiceTransport, TransportObserver};
#[derive(Debug, Clone, Serialize)]
pub struct RefundRequest {
pub transaction_id: Option<String>,
pub out_trade_no: Option<String>,
pub out_refund_no: String,
pub reason: Option<String>,
pub amount: RefundAmount,
pub notify_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefundAmount {
pub refund: u64,
pub total: u64,
pub currency: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RefundResponse {
pub refund_id: String,
pub out_refund_no: String,
pub transaction_id: String,
pub out_trade_no: String,
pub status: String,
pub amount: Option<RefundAmount>,
}
#[derive(Debug, Clone, Serialize)]
pub struct QueryRefundRequest {
pub out_refund_no: String,
}
#[allow(dead_code)]
pub struct RefundService {
config: Arc<WxPayConfig>,
http_client: Arc<dyn HttpClient>,
signer: Arc<dyn Signer>,
transport: ServiceTransport,
}
impl RefundService {
pub fn new(
config: Arc<WxPayConfig>,
http_client: Arc<dyn HttpClient>,
signer: Arc<dyn Signer>,
) -> Self {
Self::new_with_observer(config.clone(), http_client.clone(), signer.clone(), None)
}
pub fn new_with_observer(
config: Arc<WxPayConfig>,
http_client: Arc<dyn HttpClient>,
signer: Arc<dyn Signer>,
transport_observer: Option<Arc<dyn TransportObserver>>,
) -> Self {
Self {
config: config.clone(),
http_client: http_client.clone(),
signer: signer.clone(),
transport: ServiceTransport::new_with_observer(
config,
http_client,
signer,
transport_observer,
),
}
}
pub async fn create_refund(&self, request: &RefundRequest) -> WxPayResult<RefundResponse> {
let body = serde_json::to_string(request)?;
self.transport
.request(
HttpMethod::Post,
"/v3/refund/domestic/refunds",
Some(&body),
"refund.create_refund",
)
.await
}
pub async fn create(&self, request: &RefundRequest) -> WxPayResult<RefundResponse> {
self.create_refund(request).await
}
pub async fn query_refund(&self, out_refund_no: &str) -> WxPayResult<RefundResponse> {
let path = format!("/v3/refund/domestic/refunds/{}", out_refund_no);
self.transport
.request(HttpMethod::Get, &path, None, "refund.query_refund")
.await
}
pub async fn query(&self, request: &QueryRefundRequest) -> WxPayResult<RefundResponse> {
self.query_refund(&request.out_refund_no).await
}
pub async fn query_by_out_refund_no(&self, out_refund_no: &str) -> WxPayResult<RefundResponse> {
self.query_refund(out_refund_no).await
}
}
impl std::fmt::Debug for RefundService {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RefundService").finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_refund_request_serialization() {
let request = RefundRequest {
transaction_id: Some("1217752501201407033233368018".to_string()),
out_trade_no: None,
out_refund_no: "1217752501201407033233368018".to_string(),
reason: Some("商品已售完".to_string()),
amount: RefundAmount {
refund: 100,
total: 100,
currency: "CNY".to_string(),
},
notify_url: None,
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("1217752501201407033233368018"));
assert!(json.contains("商品已售完"));
}
#[test]
fn test_refund_response_deserialization() {
let json = r#"{
"refund_id": "50000000382019052709732678869",
"out_refund_no": "1217752501201407033233368018",
"transaction_id": "1217752501201407033233368018",
"out_trade_no": "1217752501201407033233368018",
"status": "SUCCESS"
}"#;
let response: RefundResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.status, "SUCCESS");
assert_eq!(response.refund_id, "50000000382019052709732678869");
}
#[test]
fn test_go_style_refund_alias_signature_exists() {
let _ = RefundService::query_by_out_refund_no;
}
}