paystack/endpoints/
transaction.rs

1//! Transactions
2//! =============
3//! The Transaction route allows to create and manage payments on your integration.
4
5use super::PAYSTACK_BASE_URL;
6use crate::{
7    ChargeRequest, ChargeResponseData, Currency, ExportTransactionData, HttpClient,
8    PartialDebitTransactionRequest, PaystackAPIError, PaystackResult, Response, Status,
9    TransactionIdentifier, TransactionRequest, TransactionResponseData, TransactionStatusData,
10    TransactionTimelineData, TransactionTotalData,
11};
12use std::sync::Arc;
13
14/// A struct to hold all the functions of the transaction API endpoint
15#[derive(Debug, Clone)]
16pub struct TransactionEndpoints<T: HttpClient + Default> {
17    /// Paystack API Key
18    key: String,
19    /// Base URL for the transaction route
20    base_url: String,
21    /// Http client for the route
22    http: Arc<T>,
23}
24
25impl<T: HttpClient + Default> TransactionEndpoints<T> {
26    /// Creates a new TransactionEndpoints instance
27    ///
28    /// # Arguments
29    /// * `key` - The Paystack API key
30    /// * `http` - The HTTP client implementation to use for API requests
31    ///
32    /// # Returns
33    /// A new TransactionEndpoints instance
34    pub fn new(key: Arc<String>, http: Arc<T>) -> TransactionEndpoints<T> {
35        let base_url = format!("{PAYSTACK_BASE_URL}/transaction");
36        TransactionEndpoints {
37            key: key.to_string(),
38            base_url,
39            http,
40        }
41    }
42
43    /// Initialize a transaction in your integration
44    ///
45    /// # Arguments
46    /// * `transaction_request` - The request data to initialize the transaction.
47    ///   Should be created with a `TransactionRequestBuilder` struct
48    ///
49    /// # Returns
50    /// A Result containing the transaction response data or an error
51    pub async fn initialize_transaction(
52        &self,
53        transaction_request: TransactionRequest,
54    ) -> PaystackResult<TransactionResponseData> {
55        let url = format!("{}/initialize", self.base_url);
56        let body = serde_json::to_value(transaction_request)
57            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
58
59        let response = self
60            .http
61            .post(&url, &self.key, &body)
62            .await
63            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
64
65        let parsed_response: Response<TransactionResponseData> = serde_json::from_str(&response)
66            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
67        Ok(parsed_response)
68    }
69
70    /// Verifies the status of a transaction
71    ///
72    /// # Arguments
73    /// * `reference` - The transaction reference used to initiate the transaction
74    ///
75    /// # Returns
76    /// A Result containing the transaction status data or an error
77    pub async fn verify_transaction(
78        &self,
79        reference: &str,
80    ) -> PaystackResult<TransactionStatusData> {
81        let url = format!("{}/verify/{}", self.base_url, reference);
82
83        let response = self
84            .http
85            .get(&url, &self.key, None)
86            .await
87            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
88
89        let parsed_response: Response<TransactionStatusData> = serde_json::from_str(&response)
90            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
91
92        Ok(parsed_response)
93    }
94
95    /// Lists transactions carried out on your integration
96    ///
97    /// # Arguments
98    /// * `per_page` - Optional number of transactions to return per page. Defaults to 10 if None
99    /// * `status` - Optional filter for transaction status. Defaults to Success if None
100    ///
101    /// # Returns
102    /// A Result containing a vector of transaction status data or an error
103    pub async fn list_transactions(
104        &self,
105        per_page: Option<u32>,
106        status: Option<Status>,
107    ) -> PaystackResult<Vec<TransactionStatusData>> {
108        let url = &self.base_url;
109
110        let per_page = per_page.unwrap_or(10).to_string();
111        let status = status.unwrap_or(Status::Success).to_string();
112        let query = vec![("perPage", per_page.as_str()), ("status", status.as_str())];
113
114        let response = self
115            .http
116            .get(url, &self.key, Some(&query))
117            .await
118            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
119
120        let parsed_response: Response<Vec<TransactionStatusData>> = serde_json::from_str(&response)
121            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
122
123        Ok(parsed_response)
124    }
125
126    /// Gets details of a specific transaction
127    ///
128    /// # Arguments
129    /// * `transaction_id` - The ID of the transaction to fetch
130    ///
131    /// # Returns
132    /// A Result containing the transaction status data or an error
133    pub async fn fetch_transactions(
134        &self,
135        transaction_id: u64,
136    ) -> PaystackResult<TransactionStatusData> {
137        let url = format!("{}/{}", self.base_url, transaction_id);
138
139        let response = self
140            .http
141            .get(&url, &self.key, None)
142            .await
143            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
144
145        let parsed_response: Response<TransactionStatusData> = serde_json::from_str(&response)
146            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
147
148        Ok(parsed_response)
149    }
150
151    /// Charges a reusable authorization
152    ///
153    /// # Arguments
154    /// * `charge_request` - The charge request data containing authorization details.
155    ///   Should be created with the `ChargeRequestBuilder` struct.
156    ///
157    /// # Returns
158    /// A Result containing the charge response data or an error
159    pub async fn charge_authorization(
160        &self,
161        charge_request: ChargeRequest,
162    ) -> PaystackResult<ChargeResponseData> {
163        let url = format!("{}/charge_authorization", self.base_url);
164        let body = serde_json::to_value(charge_request)
165            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
166
167        let response = self
168            .http
169            .post(&url, &self.key, &body)
170            .await
171            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
172
173        let parsed_response: Response<ChargeResponseData> =
174            serde_json::from_str(&response).map_err(|e| PaystackAPIError::Charge(e.to_string()))?;
175
176        Ok(parsed_response)
177    }
178
179    /// Views the timeline of a transaction
180    ///
181    /// # Arguments
182    /// * `identifier` - The transaction identifier (either ID or reference)
183    ///
184    /// # Returns
185    /// A Result containing the transaction timeline data or an error
186    pub async fn view_transaction_timeline(
187        &self,
188        identifier: TransactionIdentifier,
189    ) -> PaystackResult<TransactionTimelineData> {
190        // This is a hacky implementation to ensure that the transaction reference or id is not empty.
191        // If they are empty, a new url without them as parameter is created.
192        let url = match identifier {
193            TransactionIdentifier::Id(id) => Ok(format!("{}/timeline/{}", self.base_url, id)),
194            TransactionIdentifier::Reference(reference) => {
195                Ok(format!("{}/timeline/{}", self.base_url, &reference))
196            }
197        }?; // propagate the error upstream
198
199        let response = self
200            .http
201            .get(&url, &self.key, None)
202            .await
203            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
204
205        let parsed_response: Response<TransactionTimelineData> = serde_json::from_str(&response)
206            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
207
208        Ok(parsed_response)
209    }
210
211    /// Gets the total amount received on your account
212    ///
213    /// # Returns
214    /// A Result containing the transaction total data or an error
215    pub async fn total_transactions(&self) -> PaystackResult<TransactionTotalData> {
216        let url = format!("{}/totals", self.base_url);
217
218        let response = self
219            .http
220            .get(&url, &self.key, None)
221            .await
222            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
223
224        let parsed_response: Response<TransactionTotalData> = serde_json::from_str(&response)
225            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
226
227        Ok(parsed_response)
228    }
229
230    /// Exports a list of transactions
231    ///
232    /// # Arguments
233    /// * `status` - Optional status filter for transactions to export. Defaults to Success
234    /// * `currency` - Optional currency filter. Defaults to NGN
235    /// * `settled` - Optional filter for settled transactions. Defaults to false
236    ///
237    /// # Returns
238    /// A Result containing the export transaction data or an error
239    pub async fn export_transaction(
240        &self,
241        status: Option<Status>,
242        currency: Option<Currency>,
243        settled: Option<bool>,
244    ) -> PaystackResult<ExportTransactionData> {
245        let url = format!("{}/export", self.base_url);
246
247        // Specify a default option for settled transactions.
248        let settled = match settled {
249            Some(settled) => settled.to_string(),
250            None => String::from(""),
251        };
252
253        let status = status.unwrap_or(Status::Success).to_string();
254        let currency = currency.unwrap_or(Currency::NGN).to_string();
255
256        let query = vec![
257            ("status", status.as_str()),
258            ("currency", currency.as_str()),
259            ("settled", settled.as_str()),
260        ];
261
262        let response = self
263            .http
264            .get(&url, &self.key, Some(&query))
265            .await
266            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
267
268        let parsed_response = serde_json::from_str(&response)
269            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
270
271        Ok(parsed_response)
272    }
273
274    /// Performs a partial debit on a transaction
275    ///
276    /// # Arguments
277    /// * `partial_debit_transaction_request` - The request data for the partial debit.
278    ///   Must be created with the `PartialDebitTransactionBuilder` Struct.
279    ///
280    /// # Returns
281    /// A Result containing the transaction status data or an error
282    pub async fn partial_debit(
283        &self,
284        partial_debit_transaction_request: PartialDebitTransactionRequest,
285    ) -> PaystackResult<TransactionStatusData> {
286        let url = format!("{}/partial_debit", self.base_url);
287        let body = serde_json::to_value(partial_debit_transaction_request)
288            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
289
290        let response = self
291            .http
292            .post(&url, &self.key, &body)
293            .await
294            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
295
296        let parsed_response: Response<TransactionStatusData> = serde_json::from_str(&response)
297            .map_err(|e| PaystackAPIError::Transaction(e.to_string()))?;
298
299        Ok(parsed_response)
300    }
301}