paystack/endpoints/
transactions.rs

1//! Transactions
2//! =============
3//! The Transactions API allows you create and manage payments on your integration.
4
5use crate::{
6    get_request, post_request, ChargeBody, Currency, Error, ExportTransactionResponse,
7    InitializeTransactionBody, PartialDebitTransactionBody, PaystackResult, Status,
8    TransactionResponse, TransactionStatusListResponse, TransactionStatusResponse,
9    TransactionTimelineResponse, TransactionTotalsResponse,
10};
11use reqwest::StatusCode;
12
13/// A Struct to hold all the functions of the transaction API route
14#[derive(Debug, Clone)]
15pub struct TransactionEndpoints<'a> {
16    /// Paystack API Key
17    api_key: &'a str,
18}
19
20static BASE_URL: &str = "https://api.paystack.co/transaction";
21
22impl<'a> TransactionEndpoints<'a> {
23    /// Constructor for the transaction object
24    pub fn new(key: &'a str) -> TransactionEndpoints<'a> {
25        TransactionEndpoints { api_key: key }
26    }
27
28    /// Initialize a transaction from your backend.
29    ///
30    /// It takes a Transaction type as its parameter
31    pub async fn initialize_transaction(
32        &self,
33        transaction_body: InitializeTransactionBody,
34    ) -> PaystackResult<TransactionResponse> {
35        let url = format!("{}/initialize", BASE_URL);
36
37        match post_request(self.api_key, &url, transaction_body).await {
38            Ok(response) => match response.status() {
39                StatusCode::OK => match response.json::<TransactionResponse>().await {
40                    Ok(content) => Ok(content),
41                    Err(err) => Err(Error::Transaction(err.to_string())),
42                },
43                _ => Err(Error::RequestNotSuccessful(
44                    response.status().to_string(),
45                    response.text().await?,
46                )),
47            },
48            Err(err) => Err(Error::FailedRequest(err.to_string())),
49        }
50    }
51
52    /// Confirm the status of a transaction.
53    ///
54    /// It takes the following parameters:
55    ///     - reference: The transaction reference used to initiate the transaction
56    pub async fn verify_transaction(
57        &self,
58        reference: &str,
59    ) -> PaystackResult<TransactionStatusResponse> {
60        let url = format!("{}/verify/{}", BASE_URL, reference);
61
62        match get_request(self.api_key, &url, None).await {
63            Ok(response) => match response.status() {
64                StatusCode::OK => match response.json::<TransactionStatusResponse>().await {
65                    Ok(content) => Ok(content),
66                    Err(err) => Err(Error::Transaction(err.to_string())),
67                },
68                _ => Err(Error::RequestNotSuccessful(
69                    response.status().to_string(),
70                    response.text().await?,
71                )),
72            },
73            Err(err) => Err(Error::FailedRequest(err.to_string())),
74        }
75    }
76
77    /// List transactions carried out on your integration.
78    ///
79    /// The method takes the following parameters:
80    ///     - perPage (Optional): Number of transactions to return. If None is passed as the parameter, the last 10 transactions are returned.
81    ///     - status (Optional): Filter transactions by status, defaults to Success if no status is passed.
82    ///
83    pub async fn list_transactions(
84        &self,
85        number_of_transactions: Option<u32>,
86        status: Option<Status>,
87    ) -> PaystackResult<TransactionStatusListResponse> {
88        let url = format!("{}", BASE_URL);
89
90        let per_page = number_of_transactions.unwrap_or(10).to_string();
91        let status = status.unwrap_or(Status::Success).to_string();
92        let query = vec![("perPage", per_page.as_str()), ("status", status.as_str())];
93
94        match get_request(self.api_key, &url, Some(query)).await {
95            Ok(response) => match response.status() {
96                StatusCode::OK => match response.json::<TransactionStatusListResponse>().await {
97                    Ok(content) => Ok(content),
98                    Err(err) => Err(Error::Transaction(err.to_string())),
99                },
100                _ => Err(Error::RequestNotSuccessful(
101                    response.status().to_string(),
102                    response.text().await?,
103                )),
104            },
105            Err(err) => Err(Error::FailedRequest(err.to_string())),
106        }
107    }
108
109    /// Get details of a transaction carried out on your integration.
110    ///
111    /// This methods take the Id of the desired transaction as a parameter
112    pub async fn fetch_transactions(
113        &self,
114        transaction_id: u32,
115    ) -> PaystackResult<TransactionStatusResponse> {
116        let url = format!("{}/{}", BASE_URL, transaction_id);
117
118        match get_request(self.api_key, &url, None).await {
119            Ok(response) => match response.status() {
120                StatusCode::OK => match response.json::<TransactionStatusResponse>().await {
121                    Ok(content) => Ok(content),
122                    Err(err) => Err(Error::Transaction(err.to_string())),
123                },
124                _ => Err(Error::RequestNotSuccessful(
125                    response.status().to_string(),
126                    response.text().await?,
127                )),
128            },
129            Err(err) => Err(Error::FailedRequest(err.to_string())),
130        }
131    }
132
133    /// All authorizations marked as reusable can be charged with this endpoint whenever you need to receive payments.
134    ///
135    /// This function takes a Charge Struct as parameter
136    pub async fn charge_authorization(
137        &self,
138        charge: ChargeBody,
139    ) -> PaystackResult<TransactionStatusResponse> {
140        let url = format!("{}/charge_authorization", BASE_URL);
141
142        match post_request(self.api_key, &url, charge).await {
143            Ok(response) => match response.status() {
144                StatusCode::OK => match response.json::<TransactionStatusResponse>().await {
145                    Ok(content) => Ok(content),
146                    Err(err) => Err(Error::Charge(err.to_string())),
147                },
148                _ => Err(Error::RequestNotSuccessful(
149                    response.status().to_string(),
150                    response.text().await?,
151                )),
152            },
153            Err(err) => Err(Error::FailedRequest(err.to_string())),
154        }
155    }
156
157    /// View the timeline of a transaction.
158    ///
159    /// This method takes in the Transaction id or reference as a parameter
160    pub async fn view_transaction_timeline(
161        &self,
162        id: Option<u32>,
163        reference: Option<&str>,
164    ) -> PaystackResult<TransactionTimelineResponse> {
165        // This is a hacky implementation to ensure that the transaction reference or id is not empty.
166        // If they are empty, a url without them as parameter is created.
167        let url: PaystackResult<String> = match (id, reference) {
168            (Some(id), None) => Ok(format!("{}/timeline/{}", BASE_URL, id)),
169            (None, Some(reference)) => Ok(format!("{}/timeline/{}", BASE_URL, &reference)),
170            _ => {
171                return Err(Error::Transaction(
172                    "Transaction Id or Reference is need to view transaction timeline".to_string(),
173                ))
174            }
175        };
176
177        let url = url.unwrap(); // Send the error back up the function
178
179        match get_request(self.api_key, &url, None).await {
180            Ok(response) => match response.status() {
181                StatusCode::OK => match response.json::<TransactionTimelineResponse>().await {
182                    Ok(content) => Ok(content),
183                    Err(err) => Err(Error::Transaction(err.to_string())),
184                },
185                _ => Err(Error::RequestNotSuccessful(
186                    response.status().to_string(),
187                    response.text().await?,
188                )),
189            },
190            Err(err) => Err(Error::FailedRequest(err.to_string())),
191        }
192    }
193
194    /// Total amount received on your account.
195    ///
196    /// This route normally takes a perPage or page query,
197    /// However in this case it is ignored.
198    /// If you need it in your work please open an issue
199    /// and it will be implemented.
200    pub async fn total_transactions(&self) -> PaystackResult<TransactionTotalsResponse> {
201        let url = format!("{}/totals", BASE_URL);
202
203        match get_request(self.api_key, &url, None).await {
204            Ok(response) => match response.status() {
205                StatusCode::OK => match response.json::<TransactionTotalsResponse>().await {
206                    Ok(content) => Ok(content),
207                    Err(err) => Err(Error::Transaction(err.to_string())),
208                },
209                _ => Err(Error::RequestNotSuccessful(
210                    response.status().to_string(),
211                    response.text().await?,
212                )),
213            },
214            Err(err) => Err(Error::FailedRequest(err.to_string())),
215        }
216    }
217
218    /// Export a list of transactions carried out on your integration.
219    ///
220    /// This method takes the following parameters
221    /// - Status (Optional): The status of the transactions to export. Defaults to all
222    /// - Currency (Optional): The currency of the transactions to export. Defaults to NGN
223    /// - Settled (Optional): To state of the transactions to export. Defaults to False.
224    pub async fn export_transaction(
225        &self,
226        status: Option<Status>,
227        currency: Option<Currency>,
228        settled: Option<bool>,
229    ) -> PaystackResult<ExportTransactionResponse> {
230        let url = format!("{}/export", BASE_URL);
231
232        // Specify a default option for settled transactions.
233        let settled = match settled {
234            Some(settled) => settled.to_string(),
235            None => String::from(""),
236        };
237
238        let status = status.unwrap_or(Status::Success).to_string();
239        let currency = currency.unwrap_or(Currency::EMPTY).to_string();
240
241        let query = vec![
242            ("status", status.as_str()),
243            ("currency", currency.as_str()),
244            ("settled", settled.as_str()),
245        ];
246
247        match get_request(self.api_key, &url, Some(query)).await {
248            Ok(response) => match response.status() {
249                StatusCode::OK => match response.json::<ExportTransactionResponse>().await {
250                    Ok(content) => Ok(content),
251                    Err(err) => Err(Error::Transaction(err.to_string())),
252                },
253                _ => Err(Error::RequestNotSuccessful(
254                    response.status().to_string(),
255                    response.text().await?,
256                )),
257            },
258            Err(err) => Err(Error::FailedRequest(err.to_string())),
259        }
260    }
261
262    /// Retrieve part of a payment from a customer.
263    ///
264    /// It takes a PartialDebitTransaction type as a parameter.
265    ///
266    /// NB: it must be created with the PartialDebitTransaction Builder.
267    pub async fn partial_debit(
268        &self,
269        transaction_body: PartialDebitTransactionBody,
270    ) -> PaystackResult<TransactionStatusResponse> {
271        let url = format!("{}/partial_debit", BASE_URL);
272
273        match post_request(self.api_key, &url, transaction_body).await {
274            Ok(response) => match response.status() {
275                StatusCode::OK => match response.json::<TransactionStatusResponse>().await {
276                    Ok(content) => Ok(content),
277                    Err(err) => Err(Error::Transaction(err.to_string())),
278                },
279                _ => Err(Error::RequestNotSuccessful(
280                    response.status().to_string(),
281                    response.text().await?,
282                )),
283            },
284            Err(err) => Err(Error::FailedRequest(err.to_string())),
285        }
286    }
287}