1use crate::{
2 AvailablePaymentMethodsResponse, Checkout, CheckoutListQuery, CreateCheckoutRequest,
3 DeletedCheckout, ProcessCheckoutRequest, ProcessCheckoutResponse, Result, SumUpClient,
4};
5
6impl SumUpClient {
7 pub async fn list_checkouts(&self, checkout_reference: Option<&str>) -> Result<Vec<Checkout>> {
31 let query = CheckoutListQuery {
32 checkout_reference: checkout_reference.map(|s| s.to_string()),
33 status: None,
34 merchant_code: None,
35 customer_id: None,
36 limit: None,
37 offset: None,
38 };
39 self.list_checkouts_with_query(&query).await
40 }
41
42 pub async fn list_checkouts_with_query(
71 &self,
72 query: &CheckoutListQuery,
73 ) -> Result<Vec<Checkout>> {
74 let mut url = self.build_url("/v0.1/checkouts")?;
75
76 {
78 let mut query_pairs = url.query_pairs_mut();
79 if let Some(ref checkout_ref) = query.checkout_reference {
80 query_pairs.append_pair("checkout_reference", checkout_ref);
81 }
82 if let Some(ref status) = query.status {
83 query_pairs.append_pair("status", status);
84 }
85 if let Some(ref merchant_code) = query.merchant_code {
86 query_pairs.append_pair("merchant_code", merchant_code);
87 }
88 if let Some(ref customer_id) = query.customer_id {
89 query_pairs.append_pair("customer_id", customer_id);
90 }
91 if let Some(limit) = query.limit {
92 query_pairs.append_pair("limit", &limit.to_string());
93 }
94 if let Some(offset) = query.offset {
95 query_pairs.append_pair("offset", &offset.to_string());
96 }
97 }
98
99 let response = self
100 .http_client
101 .get(url)
102 .bearer_auth(&self.api_key)
103 .send()
104 .await?;
105
106 if response.status().is_success() {
107 let checkouts = response.json::<Vec<Checkout>>().await?;
108 Ok(checkouts)
109 } else {
110 self.handle_error(response).await
111 }
112 }
113
114 pub async fn create_checkout(&self, body: &CreateCheckoutRequest) -> Result<Checkout> {
119 let url = self.build_url("/v0.1/checkouts")?;
120
121 let response = self
122 .http_client
123 .post(url)
124 .bearer_auth(&self.api_key)
125 .json(body)
126 .send()
127 .await?;
128
129 if response.status().is_success() {
130 let checkout = response.json::<Checkout>().await?;
131 Ok(checkout)
132 } else {
133 self.handle_error(response).await
134 }
135 }
136
137 pub async fn retrieve_checkout(&self, checkout_id: &str) -> Result<Checkout> {
142 let url = self.build_url(&format!("/v0.1/checkouts/{}", checkout_id))?;
143
144 let response = self
145 .http_client
146 .get(url)
147 .bearer_auth(&self.api_key)
148 .send()
149 .await?;
150
151 if response.status().is_success() {
152 let checkout = response.json::<Checkout>().await?;
153 Ok(checkout)
154 } else {
155 self.handle_error(response).await
156 }
157 }
158
159 pub async fn process_checkout(
166 &self,
167 checkout_id: &str,
168 body: &ProcessCheckoutRequest,
169 ) -> Result<ProcessCheckoutResponse> {
170 let url = self.build_url(&format!("/v0.1/checkouts/{}", checkout_id))?;
171
172 let response = self
173 .http_client
174 .put(url)
175 .bearer_auth(&self.api_key)
176 .json(body)
177 .send()
178 .await?;
179
180 let status = response.status().as_u16();
181 println!("🔍 Response status: {}", status);
182
183 match status {
184 200 => {
185 let response_text = response.text().await.unwrap_or_default();
187 println!("🔍 200 Response body: {}", response_text);
188
189 if response_text.contains("next_step") {
191 match serde_json::from_str::<crate::CheckoutAccepted>(&response_text) {
193 Ok(accepted) => Ok(ProcessCheckoutResponse::Accepted(accepted)),
194 Err(e) => {
195 println!("🔍 Failed to parse 3DS response: {}", e);
196 Err(crate::Error::Json(e))
197 }
198 }
199 } else {
200 match serde_json::from_str::<Checkout>(&response_text) {
202 Ok(checkout) => Ok(ProcessCheckoutResponse::Success(checkout)),
203 Err(e) => {
204 println!("🔍 Failed to parse as Checkout: {}", e);
205 Err(crate::Error::Json(e))
206 }
207 }
208 }
209 }
210 202 => {
211 let response_text = response.text().await.unwrap_or_default();
212 println!("🔍 202 Response body: {}", response_text);
213
214 match serde_json::from_str::<crate::CheckoutAccepted>(&response_text) {
215 Ok(accepted) => Ok(ProcessCheckoutResponse::Accepted(accepted)),
216 Err(e) => {
217 println!("🔍 Failed to parse 202 response: {}", e);
218 Err(crate::Error::Json(e))
219 }
220 }
221 }
222 _ => self.handle_error(response).await,
223 }
224 }
225
226 pub async fn deactivate_checkout(&self, checkout_id: &str) -> Result<DeletedCheckout> {
231 let url = self.build_url(&format!("/v0.1/checkouts/{}", checkout_id))?;
232
233 let response = self
234 .http_client
235 .delete(url)
236 .bearer_auth(&self.api_key)
237 .send()
238 .await?;
239
240 if response.status().is_success() {
241 let deleted_checkout = response.json::<DeletedCheckout>().await?;
242 Ok(deleted_checkout)
243 } else {
244 self.handle_error(response).await
245 }
246 }
247
248 pub async fn get_available_payment_methods(
255 &self,
256 merchant_code: &str,
257 amount: Option<f64>,
258 currency: Option<&str>,
259 ) -> Result<AvailablePaymentMethodsResponse> {
260 let mut url = self.build_url(&format!(
261 "/v0.1/merchants/{}/payment-methods",
262 merchant_code
263 ))?;
264
265 {
266 let mut query_pairs = url.query_pairs_mut();
267 if let Some(amt) = amount {
268 query_pairs.append_pair("amount", &amt.to_string());
269 }
270 if let Some(curr) = currency {
271 query_pairs.append_pair("currency", curr);
272 }
273 }
274
275 let response = self
276 .http_client
277 .get(url)
278 .bearer_auth(&self.api_key)
279 .send()
280 .await?;
281
282 if response.status().is_success() {
283 let methods = response.json::<AvailablePaymentMethodsResponse>().await?;
284 Ok(methods)
285 } else {
286 self.handle_error(response).await
287 }
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use crate::{CreateCheckoutRequest, SumUpClient};
294 use wiremock::matchers::{body_json, header, method, path};
295 use wiremock::{Mock, MockServer, ResponseTemplate};
296
297 #[tokio::test]
298 async fn test_create_checkout_success() {
299 let mock_server = MockServer::start().await;
301
302 let request_body = CreateCheckoutRequest {
304 checkout_reference: "test_ref_123".to_string(),
305 amount: 10.50,
306 currency: "EUR".to_string(),
307 merchant_code: "M123".to_string(),
308 description: Some("A test checkout".to_string()),
309 return_url: None,
310 customer_id: None,
311 purpose: None,
312 redirect_url: None,
313 };
314
315 let response_body = serde_json::json!({
317 "id": "88fcf8de-304d-4820-8f1c-ec880290eb92",
318 "status": "PENDING",
319 "checkout_reference": "test_ref_123",
320 "amount": 10.50,
321 "currency": "EUR",
322 "merchant_code": "M123",
323 "date": "2020-02-29T10:56:56+00:00",
324 "description": "A test checkout",
325 "transactions": []
326 });
327
328 Mock::given(method("POST"))
330 .and(path("/v0.1/checkouts"))
331 .and(header("Authorization", "Bearer test-api-key"))
332 .and(body_json(&request_body))
333 .respond_with(
334 ResponseTemplate::new(201) .set_body_json(&response_body),
336 )
337 .mount(&mock_server)
338 .await;
339
340 let client =
342 SumUpClient::with_custom_url("test-api-key".to_string(), mock_server.uri()).unwrap();
343 let result = client.create_checkout(&request_body).await;
344
345 assert!(result.is_ok());
347 let checkout = result.unwrap();
348 assert_eq!(checkout.id, "88fcf8de-304d-4820-8f1c-ec880290eb92");
349 assert_eq!(checkout.status, "PENDING");
350 assert_eq!(checkout.amount, 10.50);
351 }
352
353 #[tokio::test]
354 async fn test_retrieve_checkout_success() {
355 let mock_server = MockServer::start().await;
357
358 let checkout_id = "88fcf8de-304d-4820-8f1c-ec880290eb92";
359
360 let response_body = serde_json::json!({
362 "id": "88fcf8de-304d-4820-8f1c-ec880290eb92",
363 "status": "PENDING",
364 "checkout_reference": "test_ref_123",
365 "amount": 10.50,
366 "currency": "EUR",
367 "merchant_code": "M123",
368 "date": "2020-02-29T10:56:56+00:00",
369 "description": "A test checkout",
370 "transactions": []
371 });
372
373 Mock::given(method("GET"))
375 .and(path(format!("/v0.1/checkouts/{}", checkout_id)))
376 .and(header("Authorization", "Bearer test-api-key"))
377 .respond_with(
378 ResponseTemplate::new(200) .set_body_json(&response_body),
380 )
381 .mount(&mock_server)
382 .await;
383
384 let client =
386 SumUpClient::with_custom_url("test-api-key".to_string(), mock_server.uri()).unwrap();
387 let result = client.retrieve_checkout(checkout_id).await;
388
389 assert!(result.is_ok());
391 let checkout = result.unwrap();
392 assert_eq!(checkout.id, checkout_id);
393 assert_eq!(checkout.status, "PENDING");
394 assert_eq!(checkout.amount, 10.50);
395 }
396}