1use crate::api::{SquareAPI, Verb};
6use crate::client::SquareClient;
7use crate::errors::{SquareError, ValidationError};
8use crate::objects::{DeviceCheckoutOptions, Money, PaymentOptions, TerminalCheckout,
9 TerminalCheckoutQuery, TerminalRefund, TerminalRefundQuery};
10use crate::objects::enums::{CheckoutOptionsPaymentType, TerminalCheckoutStatus};
11use crate::response::SquareResponse;
12
13use serde::{Deserialize, Serialize};
14use uuid::Uuid;
15use crate::objects::TimeRange;
16use crate::builder::{AddField, Builder, ParentBuilder, Validate, Buildable};
17
18impl SquareClient {
19 pub fn terminal(&self) -> Terminal {
20 Terminal {
21 client: &self
22 }
23 }
24}
25
26pub struct Terminal<'a> {
27 client: &'a SquareClient,
28}
29
30impl<'a> Terminal<'a> {
31 pub async fn create_checkout(self, body: CreateTerminalCheckoutBody)
35 -> Result<SquareResponse, SquareError>{
36 self.client.request(
37 Verb::POST,
38 SquareAPI::Terminals("/checkouts".to_string()),
39 Some(&body),
40 None,
41 ).await
42 }
43
44 pub async fn search_checkout(self, body: SearchTerminalCheckoutBody)
49 -> Result<SquareResponse, SquareError>{
50 self.client.request(
51 Verb::GET,
52 SquareAPI::Terminals("/checkouts/search".to_string()),
53 Some(&body),
54 None,
55 ).await
56 }
57
58 pub async fn get_checkout(self, checkout_id: String)
61 -> Result<SquareResponse, SquareError>{
62 self.client.request(
63 Verb::GET,
64 SquareAPI::Terminals(format!("/checkouts/{}", checkout_id)),
65 None::<&CreateTerminalCheckoutBody>,
66 None,
67 ).await
68 }
69
70 pub async fn cancel_checkout(self, checkout_id: String)
72 -> Result<SquareResponse, SquareError>{
73 self.client.request(
74 Verb::POST,
75 SquareAPI::Terminals(format!("/checkouts/{}/cancel", checkout_id)),
76 None::<&CreateTerminalCheckoutBody>,
77 None,
78 ).await
79 }
80
81 pub async fn create_refund(self, body: CreateTerminalRefundBody)
86 -> Result<SquareResponse, SquareError>{
87 self.client.request(
88 Verb::POST,
89 SquareAPI::Terminals("/refunds".to_string()),
90 Some(&body),
91 None,
92 ).await
93 }
94
95 pub async fn search_refunds(self, body: SearchTerminalRefundBody)
99 -> Result<SquareResponse, SquareError>{
100 self.client.request(
101 Verb::POST,
102 SquareAPI::Terminals("/refunds/search".to_string()),
103 Some(&body),
104 None,
105 ).await
106 }
107
108 pub async fn get_refund(self, terminal_refund_id: String)
111 -> Result<SquareResponse, SquareError>{
112 self.client.request(
113 Verb::GET,
114 SquareAPI::Terminals(format!("/refunds/{}", terminal_refund_id)),
115 None::<&CreateTerminalRefundBody>,
116 None,
117 ).await
118 }
119
120 pub async fn cancel_refund(self, terminal_refund_id: String)
124 -> Result<SquareResponse, SquareError>{
125 self.client.request(
126 Verb::POST,
127 SquareAPI::Terminals(format!("/refunds/{}/cancel", terminal_refund_id)),
128 None::<&CreateTerminalRefundBody>,
129 None,
130 ).await
131 }
132}
133
134#[derive(Clone, Debug, Serialize, Deserialize)]
138pub struct CreateTerminalCheckoutBody {
139 idempotency_key: Option<String>,
140 checkout: TerminalCheckout,
141}
142
143impl Default for CreateTerminalCheckoutBody {
144 fn default() -> Self {
145 CreateTerminalCheckoutBody {
146 idempotency_key: None,
147 checkout: TerminalCheckout::default(),
148 }
149 }
150}
151
152impl Validate for CreateTerminalCheckoutBody {
153 fn validate(mut self) -> Result<Self, ValidationError> where Self: Sized {
154 if self.checkout.amount_money.is_some() &&
155 self.checkout.device_options.is_some() {
156 self.idempotency_key = Some(Uuid::new_v4().to_string());
157 Ok(self)
158 } else {
159 Err(ValidationError)
160 }
161 }
162}
163
164impl<T: ParentBuilder> Builder<CreateTerminalCheckoutBody, T> {
165 pub fn amount_money(mut self, amount: Money) -> Self {
166 self.body.checkout.amount_money = Some(amount);
167
168 self
169 }
170
171 pub fn device_options(mut self, device_options: DeviceCheckoutOptions) -> Self {
172 self.body.checkout.device_options = Some(device_options);
173
174 self
175 }
176
177 pub fn customer_id(mut self, customer_id: String) -> Self {
178 self.body.checkout.customer_id = Some(customer_id);
179
180 self
181 }
182
183 pub fn deadline_duration(mut self, deadline_duration: String) -> Self {
184 self.body.checkout.deadline_duration = Some(deadline_duration);
185
186 self
187 }
188
189 pub fn note(mut self, note: String) -> Self {
190 self.body.checkout.note = Some(note);
191
192 self
193 }
194
195 pub fn order_id(mut self, order_id: String) -> Self {
196 self.body.checkout.order_id = Some(order_id);
197
198 self
199 }
200
201 pub fn payment_type(mut self, payment_type: CheckoutOptionsPaymentType) -> Self {
202 self.body.checkout.payment_type = Some(payment_type);
203
204 self
205 }
206
207 pub fn payment_options(mut self, payment_options: PaymentOptions) -> Self {
208 self.body.checkout.payment_options = Some(payment_options);
209
210 self
211 }
212
213 pub fn reference_id(mut self, reference_id: String) -> Self {
214 self.body.checkout.reference_id = Some(reference_id);
215
216 self
217 }
218}
219
220impl AddField<DeviceCheckoutOptions> for CreateTerminalCheckoutBody {
221 fn add_field(&mut self, field: DeviceCheckoutOptions) {
222 self.checkout.device_options = Some(field);
223 }
224}
225
226#[derive(Clone, Debug, Serialize, Deserialize, Default)]
230pub struct SearchTerminalCheckoutBody {
231 query: Option<TerminalCheckoutQuery>,
232 cursor: Option<String>,
233 limit: Option<i32>,
234}
235
236impl Validate for SearchTerminalCheckoutBody {
237 fn validate(self) -> Result<Self, ValidationError> where Self: Sized {
238 Ok(self)
239 }
240}
241
242impl<T: ParentBuilder> Builder<SearchTerminalCheckoutBody, T> {
243 pub fn query(mut self, query: TerminalCheckoutQuery) -> Self {
244 self.body.query = Some(query);
245
246 self
247 }
248
249 pub fn cursor(mut self, cursor: String) -> Self {
250 self.body.cursor = Some(cursor);
251
252 self
253 }
254
255 pub fn limit(mut self, limit: i32) -> Self {
256 self.body.limit = Some(limit);
257
258 self
259 }
260}
261
262impl AddField<TerminalCheckoutQuery> for SearchTerminalCheckoutBody {
263 fn add_field(&mut self, field: TerminalCheckoutQuery) {
264 self.query = Some(field);
265 }
266}
267
268#[derive(Clone, Debug, Serialize, Deserialize, Default)]
272pub struct CreateTerminalRefundBody {
273 idempotency_key: Option<String>,
274 refund: TerminalRefund,
275}
276
277impl Validate for CreateTerminalRefundBody {
278 fn validate(mut self) -> Result<Self, ValidationError> where Self: Sized {
279 if self.refund.device_id.is_some() &&
280 self.refund.amount_money.is_some() &&
281 self.refund.reason.is_some() &&
282 self.refund.payment_id.is_some() {
283 self.idempotency_key = Some(Uuid::new_v4().to_string());
284
285 Ok(self)
286 } else {
287 Err(ValidationError)
288 }
289 }
290}
291
292impl<T: ParentBuilder> Builder<CreateTerminalRefundBody, T> {
293 pub fn amount_money(mut self, amount_money: Money) -> Self {
294 self.body.refund.amount_money = Some(amount_money);
295
296 self
297 }
298
299 pub fn device_id(mut self, device_id: String) -> Self {
300 self.body.refund.device_id = Some(device_id);
301
302 self
303 }
304
305 pub fn payment_id(mut self, payment_id: String) -> Self {
306 self.body.refund.payment_id = Some(payment_id);
307
308 self
309 }
310
311 pub fn reason(mut self, reason: String) -> Self {
312 self.body.refund.reason = Some(reason);
313
314 self
315 }
316
317 pub fn deadline_duration(mut self, reason: String) -> Self {
318 self.body.refund.deadline_duration = Some(reason);
319
320 self
321 }
322}
323
324#[derive(Clone, Debug, Serialize, Deserialize, Default)]
328pub struct SearchTerminalRefundBody {
329 cursor: Option<String>,
330 limit: Option<i32>,
331 query: Option<TerminalRefundQuery>,
332}
333
334impl Validate for SearchTerminalRefundBody {
335 fn validate(self) -> Result<Self, ValidationError> where Self: Sized {
336 Ok(self)
337 }
338}
339
340impl<T: ParentBuilder> Builder<SearchTerminalRefundBody, T> {
341 pub fn query(mut self, query: TerminalRefundQuery) -> Self {
342 self.body.query = Some(query);
343
344 self
345 }
346
347 pub fn limit(mut self, limit: i32) -> Self {
348 self.body.limit = Some(limit);
349
350 self
351 }
352
353 pub fn cursor(mut self, cursor: String) -> Self {
354 self.body.cursor = Some(cursor);
355
356 self
357 }
358}
359
360impl AddField<TerminalRefundQuery> for SearchTerminalRefundBody {
361 fn add_field(&mut self, field: TerminalRefundQuery) {
362 self.query = Some(field);
363 }
364}
365
366#[cfg(test)]
367mod test_terminals {
368 use crate::builder::BackIntoBuilder;
369 use super::*;
370 use crate::objects::enums::{Currency, SortOrder};
371 use crate::objects::{TerminalCheckoutQueryFilter, TerminalCheckoutQuerySort};
372
373 #[tokio::test]
374 async fn test_create_terminal_checkout_body_builder() {
375 let expected = CreateTerminalCheckoutBody {
376 idempotency_key: None,
377 checkout: TerminalCheckout {
378 id: None,
379 amount_money: Some(Money {
380 amount: Some(10),
381 currency: Currency::USD
382 }),
383 device_options: Some(DeviceCheckoutOptions {
384 device_id: Some("some_id".to_string()),
385 collect_signature: Some(true),
386 show_itemized_cart: None,
387 skip_receipt_screen: Some(true),
388 tip_settings: None
389 }),
390 app_fee_money: None,
391 app_id: None,
392 cancel_reason: None,
393 created_at: None,
394 customer_id: None,
395 deadline_duration: None,
396 location_id: None,
397 note: None,
398 order_id: None,
399 payment_ids: None,
400 payment_options: None,
401 payment_type: None,
402 reference_id: None,
403 status: None,
404 updated_at: None
405 }
406 };
407
408 let mut actual = Builder::from(CreateTerminalCheckoutBody::default())
409 .amount_money(Money { amount: Some(10), currency: Currency::USD })
410 .sub_builder_from(DeviceCheckoutOptions::default())
411 .device_id("some_id".to_string())
412 .collect_signature()
413 .skip_receipt_screen()
414 .build()
415 .unwrap()
416 .build()
417 .unwrap();
418
419 assert!(actual.idempotency_key.is_some());
420
421 actual.idempotency_key = None;
422
423 assert_eq!(format!("{:?}", expected), format!("{:?}", actual))
424 }
425
426 #[tokio::test]
427 async fn test_search_terminal_checkout_body_builder() {
428 let expected = SearchTerminalCheckoutBody {
429 query: Some(TerminalCheckoutQuery {
430 filter: Some(TerminalCheckoutQueryFilter {
431 created_at: None,
432 device_id: Some("some_id".to_string()),
433 status: None
434 }),
435 sort: Some(TerminalCheckoutQuerySort {
436 sort_order: Some(SortOrder::Asc)
437 })
438 }),
439 cursor: None,
440 limit: Some(10)
441 };
442
443 let actual = Builder::from(SearchTerminalCheckoutBody::default())
444 .limit(10)
445 .sub_builder_from(TerminalCheckoutQuery::default())
446 .sort_ascending()
447 .device_id("some_id".to_string())
448 .build()
449 .unwrap()
450 .build()
451 .unwrap();
452
453 assert_eq!(format!("{:?}", expected), format!("{:?}", actual))
454 }
455
456 async fn test_search_checkouts() {
458 use dotenv::dotenv;
459 use std::env;
460
461 dotenv().ok();
462 let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
463 let sut = SquareClient::new(&access_token);
464
465 let input = SearchTerminalCheckoutBody {
466 query: Some(TerminalCheckoutQuery {
467 filter: Some(TerminalCheckoutQueryFilter {
468 created_at: None,
469 device_id: None,
470 status: Some(TerminalCheckoutStatus::Completed)
471 }),
472 sort: Some(TerminalCheckoutQuerySort {
473 sort_order: Some(SortOrder::Asc)
474 })
475 }),
476 cursor: None,
477 limit: Some(10)
478 };
479
480 let res = sut.terminal()
481 .search_checkout(input)
482 .await;
483
484 assert!(res.is_err())
485 }
486
487 #[tokio::test]
488 async fn test_create_terminal_refund_body_builder() {
489 let expected = CreateTerminalRefundBody {
490 idempotency_key: None,
491 refund: TerminalRefund {
492 id: None,
493 amount_money: Some(Money {
494 amount: Some(10),
495 currency: Currency::USD
496 }),
497 device_id: Some("some_id".to_string()),
498 payment_id: Some("some_id".to_string()),
499 reason: Some("some reason".to_string()),
500 app_id: None,
501 cancel_reason: None,
502 created_at: None,
503 deadline_duration: None,
504 location_id: None,
505 order_id: None,
506 refund_id: None,
507 status: None,
508 updated_at: None
509 }
510 };
511
512 let mut actual = Builder::from(CreateTerminalRefundBody::default())
513 .amount_money(Money { amount: Some(10), currency: Currency::USD })
514 .device_id("some_id".to_string())
515 .payment_id("some_id".to_string())
516 .reason("some reason".to_string())
517 .build()
518 .unwrap();
519
520 actual.idempotency_key = None;
521
522 assert_eq!(format!("{:?}", expected), format!("{:?}", actual))
523 }
524
525 #[tokio::test]
526 async fn test_create_terminal_refund_body_builder_fail() {
527
528 let res = Builder::from(CreateTerminalRefundBody::default())
529 .payment_id("some_id".to_string())
530 .device_id("some_id".to_string())
531 .amount_money(Money { amount: Some(10), currency: Currency::USD })
532 .build();
533
534 assert!(res.is_err())
535 }
536
537 #[tokio::test]
538 async fn test_search_terminal_refund_body_builder() {
539 use crate::objects::TerminalRefundQueryFilter;
540 let expected = SearchTerminalRefundBody {
541 cursor: Some("some cursor".to_string()),
542 limit: Some(10),
543 query: Some(TerminalRefundQuery{
544 filter: Some(TerminalRefundQueryFilter {
545 created_at: None,
546 device_id: Some("some_id".to_string()),
547 status: Some(TerminalCheckoutStatus::CancelRequested)
548 }),
549 sort: Some(TerminalCheckoutQuerySort { sort_order: Some(SortOrder::Desc) })
550 })
551 };
552
553 let actual = Builder::from(SearchTerminalRefundBody::default())
554 .limit(10)
555 .cursor("some cursor".to_string())
556 .sub_builder_from(TerminalRefundQuery::default())
557 .device_id("some_id".to_string())
558 .sort_descending()
559 .cancel_requested()
560 .build()
561 .unwrap()
562 .build()
563 .unwrap();
564
565 assert_eq!(format!("{:?}", expected), format!("{:?}", actual))
566 }
567}
568