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 TransferRequest {
pub appid: String,
pub out_batch_no: String,
pub batch_name: String,
pub batch_remark: String,
pub transfer_detail_list: Vec<TransferDetail>,
pub total_amount: u64,
pub total_num: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransferDetail {
pub out_detail_no: String,
pub transfer_amount: u64,
pub transfer_remark: String,
pub openid: String,
pub user_name: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TransferResponse {
pub batch_id: String,
pub out_batch_no: String,
pub batch_status: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct QueryTransferBatchRequest {
pub out_batch_no: String,
}
#[allow(dead_code)]
pub struct TransferService {
config: Arc<WxPayConfig>,
http_client: Arc<dyn HttpClient>,
signer: Arc<dyn Signer>,
transport: ServiceTransport,
}
impl TransferService {
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_transfer(
&self,
request: &TransferRequest,
) -> WxPayResult<TransferResponse> {
let body = serde_json::to_string(request)?;
self.transport
.request(
HttpMethod::Post,
"/v3/transfer/batches",
Some(&body),
"transfer.create_transfer",
)
.await
}
pub async fn create(&self, request: &TransferRequest) -> WxPayResult<TransferResponse> {
self.create_transfer(request).await
}
pub async fn initiate_batch_transfer(
&self,
request: &TransferRequest,
) -> WxPayResult<TransferResponse> {
self.create_transfer(request).await
}
pub async fn query_transfer_batch(&self, batch_id: &str) -> WxPayResult<TransferResponse> {
let path = format!("/v3/transfer/batches/{}", batch_id);
self.transport
.request(
HttpMethod::Get,
&path,
None,
"transfer.query_transfer_batch",
)
.await
}
pub async fn query_batch(
&self,
request: &QueryTransferBatchRequest,
) -> WxPayResult<TransferResponse> {
self.query_transfer_batch(&request.out_batch_no).await
}
pub async fn query(
&self,
request: &QueryTransferBatchRequest,
) -> WxPayResult<TransferResponse> {
self.query_batch(request).await
}
pub async fn get_transfer_batch_by_out_batch_no(
&self,
out_batch_no: &str,
) -> WxPayResult<TransferResponse> {
self.query_transfer_batch(out_batch_no).await
}
}
impl std::fmt::Debug for TransferService {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TransferService").finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transfer_request_serialization() {
let request = TransferRequest {
appid: "wx88888888".to_string(),
out_batch_no: "batch_001".to_string(),
batch_name: "测试转账".to_string(),
batch_remark: "测试".to_string(),
transfer_detail_list: vec![TransferDetail {
out_detail_no: "detail_001".to_string(),
transfer_amount: 100,
transfer_remark: "转账".to_string(),
openid: "test_openid".to_string(),
user_name: None,
}],
total_amount: 100,
total_num: 1,
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("batch_001"));
assert!(json.contains("测试转账"));
}
#[test]
fn test_transfer_response_deserialization() {
let json = r#"{
"batch_id": "1030000071100999991182020050700019480101",
"out_batch_no": "batch_001",
"batch_status": "ACCEPT"
}"#;
let response: TransferResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.batch_status, "ACCEPT");
assert_eq!(
response.batch_id,
"1030000071100999991182020050700019480101"
);
}
#[test]
fn test_go_style_transfer_alias_signatures_exist() {
let _ = TransferService::initiate_batch_transfer;
let _ = TransferService::get_transfer_batch_by_out_batch_no;
}
}