pub mod builder;
pub use builder::WxPayClientBuilder;
use std::sync::Arc;
use crate::auth::{Credentials, Sha256RsaSigner, Sha256RsaVerifier, Signer, Verifier};
use crate::cert::CertManager;
use crate::config::WxPayConfig;
use crate::error::WxPayResult;
use crate::http::{HttpClient, ReqwestHttpClient};
use crate::notify::NotifyHandler;
use crate::services::certificate::CertificateService;
use crate::services::payments::{AppService, H5Service, JsapiService, NativeService};
use crate::services::profit_sharing::{
ProfitSharingFinishRequest, ProfitSharingFinishResponse, ProfitSharingRequest,
ProfitSharingResponse, ProfitSharingService, QueryProfitSharingRequest,
};
use crate::services::query::{QueryService, Transaction};
use crate::services::refund::{RefundResponse, RefundService};
use crate::services::transfer::{TransferRequest, TransferResponse, TransferService};
use crate::services::transport::TransportObserver;
pub struct WxPayClient {
config: Arc<WxPayConfig>,
#[allow(dead_code)]
http_client: Arc<dyn HttpClient>,
credentials: Arc<Credentials>,
signer: Arc<dyn Signer>,
verifier: Arc<dyn Verifier>,
cert_manager: Arc<CertManager>,
services: ServiceRegistry,
}
struct ServiceRegistry {
jsapi: Arc<JsapiService>,
native: Arc<NativeService>,
h5: Arc<H5Service>,
app: Arc<AppService>,
refund: Arc<RefundService>,
transfer: Arc<TransferService>,
profitsharing: Arc<ProfitSharingService>,
certificates: Arc<CertificateService>,
query: Arc<QueryService>,
}
impl WxPayClient {
fn create_services(
config: &Arc<WxPayConfig>,
http_client: &Arc<dyn HttpClient>,
signer: &Arc<dyn Signer>,
transport_observer: Option<Arc<dyn TransportObserver>>,
) -> ServiceRegistry {
ServiceRegistry {
jsapi: Arc::new(JsapiService::new_with_observer(
config.clone(),
http_client.clone(),
signer.clone(),
transport_observer.clone(),
)),
native: Arc::new(NativeService::new_with_observer(
config.clone(),
http_client.clone(),
signer.clone(),
transport_observer.clone(),
)),
h5: Arc::new(H5Service::new_with_observer(
config.clone(),
http_client.clone(),
signer.clone(),
transport_observer.clone(),
)),
app: Arc::new(AppService::new_with_observer(
config.clone(),
http_client.clone(),
signer.clone(),
transport_observer.clone(),
)),
refund: Arc::new(RefundService::new_with_observer(
config.clone(),
http_client.clone(),
signer.clone(),
transport_observer.clone(),
)),
transfer: Arc::new(TransferService::new_with_observer(
config.clone(),
http_client.clone(),
signer.clone(),
transport_observer.clone(),
)),
profitsharing: Arc::new(ProfitSharingService::new_with_observer(
config.clone(),
http_client.clone(),
signer.clone(),
transport_observer.clone(),
)),
certificates: Arc::new(CertificateService::new_with_observer(
config.clone(),
http_client.clone(),
signer.clone(),
transport_observer.clone(),
)),
query: Arc::new(QueryService::new_with_observer(
config.clone(),
http_client.clone(),
signer.clone(),
transport_observer,
)),
}
}
pub(crate) async fn new_with_components(
config: WxPayConfig,
http_client: Arc<dyn HttpClient>,
signer: Arc<dyn Signer>,
verifier: Arc<dyn Verifier>,
cert_manager: Arc<CertManager>,
transport_observer: Option<Arc<dyn TransportObserver>>,
) -> WxPayResult<Self> {
let config = Arc::new(config);
let credentials = Arc::new(Credentials::from_config(&config));
let services = Self::create_services(&config, &http_client, &signer, transport_observer);
Ok(Self {
config,
http_client,
credentials,
signer,
verifier,
cert_manager,
services,
})
}
pub async fn new(config: WxPayConfig) -> WxPayResult<Self> {
let config = Arc::new(config);
let http_client: Arc<dyn HttpClient> = Arc::new(
ReqwestHttpClient::builder()
.timeout(config.timeout)
.max_retries(config.max_retries)
.build()?,
);
let signer: Arc<dyn Signer> = Arc::new(Sha256RsaSigner::new(
&config.merchant_id,
&config.private_key,
&config.cert_serial_number,
)?);
let verifier: Arc<dyn Verifier> = Arc::new(Sha256RsaVerifier::new(
config.platform_certificates.clone(),
)?);
let cert_manager = Arc::new(CertManager::new());
Self::new_with_components(
(*config).clone(),
http_client,
signer,
verifier,
cert_manager,
None,
)
.await
}
pub fn builder() -> WxPayClientBuilder {
WxPayClientBuilder::new()
}
pub fn config(&self) -> &WxPayConfig {
&self.config
}
pub fn credentials(&self) -> &Credentials {
&self.credentials
}
pub fn signer(&self) -> &dyn Signer {
self.signer.as_ref()
}
pub fn verifier(&self) -> &dyn Verifier {
self.verifier.as_ref()
}
pub fn cert_manager(&self) -> &CertManager {
&self.cert_manager
}
pub fn jsapi(&self) -> &JsapiService {
&self.services.jsapi
}
pub fn native(&self) -> &NativeService {
&self.services.native
}
pub fn h5(&self) -> &H5Service {
&self.services.h5
}
pub fn app(&self) -> &AppService {
&self.services.app
}
pub fn refund(&self) -> &RefundService {
&self.services.refund
}
pub fn refunddomestic(&self) -> &RefundService {
self.refund()
}
pub fn transfer(&self) -> &TransferService {
&self.services.transfer
}
pub fn transferbatch(&self) -> &TransferService {
self.transfer()
}
pub fn profit_sharing(&self) -> &ProfitSharingService {
&self.services.profitsharing
}
pub fn profitsharing(&self) -> &ProfitSharingService {
self.profit_sharing()
}
pub fn certificates(&self) -> &CertificateService {
&self.services.certificates
}
pub fn query(&self) -> &QueryService {
&self.services.query
}
pub async fn query_order_by_out_trade_no(
&self,
out_trade_no: &str,
) -> WxPayResult<Transaction> {
self.query().query_order_by_out_trade_no(out_trade_no).await
}
pub async fn query_order_by_id(&self, transaction_id: &str) -> WxPayResult<Transaction> {
self.query().query_order_by_id(transaction_id).await
}
pub async fn query_by_out_refund_no(&self, out_refund_no: &str) -> WxPayResult<RefundResponse> {
self.refunddomestic()
.query_by_out_refund_no(out_refund_no)
.await
}
pub async fn initiate_batch_transfer(
&self,
request: &TransferRequest,
) -> WxPayResult<TransferResponse> {
self.transferbatch().initiate_batch_transfer(request).await
}
pub async fn get_transfer_batch_by_out_batch_no(
&self,
out_batch_no: &str,
) -> WxPayResult<TransferResponse> {
self.transferbatch()
.get_transfer_batch_by_out_batch_no(out_batch_no)
.await
}
pub async fn create_profit_sharing_order(
&self,
request: &ProfitSharingRequest,
) -> WxPayResult<ProfitSharingResponse> {
self.profitsharing().create_order(request).await
}
pub async fn query_profit_sharing_order(
&self,
request: &QueryProfitSharingRequest,
) -> WxPayResult<ProfitSharingResponse> {
self.profitsharing().query_order(request).await
}
pub async fn finish_profit_sharing_order(
&self,
request: &ProfitSharingFinishRequest,
) -> WxPayResult<ProfitSharingFinishResponse> {
self.profitsharing().finish_order(request).await
}
pub fn notify_handler(&self) -> WxPayResult<NotifyHandler> {
let config = crate::config::NotifyConfig::builder()
.api_v3_key(&self.config.api_v3_key)
.cert_serial_number(&self.config.cert_serial_number)
.platform_certificate(
self.config
.platform_certificates
.first()
.cloned()
.unwrap_or_default(),
)
.build()?;
NotifyHandler::new(config, self.verifier.clone())
}
}
impl std::fmt::Debug for WxPayClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WxPayClient")
.field("config", &self.config)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::services::profit_sharing::ProfitSharingService;
use crate::services::refund::RefundService;
use crate::services::transfer::TransferService;
fn create_test_config() -> WxPayConfig {
WxPayConfig::builder()
.app_id("wx88888888")
.merchant_id("1900000109")
.api_v3_key("abcdefghijklmnopqrstuvwxyz123456")
.private_key(vec![1, 2, 3, 4])
.cert_serial_number("CERT123456")
.build()
.unwrap()
}
#[test]
fn test_config() {
let config = create_test_config();
assert_eq!(config.app_id, "wx88888888");
assert_eq!(config.merchant_id, "1900000109");
}
#[test]
fn test_go_style_accessor_signatures_exist() {
let _: fn(&WxPayClient) -> &RefundService = WxPayClient::refunddomestic;
let _: fn(&WxPayClient) -> &TransferService = WxPayClient::transferbatch;
let _: fn(&WxPayClient) -> &ProfitSharingService = WxPayClient::profitsharing;
}
}