maib_client/mia/
client.rs

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