async_mpesa/
client.rs

1use serde::{de::DeserializeOwned, Serialize};
2
3use crate::{
4    accountbalance::AccountBalance, b2bexpress::B2bExpress, b2ctopup::B2bTopup, b2c::B2C, bbuygoods::Bbuygoods, bill_reconciliation::Reconciliation, billmanager::BillOnboarding, billupdate::BillUpdate, cancelinvoice::CancelInvoice, config::{Config, MpesaConfig}, error::{map_deserialization_error, ApiError, MpesaError}, expressquery::ExpressQuery, qr::Qr, ratiba::Ratiba, reversal::Reversal, singleinvoice::SingleInvoice, stkpush::STKPush, tax::Tax, transactionstatus::TransactionStatus
5};
6
7#[derive(Debug, Clone)]
8pub struct Client<C: Config> {
9    http_client: reqwest::Client,
10    config: C
11}
12
13impl Client<MpesaConfig> {
14    /// Client with default [MpesaConfig]
15    pub fn new() -> Self {
16        Self { 
17            http_client: reqwest::Client::new(), 
18            config: MpesaConfig::default() 
19        }
20    }
21}
22
23impl<C: Config> Client<C> {
24    /// Create client with [MpesaConfig]
25    pub fn with_config(config: C) -> Self {
26        Self { 
27            http_client: reqwest::Client::new(), 
28            config,
29        }
30    }
31
32    /// Provide your own [client] to make HTTP requests with
33    /// 
34    /// [client]: reqwest::Client
35    pub fn with_http_client(mut self, http_client: reqwest::Client) -> Self {
36        self.http_client = http_client;
37        self
38    }
39
40
41    // API groups
42    pub fn accountbalance(&self) -> AccountBalance<C> {
43        AccountBalance::new(self)
44    }
45
46    pub fn b2c(&self) -> B2C<C> {
47        B2C::new(self)
48    }
49
50    pub fn b2bexpress(&self) -> B2bExpress<C> {
51        B2bExpress::new(self)
52    }
53    
54    pub fn b2btopup(&self) -> B2bTopup<C> {
55        B2bTopup::new(self)
56    }
57
58    pub fn bbuygoods(&self) -> Bbuygoods<C> {
59        Bbuygoods::new(self)
60    }
61
62    pub fn billreconciliation(&self) -> Reconciliation<C> {
63        Reconciliation::new(self)
64    }
65
66    pub fn billupdate(&self) -> BillUpdate<C> {
67        BillUpdate::new(self)
68    }
69
70    pub fn billmanager(&self) -> BillOnboarding<C> {
71        BillOnboarding::new(self)
72    }
73
74    pub fn bpaybill(&self) -> Bbuygoods<C> {
75        Bbuygoods::new(self)
76    }
77
78    pub fn cancelinvoice(&self) -> CancelInvoice<C> {
79        CancelInvoice::new(self)
80    }
81
82    pub fn expressquery(&self) -> ExpressQuery<C> {
83        ExpressQuery::new(self)
84    }
85
86    pub fn qr(&self) -> Qr<C> {
87        Qr::new(self)
88    }
89
90    pub fn ratiba(&self) -> Ratiba<C> {
91        Ratiba::new(self)
92    }
93
94    pub fn reversal(&self) -> Reversal<C> {
95        Reversal::new(self)
96    }
97
98    pub fn singleinvoice(&self) -> SingleInvoice<C> {
99        SingleInvoice::new(self)
100    }
101
102    pub fn stkpush(&self) -> STKPush<C> {
103        STKPush::new(self)
104    }
105
106    pub fn taxremit(&self) -> Tax<C> {
107        Tax::new(self)
108    }
109
110    pub fn transactionstatus(&self) -> TransactionStatus<C> {
111        TransactionStatus::new(self)
112    }
113
114    /// builds the request and makes the post request to the api endpoint
115    pub(crate) async fn post<I, O>(&self, path: &str, request: I) -> Result<O, MpesaError>
116    where
117        I: Serialize + std::fmt::Debug,
118        O: DeserializeOwned,
119    {
120        let request = self
121            .http_client
122            .post(self.config.url(path))
123            .headers(self.config.headers())
124            .json(&request)
125            .build()?;
126        self.execute(request).await
127    }
128
129    /// handles the deserialization of a successful response or an error
130    async fn execute<O>(&self, request: reqwest::Request) -> Result<O, MpesaError>
131    where
132        O: DeserializeOwned,
133    {
134        let client = self.http_client.clone();
135
136        let response = client
137            .execute(request.try_into().unwrap())
138            .await
139            .map_err(MpesaError::Reqwest)?;
140
141        let status = response.status();
142
143        let bytes = response
144            .bytes()
145            .await
146            .map_err(MpesaError::Reqwest)?;
147
148        if !status.is_success() {
149            let wrapped_error: ApiError = serde_json::from_slice(bytes.as_ref())
150                .map_err(|e| map_deserialization_error(e, bytes.as_ref()))?;
151            
152            return Err(MpesaError::ApiError(wrapped_error));
153        }
154    
155        let response: O = serde_json::from_slice(bytes.as_ref())
156            .map_err(|e| map_deserialization_error(e, bytes.as_ref()))?;
157
158        Ok(response)
159    }
160    
161}