1use crate::api::{SquareAPI, Verb};
6use crate::client::SquareClient;
7use crate::errors::{SquareError, ValidationError};
8use crate::objects::{Customer, Order, OrderReward, OrderServiceCharge, SearchOrdersQuery};
9use crate::response::SquareResponse;
10use crate::builder::{Builder, ParentBuilder, Validate, BackIntoBuilder, AddField, Buildable};
11use square_ox_derive::Builder;
12
13use serde::{Serialize, Deserialize};
14use uuid::Uuid;
15
16impl SquareClient {
17 pub fn orders(&self) -> Orders {
18 Orders {
19 client: &self,
20 }
21 }
22}
23
24pub struct Orders<'a> {
25 client: &'a SquareClient
26}
27
28impl<'a> Orders<'a> {
29 pub async fn create(self, body: CreateOrderBody)
33 -> Result<SquareResponse, SquareError> {
34 self.client.request(
35 Verb::POST,
36 SquareAPI::Orders("".to_string()),
37 Some(&body),
38 None,
39 ).await
40 }
41
42 pub async fn search(self, body: SearchOrderBody)
45 -> Result<SquareResponse, SquareError> {
46 self.client.request(
47 Verb::POST,
48 SquareAPI::Orders("/search".to_string()),
49 Some(&body),
50 None,
51 ).await
52 }
53
54 pub async fn retrieve(self, id: String)
57 -> Result<SquareResponse, SquareError> {
58 self.client.request(
59 Verb::GET,
60 SquareAPI::Orders(format!("/{}", id)),
61 None::<&SearchOrderBody>,
62 None,
63 ).await
64 }
65
66 pub async fn update(self, id: String, body: OrderUpdateBody)
69 -> Result<SquareResponse, SquareError> {
70 self.client.request(
71 Verb::PUT,
72 SquareAPI::Orders(format!("/{}", id)),
73 Some(&body),
74 None,
75 ).await
76 }
77
78 pub async fn pay(self, id: String, body: PayOrderBody)
82 -> Result<SquareResponse, SquareError> {
83 self.client.request(
84 Verb::POST,
85 SquareAPI::Orders(format!("/{}/pay", id)),
86 Some(&body),
87 None,
88 ).await
89 }
90
91 pub async fn calculate(self, body: OrderCalculateBody)
94 -> Result<SquareResponse, SquareError> {
95 self.client.request(
96 Verb::POST,
97 SquareAPI::Orders("/calculate".to_string()),
98 Some(&body),
99 None,
100 ).await
101 }
102}
103
104#[derive(Clone, Debug, Serialize, Deserialize, Default, Builder)]
105pub struct CreateOrderBody {
106 #[serde(skip_serializing_if = "Option::is_none")]
107 #[builder_rand("uuid")]
108 #[builder_vis("private")]
109 idempotency_key: Option<String>,
110 order: Order,
111}
112
113impl AddField<Order> for CreateOrderBody {
114 fn add_field(&mut self, field: Order) {
115 self.order = field;
116 }
117}
118
119#[derive(Clone, Debug, Serialize, Deserialize, Builder)]
120pub struct SearchOrderBody {
121 #[serde(skip_serializing_if = "Option::is_none")]
122 cursor: Option<String>,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 limit: Option<i32>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 #[builder_into]
127 location_ids: Option<Vec<String>>,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 query: Option<SearchOrdersQuery>,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 return_entries: Option<bool>
132}
133
134impl Default for SearchOrderBody {
135 fn default() -> Self {
136 SearchOrderBody {
137 cursor: None,
138 limit: None,
139 location_ids: None,
140 query: None,
141 return_entries: Some(true)
142 }
143 }
144}
145
146impl AddField<SearchOrdersQuery> for SearchOrderBody {
149 fn add_field(&mut self, field: SearchOrdersQuery) {
150 self.query = Some(field);
151 }
152}
153
154#[derive(Clone, Debug, Serialize, Default, Builder)]
155pub struct OrderUpdateBody {
156 fields_to_clear: Option<Vec<String>>,
157 #[builder_rand("uuid")]
158 idempotency_key: Option<String>,
159 #[builder_validate("is_some")]
160 order: Option<Order>,
161}
162
163impl AddField<Order> for OrderUpdateBody {
166 fn add_field(&mut self, field: Order) {
167 self.order = Some(field);
168 }
169}
170
171#[derive(Clone, Debug, Serialize, Default, Builder)]
172pub struct PayOrderBody {
173 #[builder_rand("uuid")]
174 #[serde(skip_serializing_if = "Option::is_none")]
175 idempotency_key: Option<String>,
176 #[serde(skip_serializing_if = "Option::is_none")]
177 #[builder_validate("is_some")]
178 order_version: Option<i64>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 #[builder_validate("is_some")]
181 payment_ids: Option<Vec<String>>,
182}
183
184#[derive(Clone, Debug, Serialize, Default, Builder)]
185pub struct OrderCalculateBody {
186 #[builder_validate("is_some")]
187 #[serde(skip_serializing_if = "Option::is_none")]
188 order: Option<Order>,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 proposed_rewards: Option<Vec<OrderReward>>,
191}
192
193impl AddField<Order> for OrderCalculateBody {
196 fn add_field(&mut self, field: Order) {
197 self.order = Some(field)
198 }
199}
200
201impl AddField<OrderReward> for OrderCalculateBody {
204 fn add_field(&mut self, field: OrderReward) {
205 match self.proposed_rewards.as_mut() {
206 Some(rewards) => rewards.push(field),
207 None => self.proposed_rewards = Some(vec![field])
208 }
209 }
210}
211
212#[cfg(test)]
213mod test_orders {
214 use crate::builder::Nil;
215 use crate::objects;
216 use crate::objects::enums::{Currency, OrderServiceChargeCalculationPhase, SortOrder, SearchOrdersSortField};
217 use crate::objects::{Money, SearchOrdersSort};
218 use super::*;
219
220 #[tokio::test]
221 async fn test_create_order_body_builder() {
222 let expected = CreateOrderBody {
223 idempotency_key: None,
224 order: Order {
225 id: None,
226 location_id: Some("location_id".to_string()),
227 close_at: None,
228 created_at: None,
229 customer_id: Some("customer_id".to_string()),
230 discounts: None,
231 fulfillments: None,
232 line_items: None,
233 metadata: None,
234 net_amounts: None,
235 pricing_options: None,
236 reference_id: None,
237 refunds: None,
238 return_amounts: None,
239 returns: None,
240 rewards: None,
241 rounding_adjustment: None,
242 service_charges: Some(vec![OrderServiceCharge {
243 amount_money: Some(Money{ amount: Some(10), currency: Currency::USD }),
244 applied_money: None,
245 applied_taxes: None,
246 calculation_phase: Some(OrderServiceChargeCalculationPhase::TotalPhase),
247 catalog_object_id: None,
248 catalog_version: None,
249 metadata: None,
250 name: Some("some name".to_string()),
251 percentage: None,
252 taxable: None,
253 total_money: None,
254 total_tax_money: None,
255 service_charge_type: None,
256 uid: None
257 }]),
258 source: None,
259 state: None,
260 taxes: None,
261 tenders: None,
262 ticket_name: None,
263 total_discount_money: None,
264 total_money: None,
265 total_service_charge_money: None,
266 total_tax_money: None,
267 total_tip_money: None,
268 updated_at: None,
269 version: None
270 }
271 };
272
273 let mut actual = Builder::from(CreateOrderBody::default())
274 .sub_builder_from(Order::default())
275 .location_id("location_id")
276 .customer_id("customer_id".to_string())
277 .sub_builder_from(OrderServiceCharge::default())
278 .amount_money(Money { amount: Some(10), currency: Currency::USD })
279 .name("some name")
280 .calculation_phase(OrderServiceChargeCalculationPhase::TotalPhase)
281 .build()
282 .unwrap()
283 .build()
284 .unwrap()
285 .build()
286 .unwrap();
287
288 assert!(actual.idempotency_key.is_some());
289
290 actual.idempotency_key = None;
291
292 assert_eq!(format!("{:?}", expected), format!("{:?}", actual))
293 }
294
295 #[tokio::test]
296 async fn test_create_order_body_builder_fail() {
297 let actual = Builder::from(CreateOrderBody::default())
298 .sub_builder_from(Order::default())
299 .location_id("location_id".to_string())
300 .customer_id("customer_id".to_string())
301 .sub_builder_from(OrderServiceCharge::default())
302 .amount_money(Money { amount: Some(10), currency: Currency::USD })
303 .calculation_phase(OrderServiceChargeCalculationPhase::TotalPhase)
304 .build();
305
306 assert!(actual.is_err());
307 }
308
309 #[tokio::test]
310 async fn test_create_order() {
311 use dotenv::dotenv;
312 use std::env;
313
314 dotenv().ok();
315 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
316 let sut = SquareClient::new(&access_token);
317
318 let input = CreateOrderBody {
319 idempotency_key: None,
320 order: objects::Order {
321 id: None,
322 location_id: Some("L1JC53TYHS40Z".to_string()),
323 close_at: None,
324 created_at: None,
325 customer_id: None,
326 discounts: None,
327 fulfillments: None,
328 line_items: None,
329 metadata: None,
330 net_amounts: None,
331 pricing_options: None,
332 reference_id: None,
333 refunds: None,
334 return_amounts: None,
335 returns: None,
336 rewards: None,
337 rounding_adjustment: None,
338 service_charges: Some(vec![OrderServiceCharge {
339 amount_money: Some(Money{ amount: Some(15), currency: Currency::USD }),
340 applied_money: None,
341 applied_taxes: None,
342 calculation_phase: Some(OrderServiceChargeCalculationPhase::TotalPhase),
343 catalog_object_id: None,
344 catalog_version: None,
345 metadata: None,
346 name: Some("some name".to_string()),
347 percentage: None,
348 taxable: None,
349 total_money: None,
350 total_tax_money: None,
351 service_charge_type: None,
352 uid: None
353 }]),
354 source: None,
355 state: None,
356 taxes: None,
357 tenders: None,
358 ticket_name: None,
359 total_discount_money: None,
360 total_money: None,
361 total_service_charge_money: None,
362 total_tax_money: None,
363 total_tip_money: None,
364 updated_at: None,
365 version: None
366 }
367 };
368
369 let res = sut.orders()
370 .create(input)
371 .await;
372
373 assert!(res.is_ok())
374 }
375
376 #[tokio::test]
377 async fn test_search_order_body_builder() {
378 let expected = SearchOrderBody {
379 cursor: None,
380 limit: Some(10),
381 location_ids: Some(vec!["e23icos".to_string(), "daiooaa".to_string(), "pßasmxaskm".to_string()]),
382 query: Some(SearchOrdersQuery {
383 filter: None,
384 sort: Some(SearchOrdersSort {
385 sort_field: Some(SearchOrdersSortField::CreatedAt),
386 sort_order: Some(SortOrder::Asc)
387 })
388 }),
389 return_entries: Some(true)
390 };
391
392 let actual = Builder::from(SearchOrderBody::default())
393 .location_ids(
394 vec![
395 "e23icos".to_string(),
396 "daiooaa".to_string(),
397 "pßasmxaskm".to_string(),
398 ]
399 )
400 .limit(10)
401 .sub_builder_from(SearchOrdersQuery::default())
402 .sort_ascending()
403 .build()
404 .unwrap()
405 .build()
406 .unwrap();
407
408 assert_eq!(format!("{:?}", expected), format!("{:?}", actual))
409 }
410
411 #[tokio::test]
412 async fn test_search_orders() {
413 use dotenv::dotenv;
414 use std::env;
415
416 dotenv().ok();
417 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
418 let sut = SquareClient::new(&access_token);
419
420 let input = SearchOrderBody {
421 cursor: None,
422 limit: None,
423 location_ids: Some(vec!["L1JC53TYHS40Z".to_string()]),
424 query: Some(SearchOrdersQuery {
425 filter: None,
426 sort: Some(SearchOrdersSort {
427 sort_field: Some(SearchOrdersSortField::CreatedAt),
428 sort_order: Some(SortOrder::Asc)
429 })
430 }),
431 return_entries: Some(true)
432 };
433
434 let res = sut.orders()
435 .search(input)
436 .await;
437
438 assert!(res.is_ok())
439 }
440
441 #[tokio::test]
442 async fn test_retrieve_order() {
443 use dotenv::dotenv;
444 use std::env;
445
446 dotenv().ok();
447 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
448 let sut = SquareClient::new(&access_token);
449
450
451 let res = sut.orders()
452 .retrieve("HnbOXf4007VldqxbMvuzf0IjgyAZY".to_string())
453 .await;
454
455 assert!(res.is_ok())
456 }
457
458 #[tokio::test]
459 async fn test_update_order_body_fail() {
460
461 let res_vec = vec![
462 Builder::from(OrderUpdateBody::default())
463 .fields_to_clear(vec!["a_field".to_string(), "another_field".to_string()])
464 .sub_builder_from(Order::default())
465 .version(2)
466 .build(),
467 ];
468
469 res_vec.into_iter().for_each(|res| assert!(res.is_err()))
470 }
471
472 async fn test_update_order() {
474 use dotenv::dotenv;
475 use std::env;
476
477 dotenv().ok();
478 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
479 let sut = SquareClient::new(&access_token);
480
481 let input = OrderUpdateBody {
482 fields_to_clear: None,
483 idempotency_key: Some(Uuid::new_v4().to_string()),
484 order: Some(Order {
485 id: None,
486 location_id: Some("L1JC53TYHS40Z".to_string()),
487 close_at: None,
488 created_at: None,
489 customer_id: None,
490 discounts: None,
491 fulfillments: None,
492 line_items: None,
493 metadata: None,
494 net_amounts: None,
495 pricing_options: None,
496 reference_id: None,
497 refunds: None,
498 return_amounts: None,
499 returns: None,
500 rewards: None,
501 rounding_adjustment: None,
502 service_charges: None,
503 source: None,
504 state: None,
505 taxes: None,
506 tenders: None,
507 ticket_name: None,
508 total_discount_money: None,
509 total_money: None,
510 total_service_charge_money: None,
511 total_tax_money: None,
512 total_tip_money: None,
513 updated_at: None,
514 version: Some(2)
515 })
516 };
517
518 println!("{:?}", &input);
519
520 let res = sut.orders()
521 .update("TJn1daLZuaMmPGL8vbeFGSdxB9HZY".to_string(), input)
522 .await;
523
524 assert!(res.is_ok())
525 }
526
527 #[tokio::test]
528 async fn test_pay_order_body_builder() {
529
530 let expected = PayOrderBody {
531 idempotency_key: None,
532 order_version: Some(3),
533 payment_ids: Some(vec!["some_id".to_string()])
534 };
535
536 let mut actual = Builder::from(PayOrderBody::default())
537 .order_version(3)
538 .payment_ids(vec!["some_id".to_string()])
539 .build()
540 .unwrap();
541
542 assert!(actual.idempotency_key.is_some());
543
544 actual.idempotency_key = None;
545
546 assert_eq!(format!("{:?}", expected), format!("{:?}", actual));
547 }
548
549 #[tokio::test]
550 async fn test_order_calculate_body_builder() {
551
552 let expected = OrderCalculateBody {
553 order: Some(Order {
554 id: None,
555 location_id: Some("location_id".to_string()),
556 close_at: None,
557 created_at: None,
558 customer_id: None,
559 discounts: None,
560 fulfillments: None,
561 line_items: None,
562 metadata: None,
563 net_amounts: None,
564 pricing_options: None,
565 reference_id: None,
566 refunds: None,
567 return_amounts: None,
568 returns: None,
569 rewards: None,
570 rounding_adjustment: None,
571 service_charges: Some(vec![
572 OrderServiceCharge {
573 amount_money: Some(Money {
574 amount: Some(20),
575 currency: Currency::USD
576 }),
577 applied_money: None,
578 applied_taxes: None,
579 calculation_phase: Some(OrderServiceChargeCalculationPhase::TotalPhase),
580 catalog_object_id: None,
581 catalog_version: None,
582 metadata: None,
583 name: Some("some name".to_string()),
584 percentage: None,
585 taxable: None,
586 total_money: None,
587 total_tax_money: None,
588 service_charge_type: None,
589 uid: None
590 }
591 ]),
592 source: None,
593 state: None,
594 taxes: None,
595 tenders: None,
596 ticket_name: None,
597 total_discount_money: None,
598 total_money: None,
599 total_service_charge_money: None,
600 total_tax_money: None,
601 total_tip_money: None,
602 updated_at: None,
603 version: Some(3)
604 }),
605 proposed_rewards: None
606 };
607
608 let mut actual = Builder::from(OrderCalculateBody::default())
609 .sub_builder_from(Order::default())
610 .location_id("location_id".to_string())
611 .sub_builder_from(OrderServiceCharge::default())
612 .amount_money(Money { amount: Some(20), currency: Currency::USD })
613 .name("some name".to_string())
614 .calculation_phase(OrderServiceChargeCalculationPhase::TotalPhase)
615 .build()
616 .unwrap()
617 .version(3)
618 .build()
619 .unwrap()
620 .build()
621 .unwrap();
622
623 assert_eq!(format!("{:?}", expected), format!("{:?}", actual));
624 }
625
626 #[tokio::test]
627 async fn test_calculate_order() {
628 use dotenv::dotenv;
629 use std::env;
630
631 dotenv().ok();
632 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
633 let sut = SquareClient::new(&access_token);
634
635 let input = OrderCalculateBody {
636 order: Some(Order {
637 id: None,
638 location_id: Some("L1JC53TYHS40Z".to_string()),
639 close_at: None,
640 created_at: None,
641 customer_id: None,
642 discounts: None,
643 fulfillments: None,
644 line_items: None,
645 metadata: None,
646 net_amounts: None,
647 pricing_options: None,
648 reference_id: None,
649 refunds: None,
650 return_amounts: None,
651 returns: None,
652 rewards: None,
653 rounding_adjustment: None,
654 service_charges: Some(vec![
655 OrderServiceCharge {
656 amount_money: Some(Money {
657 amount: Some(20),
658 currency: Currency::USD
659 }),
660 applied_money: None,
661 applied_taxes: None,
662 calculation_phase: Some(OrderServiceChargeCalculationPhase::TotalPhase),
663 catalog_object_id: None,
664 catalog_version: None,
665 metadata: None,
666 name: Some("some name".to_string()),
667 percentage: None,
668 taxable: None,
669 total_money: None,
670 total_tax_money: None,
671 service_charge_type: None,
672 uid: None
673 }
674 ]),
675 source: None,
676 state: None,
677 taxes: None,
678 tenders: None,
679 ticket_name: None,
680 total_discount_money: None,
681 total_money: None,
682 total_service_charge_money: None,
683 total_tax_money: None,
684 total_tip_money: None,
685 updated_at: None,
686 version: Some(3)
687 }),
688 proposed_rewards: None
689 };
690
691 let res = sut.orders()
692 .calculate(input)
693 .await;
694
695 assert!(res.is_ok())
696 }
697}
698