1use crate::client::SquareClient;
6use crate::api::{Verb, SquareAPI};
7use crate::errors::{SquareError, ValidationError};
8use crate::response::SquareResponse;
9
10use serde::{Deserialize, Serialize};
11use square_ox_derive::Builder;
12use uuid::Uuid;
13use crate::builder::{AddField, Builder, ParentBuilder, Validate, Buildable};
14use crate::objects::{self, Address, ChargeRequestAdditionalRecipient, CheckoutOptions,
15 CreateOrderRequest, Order, PaymentLink, PrePopulatedData,
16 QuickPay};
17
18impl SquareClient {
19 pub fn checkout(&self) -> Checkout {
20 Checkout {
21 client: &self
22 }
23 }
24}
25
26pub struct Checkout<'a> {
27 client: &'a SquareClient,
28}
29
30impl<'a> Checkout<'a> {
31 pub async fn create_checkout(
38 self, location_id: String,
39 create_order_request: CreateOrderRequestWrapper
40 )
41 -> Result<SquareResponse, SquareError> {
42 self.client.request(
43 Verb::POST,
44 SquareAPI::Locations(format!("/{}/checkouts", location_id)),
45 Some(&create_order_request),
46 None,
47 ).await
48 }
49
50 pub async fn list(
57 self, search_query: Option<Vec<(String, String)>>
58 )
59 -> Result<SquareResponse, SquareError> {
60 self.client.request(
61 Verb::GET,
62 SquareAPI::Checkout("/payment-links".to_string()),
63 None::<&CreateOrderRequestWrapper>,
64 search_query,
65 ).await
66 }
67
68 pub async fn create(
76 self, payment_link: CreatePaymentLinkWrapper
77 )
78 -> Result<SquareResponse, SquareError> {
79 self.client.request(
80 Verb::POST,
81 SquareAPI::Checkout("/payment-links".to_string()),
82 Some(&payment_link),
83 None,
84 ).await
85 }
86
87 pub async fn delete(
92 self, payment_link: String
93 )
94 -> Result<SquareResponse, SquareError> {
95 self.client.request(
96 Verb::DELETE,
97 SquareAPI::Checkout(format!("/payment-links/{}", payment_link)),
98 None::<&CreateOrderRequestWrapper>,
99 None,
100 ).await
101 }
102
103 pub async fn retrieve(
108 self, link_id: String
109 )
110 -> Result<SquareResponse, SquareError> {
111 self.client.request(
112 Verb::GET,
113 SquareAPI::Checkout(format!("/payment-links/{}", link_id)),
114 None::<&CreateOrderRequestWrapper>,
115 None,
116 ).await
117 }
118
119 pub async fn update(
125 self, link_id: String, payment_link: UpdatePaymentLinkWrapper
126 )
127 -> Result<SquareResponse, SquareError> {
128 self.client.request(
129 Verb::PUT,
130 SquareAPI::Checkout(format!("/payment-links/{}", link_id)),
131 Some(&payment_link),
132 None,
133 ).await
134 }
135}
136
137#[derive(Clone, Serialize, Debug, Deserialize, Default, Builder)]
138pub struct CreateOrderRequestWrapper {
139 #[builder_rand("uuid")]
140 idempotency_key: Option<String>,
141 order: CreateOrderRequest,
142 ask_for_shipping_address: Option<bool>,
143 merchant_support_email: Option<String>,
144 pre_populate_buyer_email: Option<bool>,
145 pre_populate_shipping_address: Option<Address>,
146 redirect_url: Option<String>,
147 additional_recipients: Option<Vec<ChargeRequestAdditionalRecipient>>,
148 note: Option<String>,
149}
150
151impl AddField<CreateOrderRequest> for CreateOrderRequestWrapper {
152 fn add_field(&mut self, field: CreateOrderRequest) {
153 self.order = field;
154 }
155}
156
157#[derive(Default)]
158pub struct ListPaymentLinksSearchQueryBuilder {
159 cursor: Option<String>,
160 limit: Option<i32>,
161}
162
163impl ListPaymentLinksSearchQueryBuilder {
164 pub fn new() -> Self {
165 Default::default()
166 }
167
168 pub fn cursor(mut self, cursor: String) -> Self {
169 self.cursor = Some(cursor);
170
171 self
172 }
173
174 pub fn limit(mut self, limit: i32) -> Self {
175 self.limit = Some(limit);
176
177 self
178 }
179
180 pub async fn build(self) -> Vec<(String, String)> {
181 let ListPaymentLinksSearchQueryBuilder {
182 cursor,
183 limit,
184 } = self;
185
186 let mut res = vec![];
187
188 if let Some(cursor) = cursor {
189 res.push(("cursor".to_string() , cursor));
190 }
191
192 if let Some(limit) = limit {
193 res.push(("limit".to_string() , limit.to_string()));
194 }
195
196 res
197 }
198}
199
200#[derive(Clone, Serialize, Debug, Default)]
201pub struct CreatePaymentLinkWrapper {
202 idempotency_key: String,
203 #[serde(skip_serializing_if = "Option::is_none")]
204 description: Option<String>,
205 #[serde(skip_serializing_if = "Option::is_none")]
206 quick_pay: Option<QuickPay>,
207 #[serde(skip_serializing_if = "Option::is_none")]
208 order: Option<Order>,
209 #[serde(skip_serializing_if = "Option::is_none")]
210 checkout_options: Option<CheckoutOptions>,
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pre_populated_data: Option<PrePopulatedData>,
213 #[serde(skip_serializing_if = "Option::is_none")]
214 source: Option<String>,
215 #[serde(skip_serializing_if = "Option::is_none")]
216 payment_note: Option<String>,
217}
218
219impl Validate for CreatePaymentLinkWrapper {
220 fn validate(mut self) -> Result<Self, ValidationError> where Self: Sized {
221 if self.order.is_some() || self.quick_pay.is_some() {
222 self.idempotency_key = Uuid::new_v4().to_string();
223
224 Ok(self)
225 } else {
226 Err(ValidationError)
227 }
228 }
229}
230
231impl<T: ParentBuilder> Builder<CreatePaymentLinkWrapper, T> {
232 pub fn checkout_options(mut self, checkout_options: CheckoutOptions) -> Self {
233 self.body.checkout_options = Some(checkout_options);
234
235 self
236 }
237
238 pub fn description(mut self, description: String) -> Self {
239 self.body.description = Some(description);
240
241 self
242 }
243
244 pub fn order(mut self, order: Order) -> Self {
245 self.body.order = Some(order);
246
247 self
248 }
249
250 pub fn payment_note(mut self, payment_note: String) -> Self {
251 self.body.payment_note = Some(payment_note);
252
253 self
254 }
255
256 pub fn pre_populated_data(mut self, pre_populated_data: PrePopulatedData) -> Self {
257 self.body.pre_populated_data = Some(pre_populated_data);
258
259 self
260 }
261
262 pub fn quick_pay(mut self, quick_pay: QuickPay) -> Self {
263 self.body.quick_pay = Some(quick_pay);
264
265 self
266 }
267
268 pub fn source(mut self, source: String) -> Self {
269 self.body.source = Some(source);
270
271 self
272 }
273}
274
275impl AddField<Order> for CreatePaymentLinkWrapper {
276 fn add_field(&mut self, field: Order) {
277 self.order = Some(field);
278 }
279}
280
281#[derive(Clone, Serialize, Debug, Default, Builder)]
282pub struct UpdatePaymentLinkWrapper {
283 payment_link: PaymentLink,
284}
285
286impl AddField<PaymentLink> for UpdatePaymentLinkWrapper {
287 fn add_field(&mut self, field: PaymentLink) {
288 self.payment_link = field;
289 }
290}
291
292#[cfg(test)]
293mod test_checkout {
294 use crate::builder::BackIntoBuilder;
295 use crate::objects::{enums::{OrderLineItemItemType, Currency}, Money, OrderLineItem};
296 use super::*;
297
298 #[tokio::test]
299 async fn test_create_order_request_builder() {
300 let expected = CreateOrderRequestWrapper {
301 idempotency_key: None,
302 order: CreateOrderRequest { idempotency_key: None, order: Order {
303 id: None,
304 location_id: Some("L1JC53TYHS40Z".to_string()),
305 close_at: None,
306 created_at: None,
307 customer_id: None,
308 discounts: None,
309 fulfillments: None,
310 line_items: Some(vec![
311 OrderLineItem {
312 quantity: "1".to_string(),
313 applied_discounts: None,
314 applied_taxes: None,
315 base_price_money: Some(Money {
316 amount: Some(5),
317 currency: Currency::USD
318 }),
319 catalog_object_id: Some("BSOL4BB6RCMX6SH4KQIFWZDP".to_string()),
320 catalog_version: Some(1655427266071),
321 gross_sales_money: None,
322 item_type: Some(OrderLineItemItemType::Item),
323 metadata: None,
324 modifiers: None,
325 name: None,
326 note: None,
327 pricing_blocklists: None,
328 quantity_unit: None,
329 total_discount_money: None,
330 total_money: None,
331 total_tax_money: None,
332 uid: None,
333 variation_name: None,
334 variation_total_price_money: None,
335 api_reference_ids: None
336 },
337 OrderLineItem {
338 quantity: "2".to_string(),
339 applied_discounts: None,
340 applied_taxes: None,
341 base_price_money: Some(Money {
342 amount: Some(5),
343 currency: Currency::USD
344 }),
345 catalog_object_id: Some("BSOL4BB6RCMX6SH4KQIFWZDP".to_string()),
346 catalog_version: Some(1655427266071),
347 gross_sales_money: None,
348 item_type: Some(OrderLineItemItemType::Item),
349 metadata: None,
350 modifiers: None,
351 name: None,
352 note: None,
353 pricing_blocklists: None,
354 quantity_unit: None,
355 total_discount_money: None,
356 total_money: None,
357 total_tax_money: None,
358 uid: None,
359 variation_name: None,
360 variation_total_price_money: None,
361 api_reference_ids: None
362 }]),
363 metadata: None,
364 net_amounts: None,
365 pricing_options: None,
366 reference_id: None,
367 refunds: None,
368 return_amounts: None,
369 returns: None,
370 rewards: None,
371 rounding_adjustment: None,
372 service_charges: None,
373 source: None,
374 state: None,
375 taxes: None,
376 tenders: None,
377 ticket_name: None,
378 total_discount_money: None,
379 total_money: None,
380 total_service_charge_money: None,
381 total_tax_money: None,
382 total_tip_money: None,
383 updated_at: None,
384 version: None
385 }},
386 ask_for_shipping_address: None,
387 merchant_support_email: None,
388 pre_populate_buyer_email: None,
389 pre_populate_shipping_address: None,
390 redirect_url: None,
391 additional_recipients: None,
392 note: None
393 };
394
395 let mut actual = Builder::from(CreateOrderRequestWrapper::default())
396 .sub_builder_from(CreateOrderRequest::default())
397 .sub_builder_from(Order::default())
398 .sub_builder_from(OrderLineItem::default())
399 .quantity("1")
400 .base_price_money( Money {
401 amount: Some(5),
402 currency: Currency::USD
403 })
404 .catalog_object_id("BSOL4BB6RCMX6SH4KQIFWZDP")
405 .catalog_version(1655427266071)
406 .item_type(OrderLineItemItemType::Item)
407 .build()
408 .unwrap()
409 .sub_builder_from(OrderLineItem::default())
410 .quantity("2")
411 .base_price_money( Money {
412 amount: Some(5),
413 currency: Currency::USD
414 })
415 .catalog_object_id("BSOL4BB6RCMX6SH4KQIFWZDP")
416 .catalog_version(1655427266071)
417 .item_type(OrderLineItemItemType::Item)
418 .build()
419 .unwrap()
420 .location_id("L1JC53TYHS40Z".to_string())
421 .build()
422 .unwrap()
423 .build()
424 .unwrap()
425 .build()
426 .unwrap();
427 assert!(actual.idempotency_key.is_some());
428 assert!(actual.order.idempotency_key.is_some());
429
430 actual.idempotency_key = None;
431 actual.order.idempotency_key = None;
432
433 assert_eq!(format!("{:?}", expected), format!("{:?}", actual));
434 }
435
436 #[tokio::test]
437 async fn test_create_checkout() {
438 use dotenv::dotenv;
439 use std::env;
440
441 dotenv().ok();
442 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
443 let sut = SquareClient::new(&access_token);
444
445 let input = CreateOrderRequestWrapper {
446 idempotency_key: Some(Uuid::new_v4().to_string()),
447 order: CreateOrderRequest { idempotency_key: Some(Uuid::new_v4().to_string()), order: Order {
448 id: None,
449 location_id: Some("L1JC53TYHS40Z".to_string()),
450 close_at: None,
451 created_at: None,
452 customer_id: None,
453 discounts: None,
454 fulfillments: None,
455 line_items: Some(vec![
456 OrderLineItem {
457 quantity: "1".to_string(),
458 applied_discounts: None,
459 applied_taxes: None,
460 base_price_money: Some(Money {
461 amount: Some(5),
462 currency: Currency::USD
463 }),
464 catalog_object_id: Some("BSOL4BB6RCMX6SH4KQIFWZDP".to_string()),
465 catalog_version: Some(1655427266071),
466 gross_sales_money: None,
467 item_type: Some(OrderLineItemItemType::Item),
468 metadata: None,
469 modifiers: None,
470 name: None,
471 note: None,
472 pricing_blocklists: None,
473 quantity_unit: None,
474 total_discount_money: None,
475 total_money: None,
476 total_tax_money: None,
477 uid: None,
478 variation_name: None,
479 variation_total_price_money: None,
480 api_reference_ids: None
481 },
482 OrderLineItem {
483 quantity: "2".to_string(),
484 applied_discounts: None,
485 applied_taxes: None,
486 base_price_money: Some(Money {
487 amount: Some(5),
488 currency: Currency::USD
489 }),
490 catalog_object_id: Some("BSOL4BB6RCMX6SH4KQIFWZDP".to_string()),
491 catalog_version: Some(1655427266071),
492 gross_sales_money: None,
493 item_type: Some(OrderLineItemItemType::Item),
494 metadata: None,
495 modifiers: None,
496 name: None,
497 note: None,
498 pricing_blocklists: None,
499 quantity_unit: None,
500 total_discount_money: None,
501 total_money: None,
502 total_tax_money: None,
503 uid: None,
504 variation_name: None,
505 variation_total_price_money: None,
506 api_reference_ids: None
507 }]),
508 metadata: None,
509 net_amounts: None,
510 pricing_options: None,
511 reference_id: None,
512 refunds: None,
513 return_amounts: None,
514 returns: None,
515 rewards: None,
516 rounding_adjustment: None,
517 service_charges: None,
518 source: None,
519 state: None,
520 taxes: None,
521 tenders: None,
522 ticket_name: None,
523 total_discount_money: None,
524 total_money: None,
525 total_service_charge_money: None,
526 total_tax_money: None,
527 total_tip_money: None,
528 updated_at: None,
529 version: None
530 }},
531 ask_for_shipping_address: None,
532 merchant_support_email: None,
533 pre_populate_buyer_email: None,
534 pre_populate_shipping_address: None,
535 redirect_url: None,
536 additional_recipients: None,
537 note: None
538 };
539
540 let res = sut.checkout()
541 .create_checkout("L1JC53TYHS40Z".to_string(), input)
542 .await;
543
544 assert!(res.is_ok());
545 }
546
547 #[tokio::test]
548 async fn test_list_payment_search_query_builder() {
549 let expected = vec![
550 ("cursor".to_string(), "dwasd".to_string()),
551 ("limit".to_string(), "10".to_string()),
552 ];
553
554 let actual = ListPaymentLinksSearchQueryBuilder::new()
555 .limit(10)
556 .cursor("dwasd".to_string())
557 .build()
558 .await;
559
560 assert_eq!(expected, actual)
561 }
562
563 #[tokio::test]
564 async fn test_list_payment_links() {
565 use dotenv::dotenv;
566 use std::env;
567
568 dotenv().ok();
569 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
570 let sut = SquareClient::new(&access_token);
571
572 let input = vec![("limit".to_string(), "10".to_string())];
573
574 let res = sut.checkout()
575 .list(Some(input))
576 .await;
577
578 assert!(res.is_ok());
579 }
580
581 #[tokio::test]
582 async fn test_create_payment_link_builder() {
583 let expected = CreatePaymentLinkWrapper {
584 idempotency_key: "".to_string(),
585 description: None,
586 quick_pay: Some( QuickPay {
587 location_id: "L1JC53TYHS40Z".to_string(),
588 name: "Another Thing".to_string(),
589 price_money: Money { amount: Some(10), currency: Currency::USD }
590 }),
591 order: None,
592 checkout_options: None,
593 pre_populated_data: None,
594 source: None,
595 payment_note: None
596 };
597
598 let mut actual = Builder::from(CreatePaymentLinkWrapper::default())
599 .quick_pay(QuickPay {
600 location_id: "L1JC53TYHS40Z".to_string(),
601 name: "Another Thing".to_string(),
602 price_money: Money { amount: Some(10), currency: Currency::USD }
603 })
604 .build()
605 .unwrap();
606
607 actual.idempotency_key = "".to_string();
608
609
610 assert_eq!(format!("{:?}", expected), format!("{:?}", actual))
611 }
612
613 #[tokio::test]
614 async fn test_create_payment_link() {
615 use dotenv::dotenv;
616 use std::env;
617
618 dotenv().ok();
619 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
620 let sut = SquareClient::new(&access_token);
621
622 let input = CreatePaymentLinkWrapper {
623 idempotency_key: "".to_string(),
624 description: None,
625 quick_pay: Some( QuickPay {
626 location_id: "L1JC53TYHS40Z".to_string(),
627 name: "Another Thing".to_string(),
628 price_money: Money { amount: Some(10), currency: Currency::USD }
629 }),
630 order: None,
631 checkout_options: None,
632 pre_populated_data: None,
633 source: None,
634 payment_note: None
635 };
636
637 let res = sut.checkout()
638 .create(input)
639 .await;
640
641 assert!(res.is_ok());
642 }
643
644 #[tokio::test]
645 async fn test_delete_payment_link() {
646 use dotenv::dotenv;
647 use std::env;
648
649 dotenv().ok();
650 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
651 let sut = SquareClient::new(&access_token);
652
653 let input = "PLEJUTGT4VLUKUY2".to_string();
654
655 let res = sut.checkout()
656 .delete(input)
657 .await;
658
659 assert!(res.is_ok());
660 }
661
662 #[tokio::test]
663 async fn test_retrieve_payment_link() {
664 use dotenv::dotenv;
665 use std::env;
666
667 dotenv().ok();
668 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
669 let sut = SquareClient::new(&access_token);
670
671 let input = "PN43H2RUILBXIX2H".to_string();
672
673 let res = sut.checkout()
674 .retrieve(input)
675 .await;
676
677 assert!(res.is_ok());
678 }
679
680 #[tokio::test]
681 async fn test_update_payment_link_wrapper_builder() {
682 let expected = UpdatePaymentLinkWrapper {
683 payment_link: objects::PaymentLink {
684 id: None,
685 version: 5,
686 checkout_options: None,
687 created_at: None,
688 description: None,
689 order_id: None,
690 payment_note: None,
691 pre_populated_data: None,
692 updated_at: None,
693 url: None
694 }
695 };
696
697 let actual = Builder::from(UpdatePaymentLinkWrapper::default())
698 .sub_builder_from(PaymentLink::default())
699 .version(5)
700 .build()
701 .unwrap()
702 .build()
703 .unwrap();
704
705 assert_eq!(format!("{:?}",expected), format!("{:?}",actual));
706 }
707
708 async fn test_update_payment_link() {
710 use dotenv::dotenv;
711 use std::env;
712
713 dotenv().ok();
714 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
715 let sut = SquareClient::new(&access_token);
716
717 let input = (
718 "R6BRAXXKPCMYI2ZQ".to_string(),
719 UpdatePaymentLinkWrapper {
720 payment_link: objects::PaymentLink {
721 id: None,
722 version: 5,
723 checkout_options: None,
724 created_at: None,
725 description: None,
726 order_id: None,
727 payment_note: None,
728 pre_populated_data: None,
729 updated_at: None,
730 url: None
731 }
732 });
733
734 let res = sut.checkout()
735 .update(input.0, input.1)
736 .await;
737
738 assert!(res.is_ok());
739 }
740}