paystack/endpoints/
transaction.rs

1//! Transactions
2//! =============
3//! The Transaction route allows to create and manage payments on your integration.
4
5use crate::{
6    ChargeRequest, ChargeResponseData, Currency, ExportTransactionData, HttpClient,
7    PartialDebitTransactionRequest, PaystackAPIError, PaystackResult, Response, Status,
8    TransactionIdentifier, TransactionRequest, TransactionResponseData, TransactionStatusData,
9    TransactionTimelineData, TransactionTotalData,
10};
11use std::sync::Arc;
12
13/// A struct to hold all the functions of the transaction API endpoint
14#[derive(Debug, Clone)]
15pub struct TransactionEndpoints<T: HttpClient + Default> {
16    /// Paystack API Key
17    key: String,
18    /// Base URL for the transaction route
19    base_url: String,
20    /// Http client for the route
21    http: Arc<T>,
22}
23
24impl<T: HttpClient + Default> TransactionEndpoints<T> {
25    /// Constructor
26    pub fn new(key: Arc<String>, http: Arc<T>) -> TransactionEndpoints<T> {
27        let base_url = String::from("https://api.paystack.co/transaction");
28        TransactionEndpoints {
29            key: key.to_string(),
30            base_url,
31            http,
32        }
33    }
34
35    /// Initialize a transaction in your integration
36    ///
37    /// Takes a `TransactionRequest`struct as input.
38    pub async fn initialize_transaction(
39        &self,
40        transaction_request: TransactionRequest,
41    ) -> PaystackResult<TransactionResponseData> {
42        let url = format!("{}/initialize", self.base_url);
43        let body = serde_json::to_value(transaction_request)
44            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
45
46        let response = self.http.post(&url, &self.key, &body).await;
47
48        match response {
49            Ok(response) => {
50                let parsed_response: Response<TransactionResponseData> =
51                    serde_json::from_str(&response)
52                        .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
53                Ok(parsed_response)
54            }
55            Err(e) => {
56                // convert the error to a transaction error
57                Err(PaystackAPIError::Transaction(e.to_string()))
58            }
59        }
60    }
61
62    /// Confirm the status of a transaction.
63    ///
64    /// It takes the following parameters:
65    ///     - reference: The transaction reference used to initiate the transaction
66    pub async fn verify_transaction(
67        &self,
68        reference: &str,
69    ) -> PaystackResult<TransactionStatusData> {
70        let url = format!("{}/verify/{}", self.base_url, reference);
71
72        let response = self.http.get(&url, &self.key, None).await;
73
74        match response {
75            Ok(response) => {
76                let parsed_response: Response<TransactionStatusData> =
77                    serde_json::from_str(&response)
78                        .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
79
80                Ok(parsed_response)
81            }
82            Err(e) => Err(PaystackAPIError::Transaction(e.to_string())),
83        }
84    }
85
86    /// List transactions carried out on your integration.
87    ///
88    /// The method takes the following parameters:
89    ///     - perPage (Optional): Number of transactions to return. If None is passed as the parameter, the last 10 transactions are returned.
90    ///     - status (Optional): Filter transactions by status, defaults to Success if no status is passed.
91    ///
92    pub async fn list_transactions(
93        &self,
94        per_page: Option<u32>,
95        status: Option<Status>,
96    ) -> PaystackResult<Vec<TransactionStatusData>> {
97        let url = self.base_url.to_string();
98
99        let per_page = per_page.unwrap_or(10).to_string();
100        let status = status.unwrap_or(Status::Success).to_string();
101        let query = vec![("perPage", per_page.as_str()), ("status", status.as_str())];
102
103        let response = self.http.get(&url, &self.key, Some(&query)).await;
104
105        match response {
106            Ok(response) => {
107                let parsed_response: Response<Vec<TransactionStatusData>> =
108                    serde_json::from_str(&response)
109                        .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
110
111                Ok(parsed_response)
112            }
113            Err(e) => Err(PaystackAPIError::Transaction(e.to_string())),
114        }
115    }
116
117    /// Get details of a transaction carried out on your integration.
118    ///
119    /// This method take the ID of the desired transaction as a parameter
120    pub async fn fetch_transactions(
121        &self,
122        transaction_id: u64,
123    ) -> PaystackResult<TransactionStatusData> {
124        let url = format!("{}/{}", self.base_url, transaction_id);
125
126        let response = self.http.get(&url, &self.key, None).await;
127
128        match response {
129            Ok(response) => {
130                let parsed_response: Response<TransactionStatusData> =
131                    serde_json::from_str(&response)
132                        .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
133
134                Ok(parsed_response)
135            }
136            Err(e) => Err(PaystackAPIError::Transaction(e.to_string())),
137        }
138    }
139
140    /// All authorizations marked as reusable can be charged with this endpoint whenever you need to receive payments.
141    ///
142    /// This function takes a Charge Struct as parameter
143    pub async fn charge_authorization(
144        &self,
145        charge_request: ChargeRequest,
146    ) -> PaystackResult<ChargeResponseData> {
147        let url = format!("{}/charge_authorization", self.base_url);
148        let body = serde_json::to_value(charge_request)
149            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
150
151        let response = self.http.post(&url, &self.key, &body).await;
152
153        match response {
154            Ok(response) => {
155                let parsed_response: Response<ChargeResponseData> = serde_json::from_str(&response)
156                    .map_err(|e| PaystackAPIError::Charge(e.to_string()))?;
157
158                Ok(parsed_response)
159            }
160            Err(e) => Err(PaystackAPIError::Transaction(e.to_string())),
161        }
162    }
163
164    /// View the timeline of a transaction.
165    ///
166    /// This method takes in the TransactionIdentifier as a parameter
167    pub async fn view_transaction_timeline(
168        &self,
169        identifier: TransactionIdentifier,
170    ) -> PaystackResult<TransactionTimelineData> {
171        // This is a hacky implementation to ensure that the transaction reference or id is not empty.
172        // If they are empty, a new url without them as parameter is created.
173        let url = match identifier {
174            TransactionIdentifier::Id(id) => Ok(format!("{}/timeline/{}", self.base_url, id)),
175            TransactionIdentifier::Reference(reference) => {
176                Ok(format!("{}/timeline/{}", self.base_url, &reference))
177            }
178        }?; // propagate the error upstream
179
180        let response = self.http.get(&url, &self.key, None).await;
181
182        match response {
183            Ok(response) => {
184                let parsed_response: Response<TransactionTimelineData> =
185                    serde_json::from_str(&response)
186                        .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
187
188                Ok(parsed_response)
189            }
190            Err(e) => Err(PaystackAPIError::Transaction(e.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<TransactionTotalData> {
201        let url = format!("{}/totals", self.base_url);
202
203        let response = self.http.get(&url, &self.key, None).await;
204
205        match response {
206            Ok(response) => {
207                let parsed_response: Response<TransactionTotalData> =
208                    serde_json::from_str(&response)
209                        .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
210
211                Ok(parsed_response)
212            }
213            Err(e) => Err(PaystackAPIError::Transaction(e.to_string())),
214        }
215    }
216
217    /// Export a list of transactions carried out on your integration.
218    ///
219    /// This method takes the following parameters
220    /// - Status (Optional): The status of the transactions to export. Defaults to all
221    /// - Currency (Optional): The currency of the transactions to export. Defaults to NGN
222    /// - Settled (Optional): To state of the transactions to export. Defaults to False.
223    pub async fn export_transaction(
224        &self,
225        status: Option<Status>,
226        currency: Option<Currency>,
227        settled: Option<bool>,
228    ) -> PaystackResult<ExportTransactionData> {
229        let url = format!("{}/export", self.base_url);
230
231        // Specify a default option for settled transactions.
232        let settled = match settled {
233            Some(settled) => settled.to_string(),
234            None => String::from(""),
235        };
236
237        let status = status.unwrap_or(Status::Success).to_string();
238        let currency = currency.unwrap_or(Currency::NGN).to_string();
239
240        let query = vec![
241            ("status", status.as_str()),
242            ("currency", currency.as_str()),
243            ("settled", settled.as_str()),
244        ];
245
246        let response = self.http.get(&url, &self.key, Some(&query)).await;
247
248        match response {
249            Ok(response) => {
250                let parsed_response = serde_json::from_str(&response)
251                    .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
252
253                Ok(parsed_response)
254            }
255            Err(e) => Err(PaystackAPIError::Transaction(e.to_string())),
256        }
257    }
258
259    /// Retrieve part of a payment from a customer.
260    ///
261    /// It takes a PartialDebitTransaction type as a parameter.
262    ///
263    /// NB: it must be created with the PartialDebitTransaction Builder.
264    pub async fn partial_debit(
265        &self,
266        partial_debit_transaction_request: PartialDebitTransactionRequest,
267    ) -> PaystackResult<TransactionStatusData> {
268        let url = format!("{}/partial_debit", self.base_url);
269        let body = serde_json::to_value(partial_debit_transaction_request)
270            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
271
272        let response = self.http.post(&url, &self.key, &body).await;
273
274        match response {
275            Ok(response) => {
276                let parsed_response: Response<TransactionStatusData> =
277                    serde_json::from_str(&response)
278                        .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
279
280                Ok(parsed_response)
281            }
282            Err(e) => Err(PaystackAPIError::Transaction(e.to_string())),
283        }
284    }
285}