1use crate::client::SquareClient;
6use crate::api::{Verb, SquareAPI};
7use crate::errors::{PaymentBuildError, ValidationError};
8use crate::errors::SquareError;
9use crate::objects::{Address, CashPaymentDetails, enums::Currency, ExternalPaymentDetails, Money, Payment};
10use crate::response::SquareResponse;
11
12use serde::{Deserialize, Serialize};
13use uuid::Uuid;
14use crate::builder::{Builder, ParentBuilder, Validate, Buildable};
15use crate::objects::enums::SortOrder;
16
17impl SquareClient {
18 pub fn payments(&self) -> Payments {
19 Payments {
20 client: &self,
21 }
22 }
23}
24
25pub struct Payments<'a> {
26 client: &'a SquareClient,
27}
28
29impl<'a> Payments<'a> {
30 pub async fn list(self, parameters: Option<Vec<(String, String)>>) -> Result<SquareResponse, SquareError> {
37 self.client.request(
38 Verb::GET,
39 SquareAPI::Payments("".to_string()),
40 None::<&PaymentRequest>,
41 parameters,
42 ).await
43 }
44
45 pub async fn create(self, payment: PaymentRequest) -> Result<SquareResponse, SquareError> {
51 self.client.request(
52 Verb::POST,
53 SquareAPI::Payments("".to_string()),
54 Some(&payment),
55 None,
56 ).await
57 }
58
59 pub async fn cancel_by_idempotency_key(self, idempotency_key: String) -> Result<SquareResponse, SquareError> {
65 self.client.request(
66 Verb::POST,
67 SquareAPI::Payments("/cancel".to_string()),
68 Some(&CancelByIdempotencyKey { idempotency_key }),
69 None,
70 ).await
71 }
72
73 pub async fn get(self, payment_id: String) -> Result<SquareResponse, SquareError> {
79 self.client.request(
80 Verb::GET,
81 SquareAPI::Payments(format!("/{}", payment_id)),
82 None::<&PaymentRequest>,
83 None,
84 ).await
85 }
86
87 pub async fn update(self, payment_id: String, body: UpdatePaymentBody)
95 -> Result<SquareResponse, SquareError> {
96 self.client.request(
97 Verb::PUT,
98 SquareAPI::Payments(format!("/{}", payment_id)),
99 Some(&body),
100 None,
101 ).await
102 }
103
104 pub async fn cancel(self, payment_id: String)
110 -> Result<SquareResponse, SquareError> {
111 self.client.request(
112 Verb::POST,
113 SquareAPI::Payments(format!("/{}/cancel", payment_id)),
114 None::<&PaymentRequest>,
115 None,
116 ).await
117 }
118
119 pub async fn complete(self, payment_id: String, version_token: Option<String>)
129 -> Result<SquareResponse, SquareError> {
130 self.client.request(
131 Verb::POST,
132 SquareAPI::Payments(format!("/{}/complete", payment_id)),
133 Some(&CompletePaymentBody {
134 version_token
135 }),
136 None,
137 ).await
138 }
139}
140
141#[derive(Default)]
145pub struct ListPaymentsParametersBuilder {
146 begin_time: Option<String>,
147 end_time: Option<String>,
148 sort_order: Option<SortOrder>,
149 cursor: Option<String>,
150 location_id: Option<String>,
151 total: Option<i32>,
152 last_4: Option<String>,
153 card_brand: Option<String>,
154 limit: Option<i32>,
155}
156
157impl ListPaymentsParametersBuilder {
158 pub fn new() -> Self {
159 Default::default()
160 }
161
162 pub fn begin_time(mut self, begin_time: String) -> Self {
165 self.begin_time = Some(begin_time);
166
167 self
168 }
169
170 pub fn end_time(mut self, end_time: String) -> Self {
173 self.end_time = Some(end_time);
174
175 self
176 }
177
178 pub fn sort_ascending(mut self) -> Self {
180 self.sort_order = Some(SortOrder::Asc);
181
182 self
183 }
184
185 pub fn sort_descending(mut self) -> Self {
187 self.sort_order = Some(SortOrder::Desc);
188
189 self
190 }
191
192 pub fn cursor(mut self, cursor: String) -> Self {
195 self.cursor = Some(cursor);
196
197 self
198 }
199
200 pub fn location_id(mut self, location_id: String) -> Self {
203 self.location_id = Some(location_id);
204
205 self
206 }
207
208 pub fn total(mut self, total: i32) -> Self {
210 self.total = Some(total);
211
212 self
213 }
214
215 pub fn last_4(mut self, last_4: String) -> Self {
217 self.last_4 = Some(last_4);
218
219 self
220 }
221
222 pub fn card_brand(mut self, card_brand: String) -> Self {
224 self.card_brand = Some(card_brand);
225
226 self
227 }
228
229 pub fn limit(mut self, limit: i32) -> Self {
236 self.limit = Some(limit);
237
238 self
239 }
240
241 pub async fn build(self) -> Vec<(String,String)> {
242 let ListPaymentsParametersBuilder {
243 begin_time,
244 end_time,
245 sort_order,
246 cursor,
247 location_id,
248 total,
249 last_4,
250 card_brand,
251 limit,
252
253 } = self;
254
255 let mut res = vec![];
256
257
258 if let Some(begin_time) = begin_time {
259 res.push(("begin_time".to_string(), begin_time.to_string()))
260 }
261 if let Some(end_time) = end_time {
262 res.push(("end_time".to_string(), end_time.to_string()))
263 }
264 if let Some(sort_order) = sort_order {
265 res.push(("sort_order".to_string(), sort_order.to_string()))
266 }
267 if let Some(cursor) = cursor {
268 res.push(("cursor".to_string(), cursor.to_string()))
269 }
270 if let Some(location_id) = location_id {
271 res.push(("location_id".to_string(), location_id.to_string()))
272 }
273 if let Some(total) = total {
274 res.push(("total".to_string(), total.to_string()))
275 }
276 if let Some(last_4) = last_4 {
277 res.push(("last_4".to_string(), last_4.to_string()))
278 }
279 if let Some(card_brand) = card_brand {
280 res.push(("card_brand".to_string(), card_brand.to_string()))
281 }
282 if let Some(limit) = limit {
283 res.push(("limit".to_string(), limit.to_string()))
284 }
285
286 res
287 }
288}
289
290#[derive(Serialize, Debug, Deserialize, Default)]
295pub struct PaymentRequest {
296 #[serde(rename(serialize = "source_id"), skip_serializing_if = "Option::is_none")]
297 source_id: Option<String>,
298 #[serde(skip_serializing_if = "Option::is_none")]
299 idempotency_key: Option<String>,
300 #[serde(skip_serializing_if = "Option::is_none")]
301 amount_money: Option<Money>,
302 #[serde(skip_serializing_if = "Option::is_none")]
303 accept_partial_authorization: Option<String>,
304 #[serde(skip_serializing_if = "Option::is_none")]
305 app_fee_money: Option<Money>,
306 #[serde(skip_serializing_if = "Option::is_none")]
307 autocomplete: Option<bool>,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 billing_address: Option<Address>,
310 #[serde(skip_serializing_if = "Option::is_none")]
311 buyer_email_address: Option<String>,
312 #[serde(skip_serializing_if = "Option::is_none")]
313 cash_details: Option<CashPaymentDetails>,
314 #[serde(skip_serializing_if = "Option::is_none")]
315 customer_id: Option<String>,
316 #[serde(skip_serializing_if = "Option::is_none")]
317 delay_action: Option<String>,
318 #[serde(skip_serializing_if = "Option::is_none")]
319 delay_duration: Option<String>,
320 #[serde(skip_serializing_if = "Option::is_none")]
321 external_details: Option<ExternalPaymentDetails>,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 location_id: Option<String>,
324 #[serde(skip_serializing_if = "Option::is_none")]
325 note: Option<String>,
326 #[serde(skip_serializing_if = "Option::is_none")]
327 order_id: Option<String>,
328 #[serde(skip_serializing_if = "Option::is_none")]
329 reference_id: Option<String>,
330 #[serde(skip_serializing_if = "Option::is_none")]
331 shipping_address: Option<Address>,
332 #[serde(skip_serializing_if = "Option::is_none")]
333 statement_description_identifier: Option<String>,
334 #[serde(skip_serializing_if = "Option::is_none")]
335 team_member_id: Option<String>,
336 #[serde(skip_serializing_if = "Option::is_none")]
337 tip_money: Option<Money>,
338 #[serde(skip_serializing_if = "Option::is_none")]
339 verification_token: Option<String>,
340}
341
342impl Validate for PaymentRequest {
343 fn validate(mut self) -> Result<Self, ValidationError> where Self: Sized {
344 if self.source_id.is_some() &&
345 self.amount_money.is_some() {
346 self.idempotency_key = Some(Uuid::new_v4().to_string());
347
348 Ok(self)
349 } else {
350 Err(ValidationError)
351 }
352 }
353}
354
355impl<T: ParentBuilder> Builder<PaymentRequest, T> {
356 pub fn source_id(mut self, source_id: String) -> Self {
357 self.body.source_id = Some(source_id);
358
359 self
360 }
361
362 pub fn amount(mut self, amount: i64, currency: Currency) -> Self {
363 self.body.amount_money = Some(Money { amount: Some(amount), currency });
364
365 self
366 }
367
368 pub fn verification_token(mut self, token: String) -> Self {
369 self.body.verification_token = Some(token);
370
371 self
372 }
373}
374
375#[derive(Serialize, Debug, Deserialize)]
379struct CancelByIdempotencyKey {
380 idempotency_key: String,
381}
382
383#[derive(Serialize, Debug, Deserialize, Default)]
387pub struct UpdatePaymentBody {
388 idempotency_key: Option<String>,
389 payment: Payment
390}
391
392impl Validate for UpdatePaymentBody {
393 fn validate(mut self) -> Result<Self, ValidationError> where Self: Sized {
394 self.idempotency_key = Some(Uuid::new_v4().to_string());
395
396 Ok(self)
397 }
398}
399
400impl<T: ParentBuilder> Builder<UpdatePaymentBody, T> {
401 pub fn amount_money(mut self, amount_money: Money) -> Self {
402 self.body.payment.amount_money = Some(amount_money);
403
404 self
405 }
406
407 pub fn app_fee_money(mut self, app_fee_money: Money) -> Self {
408 self.body.payment.app_fee_money = Some(app_fee_money);
409
410 self
411 }
412
413 pub fn approved_money(mut self, approved_money: Money) -> Self {
414 self.body.payment.approved_money = Some(approved_money);
415
416 self
417 }
418
419 pub fn cash_details(mut self, cash_details: CashPaymentDetails) -> Self {
420 self.body.payment.cash_details = Some(cash_details);
421
422 self
423 }
424
425 pub fn tip_money(mut self, tip_money: Money) -> Self {
426 self.body.payment.tip_money = Some(tip_money);
427
428 self
429 }
430
431 pub fn version_token(mut self, version_token: String) -> Self {
432 self.body.payment.version_token = Some(version_token);
433
434 self
435 }
436}
437
438#[derive(Serialize, Debug, Deserialize)]
439struct CompletePaymentBody {
440 version_token: Option<String>,
441}
442
443#[cfg(test)]
444mod test_payments {
445 use super::*;
446
447 #[tokio::test]
448 async fn test_create_payment() {
449 use dotenv::dotenv;
450 use std::env;
451
452 dotenv().ok();
453 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
454 let sut = SquareClient::new(&access_token);
455
456 let input = PaymentRequest {
457 source_id: Some("cnon:card-nonce-ok".to_string()),
458 idempotency_key: Some(Uuid::new_v4().to_string()),
459 amount_money: Some(Money { amount: Some(10), currency: Currency::USD }),
460 accept_partial_authorization: None,
461 app_fee_money: None,
462 autocomplete: None,
463 billing_address: None,
464 buyer_email_address: None,
465 cash_details: None,
466 customer_id: None,
467 delay_action: None,
468 delay_duration: None,
469 external_details: None,
470 location_id: None,
471 note: None,
472 order_id: None,
473 reference_id: None,
474 shipping_address: None,
475 statement_description_identifier: None,
476 team_member_id: None,
477 tip_money: None,
478 verification_token: None
479 };
480
481 let res = sut.payments()
482 .create(input)
483 .await;
484
485 assert!(res.is_ok())
486 }
487
488 #[tokio::test]
489 async fn test_list_payments_parameters_builder() {
490 let expected = vec![
491 ("sort_order".to_string(), "ASC".to_string()),
492 ("location_id".to_string(), "DMIOW91D2MDS".to_string()),
493 ("total".to_string(), "10".to_string()),
494 ("card_brand".to_string(), "Visa".to_string()),
495 ];
496
497 let actual = ListPaymentsParametersBuilder::new()
498 .location_id("DMIOW91D2MDS".to_string())
499 .card_brand("Visa".to_string())
500 .total(10)
501 .sort_ascending()
502 .build()
503 .await;
504
505 assert_eq!(expected, actual);
506 }
507
508 #[tokio::test]
509 async fn test_list_payments() {
510 use dotenv::dotenv;
511 use std::env;
512
513 dotenv().ok();
514 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
515 let sut = SquareClient::new(&access_token);
516
517 let input = vec![
518 ("sort_order".to_string(), "ASC".to_string()),
519 ];
520
521 let res = sut.payments()
522 .list(Some(input))
523 .await;
524
525 assert!(res.is_ok())
526 }
527
528 async fn test_cancel_by_idempotency_key() {
530 use dotenv::dotenv;
531 use std::env;
532
533 dotenv().ok();
534 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
535 let sut = SquareClient::new(&access_token);
536
537 let res = sut.payments()
538 .cancel_by_idempotency_key("d56c4e7f-c9f2-4204-b757-30abbcfae419".to_string())
539 .await;
540
541 assert!(res.is_ok())
542 }
543
544 #[tokio::test]
545 async fn test_get_payment() {
546 use dotenv::dotenv;
547 use std::env;
548
549 dotenv().ok();
550 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
551 let sut = SquareClient::new(&access_token);
552
553 let res = sut.payments()
554 .get("tusWbiVmrQb2ibB06xqqRaVqKCXZY".to_string())
555 .await;
556
557 assert!(res.is_ok())
558 }
559
560 #[tokio::test]
561 async fn test_update_payment_body_builder() {
562 let expected = UpdatePaymentBody {
563 idempotency_key: None,
564 payment: Payment {
565 id: None,
566 amount_money: Some(Money { amount: Some(30), currency: Currency::USD }),
567 app_fee_money: None,
568 application_details: None,
569 approved_money: None,
570 bank_account_details: None,
571 billing_address: None,
572 buy_now_pay_later_details: None,
573 buyer_email_address: None,
574 capabilities: None,
575 card_details: None,
576 cash_details: None,
577 created_at: None,
578 customer_id: None,
579 delay_action: None,
580 delay_duration: None,
581 delayed_until: None,
582 device_details: None,
583 external_details: None,
584 location_id: None,
585 note: None,
586 order_id: None,
587 processing_fee: None,
588 receipt_number: None,
589 receipt_url: None,
590 reference_id: None,
591 refund_ids: None,
592 refunded_money: None,
593 risk_evaluation: None,
594 shipping_address: None,
595 source_type: None,
596 statement_description_identifier: None,
597 status: None,
598 team_member_id: None,
599 tip_money: None,
600 total_money: None,
601 updated_at: None,
602 version_token: None,
603 wallet_details: None
604 }
605 };
606
607 let mut actual = Builder::from(UpdatePaymentBody::default())
608 .amount_money(Money { amount: Some(30), currency: Currency::USD })
609 .build()
610 .unwrap();
611
612 assert!(actual.idempotency_key.is_some());
613
614 actual.idempotency_key = None;
615
616 assert_eq!(format!("{:?}", expected), format!("{:?}", actual));
617 }
618
619 async fn test_update_payment() {
621 use dotenv::dotenv;
622 use std::env;
623
624 dotenv().ok();
625 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
626 let sut = SquareClient::new(&access_token);
627
628 let input = UpdatePaymentBody {
629 idempotency_key: Some(Uuid::new_v4().to_string()),
630 payment: Payment {
631 id: None,
632 amount_money: Some(Money { amount: Some(30), currency: Currency::USD }),
633 app_fee_money: None,
634 application_details: None,
635 approved_money: None,
636 bank_account_details: None,
637 billing_address: None,
638 buy_now_pay_later_details: None,
639 buyer_email_address: None,
640 capabilities: None,
641 card_details: None,
642 cash_details: None,
643 created_at: None,
644 customer_id: None,
645 delay_action: None,
646 delay_duration: None,
647 delayed_until: None,
648 device_details: None,
649 external_details: None,
650 location_id: None,
651 note: None,
652 order_id: None,
653 processing_fee: None,
654 receipt_number: None,
655 receipt_url: None,
656 reference_id: None,
657 refund_ids: None,
658 refunded_money: None,
659 risk_evaluation: None,
660 shipping_address: None,
661 source_type: None,
662 statement_description_identifier: None,
663 status: None,
664 team_member_id: None,
665 tip_money: None,
666 total_money: None,
667 updated_at: None,
668 version_token: None,
669 wallet_details: None
670 }
671 };
672
673 let res = sut.payments()
674 .update("tusWbiVmrQb2ibB06xqqRaVqKCXZY".to_string(), input)
675 .await;
676
677 assert!(res.is_ok())
678 }
679
680 async fn test_cancel_payment() {
682 use dotenv::dotenv;
683 use std::env;
684
685 dotenv().ok();
686 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
687 let sut = SquareClient::new(&access_token);
688
689
690 let res = sut.payments()
691 .cancel("tusWbiVmrQb2ibB06xqqRaVqKCXZY".to_string())
692 .await;
693
694 assert!(res.is_ok())
695 }
696
697 #[tokio::test]
698 async fn test_complete_payment() {
699 use dotenv::dotenv;
700 use std::env;
701
702 dotenv().ok();
703 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
704 let sut = SquareClient::new(&access_token);
705
706
707 let res = sut.payments()
708 .complete("tusWbiVmrQb2ibB06xqqRaVqKCXZY".to_string(), None)
709 .await;
710
711 assert!(res.is_ok())
712 }
713}