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 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}