square_ox/api/
checkout.rs

1/*!
2Checkout functionality of the [Square API](https://developer.squareup.com).
3 */
4
5use 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    /// Link a checkout id to a checkout page in order to redirect customers
32    ///
33    /// # Arguments:
34    /// * `location_id` - The id of the location you would like to link to the checkout page.
35    /// * `create_order_request`- The request body of the create_checkout call wrapped in a
36    /// [CreateOrderRequestWrapper](CreateOrderRequestWrapper).
37    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    /// Lists all payment links registered at the [Square API](https://developer.squareup.com).
51    ///
52    /// # Arguments:
53    /// * `search_query` - The parameters restricting the listing of payment links. They are build
54    /// through the [ListPaymentLinksSearchQueryBuilder](ListPaymentLinksSearchQueryBuilder) and in
55    /// vector form.
56    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    /// Creates a Square-hosted checkout page. Applications can share the resulting payment link
69    /// with their buyer to pay for goods and services.
70    ///
71    /// # Arguments:
72    /// * `payment_link` - The body of the quest, holding the details of the payment link that is
73    /// being added. This body is wrapped by a [CreatePaymentLinkWrapper](CreatePaymentLinkWrapper).
74    /// The payment link must contain at least one Order or QuickPay object.
75    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    /// Deletes a payment link.
88    ///
89    /// # Arguments:
90    /// * `link_id` - The id of the payment link to delete.
91    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    /// Retrieves a payment link from the [Square API](https://developer.squareup.com).
104    ///
105    /// # Arguments:
106    /// * `link_id` - The id of the payment link to delete.
107    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    /// Updates a payment link at the [Square API](https://developer.squareup.com).
120    ///
121    /// # Arguments:
122    /// * `link_id` - The id of the payment link to update.
123    /// * `payment_link` - The updated [PaymentLink](PaymentLink).
124    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    // #[tokio::test]
709    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}