gocardless_unofficial/client.rs
1use secrecy::{ExposeSecret, SecretString};
2use serde_json::json;
3
4use crate::model::*;
5
6const URL_CREATE_TOKEN: &str = "https://bankaccountdata.gocardless.com/api/v2/token/new/";
7const URL_GET_INSTITUTIONS: &str =
8 "https://bankaccountdata.gocardless.com/api/v2/institutions/?country=gb"; // TODO: make country a variable
9const URL_CREATE_END_USER_AGREEMENT: &str =
10 "https://bankaccountdata.gocardless.com/api/v2/agreements/enduser/";
11const URL_REQUISITIONS: &str = "https://bankaccountdata.gocardless.com/api/v2/requisitions/";
12
13/// `Client` is a public struct that represents a client for making requests to the API.
14///
15/// Fields:
16/// * `req_client`: A `reqwest::Client` instance used for making HTTP requests.
17/// * `secret_id`: A `SecretString` that represents the client's secret ID.
18/// * `secret_key`: A `SecretString` that represents the client's secret key.
19/// * `created_token`: An `Option<CreateTokenResponse>` that represents the token created by the client. It is `None` if no token has been created yet.
20///
21/// The `Client` struct is used to interact with the API. It uses the `reqwest` crate for making HTTP requests and the `secrecy` crate for handling secret strings.
22/// The `secret_id` and `secret_key` are used for authentication with the API.
23/// The `created_token` field is used to store the token received from the API after successful authentication.
24pub struct Client {
25 req_client: reqwest::Client,
26 secret_id: SecretString,
27 secret_key: SecretString,
28 created_token: Option<CreateTokenResponse>,
29}
30
31impl Client {
32 /// `new` is an associated function that creates a new instance of the `Client` struct.
33 ///
34 /// # Arguments
35 ///
36 /// * `secret_id`: An implementor of the `Into<SecretString>` trait. This is converted into a `SecretString` that represents the client's secret ID.
37 /// * `secret_key`: An implementor of the `Into<SecretString>` trait. This is converted into a `SecretString` that represents the client's secret key.
38 ///
39 /// # Returns
40 ///
41 /// This function returns a `Client` instance.
42 ///
43 /// # Examples
44 ///
45 /// ```
46 /// let secret_id = "my_secret_id".to_string();
47 /// let secret_key = "my_secret_key".to_string();
48 /// let client = Client::new(secret_id, secret_key);
49 /// ```
50 ///
51 /// # Async
52 ///
53 /// This function is async and should be awaited.
54 pub async fn new(
55 secret_id: impl Into<SecretString>,
56 secret_key: impl Into<SecretString>,
57 ) -> Result<Self, Box<dyn std::error::Error>> {
58 let req_client = reqwest::Client::new();
59
60 let mut c = Client {
61 req_client,
62 secret_id: secret_id.into(),
63 secret_key: secret_key.into(),
64 created_token: None,
65 };
66
67 let created_token = c.create_token().await?;
68 c.created_token = Some(created_token);
69
70 Ok(c)
71 }
72
73 /// `create_token` is an async method that sends a POST request to the `URL_CREATE_TOKEN` endpoint to create a new token.
74 ///
75 /// # Returns
76 ///
77 /// This method returns a `Result` that is either a `CreateTokenResponse` on success or a `Box<dyn std::error::Error>` on failure.
78 ///
79 /// # Async
80 ///
81 /// This method is async and should be awaited.
82 ///
83 /// # Examples
84 ///
85 /// ```
86 /// let secret_id = "my_secret_id".to_string();
87 /// let secret_key = "my_secret_key".to_string();
88 /// let mut client = Client::new(secret_id, secret_key).await?;
89 /// let create_token_response = client.create_token().await?;
90 /// ```
91 ///
92 /// This method is typically called within the `Client::new` method to automatically create a token when a new `Client` is created.
93 pub async fn create_token(&self) -> Result<CreateTokenResponse, Box<dyn std::error::Error>> {
94 let response: CreateTokenResponse = self
95 .req_client
96 .post(URL_CREATE_TOKEN)
97 .body(
98 json!({
99 "secret_id": self.secret_id.expose_secret(),
100 "secret_key": self.secret_key.expose_secret(),
101 })
102 .to_string(),
103 )
104 .header("Accept", "application/json")
105 .header("Content-Type", "application/json")
106 .send()
107 .await?
108 .json()
109 .await?;
110
111 Ok(response)
112 }
113
114 /// `get_institutions` is an async method that sends a GET request to the `URL_GET_INSTITUTIONS` endpoint to retrieve a list of institutions.
115 ///
116 /// # Returns
117 ///
118 /// This method returns a `Result` that is either a `Vec<Institution>` on success or a `Box<dyn std::error::Error>` on failure.
119 ///
120 /// # Async
121 ///
122 /// This method is async and should be awaited.
123 ///
124 /// # Examples
125 ///
126 /// ```
127 /// let secret_id = "my_secret_id".to_string();
128 /// let secret_key = "my_secret_key".to_string();
129 /// let mut client = Client::new(secret_id, secret_key).await?;
130 /// let institutions = client.get_institutions().await?;
131 /// ```
132 ///
133 /// This method requires that a token has been created and stored in the `created_token` field of the `Client` struct. If no token has been created, this method will return an error.
134 pub async fn get_institutions(&self) -> Result<Vec<Institution>, Box<dyn std::error::Error>> {
135 let access_token = self.created_token.clone().unwrap().access;
136
137 let response: Vec<Institution> = self
138 .req_client
139 .get(URL_GET_INSTITUTIONS)
140 .header("Accept", "application/json")
141 .header("Authorization", format!("Bearer {}", access_token))
142 .send()
143 .await?
144 .json()
145 .await?;
146
147 Ok(response)
148 }
149
150 /// `create_end_user_agreement` is an async method that sends a POST request to the `URL_CREATE_END_USER_AGREEMENT` endpoint to create an end user agreement.
151 ///
152 /// # Arguments
153 ///
154 /// * `institution_id`: A reference to a string that represents the ID of the institution for which the end user agreement is being created.
155 ///
156 /// # Returns
157 ///
158 /// This method returns a `Result` that is either an `EndUserAgreement` on success or a `Box<dyn std::error::Error>` on failure.
159 ///
160 /// # Async
161 ///
162 /// This method is async and should be awaited.
163 ///
164 /// # Examples
165 ///
166 /// ```
167 /// let secret_id = "my_secret_id".to_string();
168 /// let secret_key = "my_secret_key".to_string();
169 /// let mut client = Client::new(secret_id, secret_key).await?;
170 /// let institution_id = "institution_id".to_string();
171 /// let end_user_agreement = client.create_end_user_agreement(&institution_id).await?;
172 /// ```
173 ///
174 /// This method requires that a token has been created and stored in the `created_token` field of the `Client` struct. If no token has been created, this method will return an error.
175 pub async fn create_end_user_agreement(
176 &self,
177 institution_id: &str,
178 max_historical_days: i32,
179 ) -> Result<EndUserAgreement, Box<dyn std::error::Error>> {
180 let access_token = self.created_token.clone().unwrap().access;
181
182 let response = self
183 .req_client
184 .post(URL_CREATE_END_USER_AGREEMENT)
185 .body(
186 json!({
187 "institution_id": institution_id,
188 "max_historical_days": max_historical_days,
189 "access_valid_for_days": "30",
190 "access_scope": [
191 "balances",
192 "details",
193 "transactions"
194 ]
195 })
196 .to_string(),
197 )
198 .header("Accept", "application/json")
199 .header("Content-Type", "application/json")
200 .header("Authorization", format!("Bearer {}", access_token))
201 .send()
202 .await?
203 .text()
204 .await?;
205
206 let agreement: EndUserAgreement = serde_json::from_str(&response)?;
207
208 Ok(agreement)
209 }
210
211 /// `list_requisitions` is an async method that sends a GET request to the `URL_REQUISITIONS` endpoint to retrieve a list of requisitions.
212 ///
213 /// # Returns
214 ///
215 /// This method returns a `Result` that is either a `ListRequisitionsResponse` on success or a `Box<dyn std::error::Error>` on failure.
216 ///
217 /// # Async
218 ///
219 /// This method is async and should be awaited.
220 ///
221 /// # Examples
222 ///
223 /// ```
224 /// let secret_id = "my_secret_id".to_string();
225 /// let secret_key = "my_secret_key".to_string();
226 /// let mut client = Client::new(secret_id, secret_key).await?;
227 /// let requisitions = client.list_requisitions().await?;
228 /// ```
229 ///
230 /// This method requires that a token has been created and stored in the `created_token` field of the `Client` struct. If no token has been created, this method will return an error.
231 pub async fn list_requisitions(
232 &self,
233 ) -> Result<ListRequisitionsResponse, Box<dyn std::error::Error>> {
234 let access_token = self.created_token.clone().unwrap().access;
235
236 let response: ListRequisitionsResponse = self
237 .req_client
238 .get(URL_REQUISITIONS)
239 .header("Accept", "application/json")
240 .header("Authorization", format!("Bearer {}", access_token))
241 .send()
242 .await?
243 .json()
244 .await?;
245
246 Ok(response)
247 }
248
249 /// `create_requisition` is an async method that sends a POST request to the `URL_REQUISITIONS` endpoint to create a new requisition.
250 ///
251 /// # Arguments
252 ///
253 /// * `redirect`: A reference to a string that represents the URL to which the user will be redirected after completing the requisition.
254 /// * `institution_id`: A reference to a string that represents the ID of the institution for which the requisition is being created.
255 /// * `agreement_id`: A reference to a string that represents the ID of the end user agreement associated with the requisition.
256 /// * `reference`: A reference to a string that represents a unique reference for the requisition.
257 ///
258 /// # Returns
259 ///
260 /// This method returns a `Result` that is either a `Requisition` on success or a `Box<dyn std::error::Error>` on failure.
261 ///
262 /// # Async
263 ///
264 /// This method is async and should be awaited.
265 ///
266 /// # Examples
267 ///
268 /// ```
269 /// let secret_id = "my_secret_id".to_string();
270 /// let secret_key = "my_secret_key".to_string();
271 /// let mut client = Client::new(secret_id, secret_key).await?;
272 /// let redirect = "http://localhost:3000/callback".to_string();
273 /// let institution_id = "institution_id".to_string();
274 /// let agreement_id = "agreement_id".to_string();
275 /// let reference = "reference".to_string();
276 /// let requisition = client.create_requisition(&redirect, &institution_id, Some(&agreement_id), Some(&reference)).await?;
277 /// ```
278 ///
279 /// This method requires that a token has been created and stored in the `created_token` field of the `Client` struct. If no token has been created, this method will return an error.
280 pub async fn create_requisition(
281 &self,
282 redirect: &str,
283 institution_id: &str,
284 agreement_id: Option<&str>,
285 reference: Option<&str>,
286 ) -> Result<Requisition, Box<dyn std::error::Error>> {
287 let access_token = self.created_token.clone().unwrap().access;
288
289 let mut request = json!({
290 "redirect": redirect,
291 "institution_id": institution_id,
292 "user_language": "EN" // TODO: configurable
293 });
294 if let Some(reference) = reference {
295 request["reference"] = json!(reference);
296 }
297 if let Some(agreement_id) = agreement_id {
298 request["agreement"] = json!(agreement_id);
299 }
300
301 let response: Requisition = self
302 .req_client
303 .post(URL_REQUISITIONS)
304 .body(request.to_string())
305 .header("Accept", "application/json")
306 .header("Content-Type", "application/json")
307 .header("Authorization", format!("Bearer {}", access_token))
308 .send()
309 .await?
310 .json()
311 .await?;
312
313 Ok(response)
314 }
315
316 /// `list_transactions` is an async method that sends a GET request to the `https://bankaccountdata.gocardless.com/api/v2/accounts/{account_id}/transactions` endpoint to retrieve a list of transactions for a specific account.
317 ///
318 /// # Arguments
319 ///
320 /// * `account_id`: A reference to a string that represents the ID of the account for which the transactions are being retrieved.
321 ///
322 /// # Returns
323 ///
324 /// This method returns a `Result` that is either a `ListTransactionsResponse` on success or a `Box<dyn std::error::Error>` on failure.
325 ///
326 /// # Async
327 ///
328 /// This method is async and should be awaited.
329 ///
330 /// # Examples
331 ///
332 /// ```
333 /// let secret_id = "my_secret_id".to_string();
334 /// let secret_key = "my_secret_key".to_string();
335 /// let mut client = Client::new(secret_id, secret_key).await?;
336 /// let account_id = "account_id".to_string();
337 /// let transactions = client.list_transactions(&account_id).await?;
338 /// ```
339 ///
340 /// This method requires that a token has been created and stored in the `created_token` field of the `Client` struct. If no token has been created, this method will return an error.
341 pub async fn list_transactions(
342 &self,
343 account_id: &str,
344 ) -> Result<ListTransactionsResponse, Box<dyn std::error::Error>> {
345 let access_token = self.created_token.clone().unwrap().access;
346
347 let response = self
348 .req_client
349 .get(format!(
350 "https://bankaccountdata.gocardless.com/api/v2/accounts/{}/transactions",
351 account_id
352 ))
353 .header("Accept", "application/json")
354 .header("Authorization", format!("Bearer {}", access_token))
355 .send()
356 .await?
357 .text()
358 .await?;
359
360 let parsed: ListTransactionsResponse = serde_json::from_str(&response)?;
361
362 Ok(parsed)
363 }
364
365 /// `list_balances` is an async method that sends a GET request to the `https://bankaccountdata.gocardless.com/api/v2/accounts/{account_id}/balances` endpoint to retrieve a list of balances for a specific account.
366 ///
367 /// # Arguments
368 ///
369 /// * `account_id`: A reference to a string that represents the ID of the account for which the balances are being retrieved.
370 ///
371 /// # Returns
372 ///
373 /// This method returns a `Result` that is either a `ListBalancesResponse` on success or a `Box<dyn std::error::Error>` on failure.
374 ///
375 /// # Async
376 ///
377 /// This method is async and should be awaited.
378 ///
379 /// # Examples
380 ///
381 /// ```
382 /// let secret_id = "my_secret_id".to_string();
383 /// let secret_key = "my_secret_key".to_string();
384 /// let mut client = Client::new(secret_id, secret_key).await?;
385 /// let account_id = "account_id".to_string();
386 /// let balances = client.list_balances(&account_id).await?;
387 /// ```
388 ///
389 /// This method requires that a token has been created and stored in the `created_token` field of the `Client` struct. If no token has been created, this method will return an error.
390 pub async fn list_balances(
391 &self,
392 account_id: &str,
393 ) -> Result<ListBalancesResponse, Box<dyn std::error::Error>> {
394 let access_token = self.created_token.clone().unwrap().access;
395
396 let response: ListBalancesResponse = self
397 .req_client
398 .get(format!(
399 "https://bankaccountdata.gocardless.com/api/v2/accounts/{}/balances",
400 account_id
401 ))
402 .header("Accept", "application/json")
403 .header("Authorization", format!("Bearer {}", access_token))
404 .send()
405 .await?
406 .json()
407 .await?;
408
409 Ok(response)
410 }
411
412 /// `get_account_details` is an async method that sends a GET request to the `https://bankaccountdata.gocardless.com/api/v2/accounts/{account_id}/details` endpoint to retrieve the details of a specific account.
413 ///
414 /// # Arguments
415 ///
416 /// * `account_id`: A reference to a string that represents the ID of the account for which the details are being retrieved.
417 ///
418 /// # Returns
419 ///
420 /// This method returns a `Result` that is either an `AccountDetailsResponse` on success or a `Box<dyn std::error::Error>` on failure.
421 ///
422 /// # Async
423 ///
424 /// This method is async and should be awaited.
425 ///
426 /// # Examples
427 ///
428 /// ```
429 /// let secret_id = "my_secret_id".to_string();
430 /// let secret_key = "my_secret_key".to_string();
431 /// let mut client = Client::new(secret_id, secret_key).await?;
432 /// let account_id = "account_id".to_string();
433 /// let account_details = client.get_account_details(&account_id).await?;
434 /// ```
435 ///
436 /// This method requires that a token has been created and stored in the `created_token` field of the `Client` struct. If no token has been created, this method will return an error.
437 pub async fn get_account_details(
438 &self,
439 account_id: &str,
440 ) -> Result<AccountDetailsResponse, Box<dyn std::error::Error>> {
441 let access_token = self.created_token.clone().unwrap().access;
442
443 let response: AccountDetailsResponse = self
444 .req_client
445 .get(format!(
446 "https://bankaccountdata.gocardless.com/api/v2/accounts/{}/details",
447 account_id
448 ))
449 .header("Accept", "application/json")
450 .header("Authorization", format!("Bearer {}", access_token))
451 .send()
452 .await?
453 .json()
454 .await?;
455
456 Ok(response)
457 }
458}