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}