sumup_rs/transactions.rs
1use crate::{Result, SumUpClient, Transaction, TransactionHistoryResponse};
2use serde::Serialize;
3
4#[derive(Debug, Clone, Serialize, Default)]
5pub struct TransactionHistoryQuery<'a> {
6 #[serde(skip_serializing_if = "Option::is_none")]
7 pub limit: Option<i32>,
8 #[serde(skip_serializing_if = "Option::is_none")]
9 pub order: Option<&'a str>,
10 #[serde(skip_serializing_if = "Option::is_none")]
11 pub newest_time: Option<&'a str>,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub oldest_time: Option<&'a str>,
14 // Add other query parameters as needed
15}
16
17impl SumUpClient {
18 /// Lists detailed history of all transactions associated with the merchant profile.
19 /// Uses the modern v2.1 endpoint.
20 ///
21 /// # Arguments
22 /// * `merchant_code` - The merchant's unique code.
23 /// * `query` - A struct with query parameters for filtering and pagination.
24 pub async fn list_transactions_history(
25 &self,
26 merchant_code: &str,
27 query: &TransactionHistoryQuery<'_>,
28 ) -> Result<TransactionHistoryResponse> {
29 let url = self.build_url(&format!(
30 "/v2.1/merchants/{}/transactions/history",
31 merchant_code
32 ))?;
33
34 let response = self
35 .http_client
36 .get(url)
37 .bearer_auth(&self.api_key)
38 .query(query)
39 .send()
40 .await?;
41
42 if response.status().is_success() {
43 let history = response.json::<TransactionHistoryResponse>().await?;
44 Ok(history)
45 } else {
46 self.handle_error(response).await
47 }
48 }
49
50 /// Retrieves the full details of an identified transaction.
51 /// Uses the modern v2.1 endpoint.
52 ///
53 /// # Arguments
54 /// * `merchant_code` - The merchant's unique code.
55 /// * `transaction_id` - The transaction's unique ID.
56 pub async fn retrieve_transaction_by_id(
57 &self,
58 merchant_code: &str,
59 transaction_id: &str,
60 ) -> Result<Transaction> {
61 let mut url = self.build_url(&format!("/v2.1/merchants/{}/transactions", merchant_code))?;
62 url.query_pairs_mut().append_pair("id", transaction_id);
63
64 let response = self
65 .http_client
66 .get(url)
67 .bearer_auth(&self.api_key)
68 .send()
69 .await?;
70
71 if response.status().is_success() {
72 let transaction = response.json::<Transaction>().await?;
73 Ok(transaction)
74 } else {
75 self.handle_error(response).await
76 }
77 }
78
79 /// Refunds a transaction.
80 ///
81 /// # Arguments
82 /// * `merchant_code` - The merchant's unique code.
83 /// * `transaction_id` - The transaction's unique ID.
84 /// * `amount` - The amount to refund (optional, defaults to full amount).
85 /// * `reason` - The reason for the refund.
86 pub async fn refund_transaction(
87 &self,
88 merchant_code: &str,
89 transaction_id: &str,
90 amount: Option<f64>,
91 reason: &str,
92 ) -> Result<Transaction> {
93 let url = self.build_url(&format!(
94 "/v0.1/merchants/{}/transactions/{}/refunds",
95 merchant_code, transaction_id
96 ))?;
97
98 let mut body = serde_json::Map::new();
99 body.insert(
100 "reason".to_string(),
101 serde_json::Value::String(reason.to_string()),
102 );
103 if let Some(amt) = amount {
104 body.insert(
105 "amount".to_string(),
106 serde_json::Value::Number(serde_json::Number::from_f64(amt).unwrap()),
107 );
108 }
109
110 let response = self
111 .http_client
112 .post(url)
113 .bearer_auth(&self.api_key)
114 .json(&body)
115 .send()
116 .await?;
117
118 if response.status().is_success() {
119 let transaction = response.json::<Transaction>().await?;
120 Ok(transaction)
121 } else {
122 self.handle_error(response).await
123 }
124 }
125}
126
127// --- Pagination Helpers ---
128
129impl SumUpClient {
130 /// Extracts the next page URL from a transaction history response.
131 /// Returns `None` if there are no more pages.
132 ///
133 /// # Arguments
134 /// * `history` - The transaction history response to extract from
135 ///
136 /// # Returns
137 /// * `Some(url)` - The URL for the next page, if available
138 /// * `None` - If there are no more pages
139 ///
140 /// # Examples
141 ///
142 /// ```rust,no_run
143 /// use sumup_rs::{SumUpClient, TransactionHistoryQuery};
144 ///
145 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
146 /// let client = SumUpClient::new("your-api-key".to_string(), true)?;
147 /// let query = TransactionHistoryQuery {
148 /// limit: Some(10),
149 /// order: Some("desc"),
150 /// newest_time: None,
151 /// oldest_time: None,
152 /// };
153 /// let history = client.list_transactions_history("merchant123", &query).await?;
154 ///
155 /// if let Some(next_url) = SumUpClient::get_next_page_url_from_history(&history) {
156 /// println!("Next page available at: {}", next_url);
157 /// } else {
158 /// println!("No more pages available");
159 /// }
160 /// # Ok(())
161 /// # }
162 /// ```
163 pub fn get_next_page_url_from_history(history: &TransactionHistoryResponse) -> Option<String> {
164 history
165 .links
166 .iter()
167 .find(|link| link.rel == "next")
168 .map(|link| link.href.clone())
169 }
170
171 /// Extracts the previous page URL from a transaction history response.
172 /// Returns `None` if there is no previous page.
173 ///
174 /// # Arguments
175 /// * `history` - The transaction history response to extract from
176 ///
177 /// # Returns
178 /// * `Some(url)` - The URL for the previous page, if available
179 /// * `None` - If there is no previous page
180 ///
181 /// # Examples
182 ///
183 /// ```rust,no_run
184 /// use sumup_rs::{SumUpClient, TransactionHistoryQuery};
185 ///
186 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
187 /// let client = SumUpClient::new("your-api-key".to_string(), true)?;
188 /// let query = TransactionHistoryQuery {
189 /// limit: Some(10),
190 /// order: Some("desc"),
191 /// newest_time: None,
192 /// oldest_time: None,
193 /// };
194 /// let history = client.list_transactions_history("merchant123", &query).await?;
195 ///
196 /// if let Some(prev_url) = SumUpClient::get_previous_page_url_from_history(&history) {
197 /// println!("Previous page available at: {}", prev_url);
198 /// } else {
199 /// println!("No previous page available");
200 /// }
201 /// # Ok(())
202 /// # }
203 /// ```
204 pub fn get_previous_page_url_from_history(
205 history: &TransactionHistoryResponse,
206 ) -> Option<String> {
207 history
208 .links
209 .iter()
210 .find(|link| link.rel == "prev")
211 .map(|link| link.href.clone())
212 }
213
214 /// Checks if there are more pages available in a transaction history response.
215 ///
216 /// # Arguments
217 /// * `history` - The transaction history response to check
218 ///
219 /// # Returns
220 /// * `true` - If there are more pages available
221 /// * `false` - If this is the last page
222 ///
223 /// # Examples
224 ///
225 /// ```rust,no_run
226 /// use sumup_rs::{SumUpClient, TransactionHistoryQuery};
227 ///
228 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
229 /// let client = SumUpClient::new("your-api-key".to_string(), true)?;
230 /// let query = TransactionHistoryQuery {
231 /// limit: Some(10),
232 /// order: Some("desc"),
233 /// newest_time: None,
234 /// oldest_time: None,
235 /// };
236 /// let history = client.list_transactions_history("merchant123", &query).await?;
237 ///
238 /// if SumUpClient::has_next_page_from_history(&history) {
239 /// println!("More pages available");
240 /// } else {
241 /// println!("This is the last page");
242 /// }
243 /// # Ok(())
244 /// # }
245 /// ```
246 pub fn has_next_page_from_history(history: &TransactionHistoryResponse) -> bool {
247 history.links.iter().any(|link| link.rel == "next")
248 }
249
250 /// Fetches all transactions for a merchant by automatically handling pagination.
251 /// This is a convenience method that fetches all pages and combines the results.
252 ///
253 /// # Arguments
254 /// * `merchant_code` - The merchant's unique code
255 /// * `order` - Sort order (e.g., "asc", "desc")
256 /// * `max_pages` - Maximum number of pages to fetch (None for unlimited)
257 ///
258 /// # Returns
259 /// * `Vec<Transaction>` - All transactions from all pages
260 ///
261 /// # Examples
262 ///
263 /// ```rust,no_run
264 /// use sumup_rs::SumUpClient;
265 ///
266 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
267 /// let client = SumUpClient::new("your-api-key".to_string(), true)?;
268 ///
269 /// // Fetch all transactions (up to 5 pages to avoid overwhelming the API)
270 /// let all_transactions = client.list_all_transactions_history("merchant123", Some("desc"), Some(5)).await?;
271 /// println!("Fetched {} transactions", all_transactions.len());
272 ///
273 /// // Fetch all transactions without page limit (use with caution)
274 /// let all_transactions = client.list_all_transactions_history("merchant123", Some("desc"), None).await?;
275 /// println!("Fetched {} transactions", all_transactions.len());
276 /// # Ok(())
277 /// # }
278 /// ```
279 pub async fn list_all_transactions_history(
280 &self,
281 merchant_code: &str,
282 order: Option<&str>,
283 max_pages: Option<usize>,
284 ) -> Result<Vec<Transaction>> {
285 let mut all_transactions = Vec::new();
286 let mut page_count = 0;
287 let mut newest_time: Option<String> = None;
288
289 loop {
290 // Check if we've reached the maximum number of pages
291 if let Some(max) = max_pages {
292 if page_count >= max {
293 break;
294 }
295 }
296
297 // Fetch the current page
298 let history = self
299 .list_transactions_history(
300 merchant_code,
301 &TransactionHistoryQuery {
302 limit: Some(100), // Use a reasonable page size
303 order,
304 newest_time: newest_time.as_deref(),
305 oldest_time: None, // No oldest_time for this convenience method
306 },
307 )
308 .await?;
309
310 // Check if there are more pages before moving data
311 let has_next_page = Self::has_next_page_from_history(&history);
312
313 // Get the newest time from the last transaction for the next page
314 let last_transaction = history.items.last().map(|t| t.timestamp.clone());
315
316 // Add transactions from this page
317 all_transactions.extend(history.items);
318
319 // Update newest_time for next iteration
320 if let Some(timestamp) = last_transaction {
321 newest_time = Some(timestamp);
322 } else {
323 break;
324 }
325
326 // Check if we should continue
327 if !has_next_page {
328 break;
329 }
330
331 page_count += 1;
332 }
333
334 Ok(all_transactions)
335 }
336}