Skip to main content

maib_client/mia/
client.rs

1use crate::{
2    error::{Error, Result},
3    models::{
4        request::{self, CancelQR, GetAccessToken, RefundPayment},
5        response::{self, AuthToken},
6        AccessToken, ClientId, ClientSecret, PaymentId, QRId,
7    },
8};
9
10#[derive(Debug)]
11pub struct Client {
12    http_client: reqwest::Client,
13    api_base_url: String,
14}
15
16impl Client {
17    pub fn new(api_base_url: String) -> Self {
18        return Self {
19            http_client: reqwest::Client::new(),
20            api_base_url,
21        };
22    }
23
24    /// Attempt to fetch a new [AccessToken]
25    pub async fn get_access_token(
26        &self,
27        id: &ClientId,
28        secret: &ClientSecret,
29    ) -> Result<AuthToken> {
30        let body = GetAccessToken {
31            client_id: id,
32            client_secret: secret,
33        };
34
35        println!("ID: {}", body.client_id.as_str());
36        println!("Secret: {}", body.client_secret.as_str());
37
38        let input = SendRequestInput {
39            method: reqwest::Method::POST,
40            url: "/v2/auth/token",
41            token: None,
42            body: Some(body),
43        };
44        return self.send_request(input).await;
45    }
46
47    pub async fn create_qr<'a, 'b>(
48        &'a self,
49        payload: &request::CreateQR<'b>,
50        token: &'a AccessToken,
51    ) -> Result<response::CreateQRResponse> {
52        let input = SendRequestInput {
53            method: reqwest::Method::POST,
54            url: "/v2/mia/qr",
55            token: Some(token),
56            body: Some(payload),
57        };
58        return self.send_request(input).await;
59    }
60
61    pub async fn get_qr(
62        &self,
63        qr_id: &QRId,
64        token: &AccessToken,
65    ) -> Result<response::GetQRDetails> {
66        let url = format!("/v2/mia/qr/{}", qr_id.as_str());
67        let input: SendRequestInput<QRId> = SendRequestInput {
68            method: reqwest::Method::GET,
69            url: url.as_str(),
70            token: Some(token),
71            body: None,
72        };
73
74        return self.send_request(input).await;
75    }
76
77    pub async fn cancel_qr(
78        &self,
79        qr_id: &QRId,
80        payload: &CancelQR,
81        token: &AccessToken,
82    ) -> Result<response::CancelQR> {
83        let url = format!("/v2/mia/qr/{qr_id}/cancel");
84        let input = SendRequestInput {
85            method: reqwest::Method::POST,
86            url: url.as_str(),
87            token: Some(token),
88            body: Some(payload),
89        };
90
91        return self.send_request(input).await;
92    }
93
94    pub async fn get_payment(
95        &self,
96        id: &PaymentId,
97        token: &AccessToken,
98    ) -> Result<response::PaymentDetails> {
99        let url = format!("/v2/mia/payments/{id}");
100        let input: SendRequestInput<()> = SendRequestInput {
101            method: reqwest::Method::GET,
102            url: url.as_str(),
103            token: Some(token),
104            body: None,
105        };
106
107        return self.send_request(input).await;
108    }
109
110    pub async fn refund_payment(
111        &self,
112        id: &PaymentId,
113        payload: &RefundPayment,
114        token: &AccessToken,
115    ) -> Result<response::RefundPayment> {
116        let url = format!("/v2/mia/payments/{id}/refund");
117
118        let input = SendRequestInput {
119            method: reqwest::Method::POST,
120            url: &url,
121            token: Some(token),
122            body: Some(payload),
123        };
124
125        return self.send_request(input).await;
126    }
127
128    async fn send_request<'a, B, R>(&self, input: SendRequestInput<'a, B>) -> Result<R>
129    where
130        B: serde::Serialize,
131        R: serde::de::DeserializeOwned + core::fmt::Debug,
132    {
133        use reqwest::header::{self, HeaderMap, HeaderValue};
134
135        let mut headers: HeaderMap<HeaderValue> = HeaderMap::new();
136        headers.insert(
137            header::ACCEPT,
138            HeaderValue::from_str("application/json").unwrap(),
139        );
140
141        if input.method != reqwest::Method::GET {
142            headers.insert(
143                header::CONTENT_TYPE,
144                HeaderValue::from_str("application/json").unwrap(),
145            );
146        }
147
148        if let Some(token) = input.token {
149            let value = format!("Bearer {}", token.as_str());
150            headers.insert(
151                header::AUTHORIZATION,
152                HeaderValue::from_str(&value).unwrap(),
153            );
154        }
155
156        let url = format!("{}{}", &self.api_base_url, input.url);
157        let mut req = self.http_client.request(input.method, url).headers(headers);
158
159        if let Some(ref body) = input.body {
160            req = req.json(body);
161        }
162
163        let res = req.send().await.map_err(|err| {
164            let source = report(&err);
165            eprintln!("{source}");
166            return Error::Http(format!("error sending request: {err}"));
167        })?;
168
169        if res.status() == 401 {
170            return Err(Error::Unauthorized);
171        }
172
173        let res: response::ApiResponse<R> = res
174            .json()
175            .await
176            .map_err(|err| Error::Json(format!("error parsing response: {err}")))?;
177
178        if res.result.is_some() {
179            return Ok(res.result.unwrap());
180        }
181
182        return Err(Error::Api(res.errors.unwrap()));
183    }
184}
185
186struct SendRequestInput<'a, B: serde::Serialize> {
187    method: reqwest::Method,
188    url: &'a str,
189    token: Option<&'a AccessToken>,
190    body: Option<B>,
191}
192
193fn report(mut err: &dyn std::error::Error) -> String {
194    use std::fmt::Write;
195
196    let mut s = format!("{}", err);
197    while let Some(src) = err.source() {
198        let _ = write!(s, "\n\nCaused by: {}", src);
199        err = src;
200    }
201    s
202}