square_ox/api/
payment.rs

1/*!
2Payment functionality of the [Square API](https://developer.squareup.com).
3*/
4
5use 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    /// Retrieves a list of payments taken by the account making the request.
31    /// [Open in API Reference](https://developer.squareup.com/reference/square/payments/list-payments)
32    ///
33    /// # Arguments
34    /// * `parameters` - A vector of parameters created through the
35    /// [ListPaymentsParametersBuilder](ListPaymentsParametersBuilder)
36    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    /// Create a payment with the given [Payment](Payment) to the Square API
46    /// and get the response back
47    ///
48    /// # Arguments
49    /// * `payment` - A [Payment](Payment)
50    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    /// Cancels (voids) a payment identified by the idempotency key that is specified in the request.
60    /// [Open in API Reference](https://developer.squareup.com/reference/square/payments/cancel-payment-by-idempotency-key)
61    ///
62    /// # Arguments
63    /// * `idempotency_key` - The idempotency key identifying the payment to be canceled.
64    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    /// Retrieves details for a specific payment.
74    /// [Open in API Reference](https://developer.squareup.com/reference/square/payments/get-payment)
75    ///
76    /// # Arguments
77    /// * `payment_id` - The idempotency key identifying the payment to be canceled.
78    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    /// Updates a payment with the APPROVED status.
88    /// You can update the `amount_money` and `tip_money` using this endpoint.
89    /// [Open in API Reference](https://developer.squareup.com/reference/square/payments/get-payment)
90    ///
91    /// # Arguments
92    /// * `payment_id` - The idempotency key identifying the payment to be updated.
93    /// * `body` - The request body with the updated [Payment](Payment) object.
94    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    /// Cancels (voids) a payment.
105    /// [Open in API Reference](https://developer.squareup.com/reference/square/payments/cancel-payment)
106    ///
107    /// # Arguments
108    /// * `payment_id` - The idempotency key identifying the payment to be canceled.
109    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    /// Cancels (voids) a payment.
120    /// [Open in API Reference](https://developer.squareup.com/reference/square/payments/cancel-payment)
121    ///
122    /// # Arguments
123    /// * `payment_id` - The idempotency key identifying the payment to be completed.
124    /// * `version_token` - Used for optimistic concurrency. This opaque token identifies the
125    /// current [Payment](Payment) version that the caller expects. If the server has a different
126    /// version of the [Payment](Payment), the update fails and a response with a VERSION_MISMATCH
127    /// error is returned.
128    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// -------------------------------------------------------------------------------------------------
142// ListPaymentsParametersBuilder implementation
143// -------------------------------------------------------------------------------------------------
144#[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    /// The timestamp for the beginning of the reporting period, in RFC 3339 format. Inclusive.
163    /// Default: The current time minus one year.
164    pub fn begin_time(mut self, begin_time: String) -> Self {
165        self.begin_time = Some(begin_time);
166
167        self
168    }
169
170    /// The timestamp for the end of the reporting period, in RFC 3339 format.
171    // Default: The current time.
172    pub fn end_time(mut self, end_time: String) -> Self {
173        self.end_time = Some(end_time);
174
175        self
176    }
177
178    /// The order in which results are listed.
179    pub fn sort_ascending(mut self) -> Self {
180        self.sort_order = Some(SortOrder::Asc);
181
182        self
183    }
184
185    /// The order in which results are listed.
186    pub fn sort_descending(mut self) -> Self {
187        self.sort_order = Some(SortOrder::Desc);
188
189        self
190    }
191
192    /// A pagination cursor returned by a previous call to this endpoint.
193    /// Provide this cursor to retrieve the next set of results for the original query.
194    pub fn cursor(mut self, cursor: String) -> Self {
195        self.cursor = Some(cursor);
196
197        self
198    }
199
200    /// Limit results to the location supplied. By default, results are returned for the default
201    /// (main) location associated with the seller.
202    pub fn location_id(mut self, location_id: String) -> Self {
203        self.location_id = Some(location_id);
204
205        self
206    }
207
208    /// The exact amount in the total_money for a payment.
209    pub fn total(mut self, total: i32) -> Self {
210        self.total = Some(total);
211
212        self
213    }
214
215    /// The last four digits of a payment card.
216    pub fn last_4(mut self, last_4: String) -> Self {
217        self.last_4 = Some(last_4);
218
219        self
220    }
221
222    /// The brand of the payment card (for example, VISA).
223    pub fn card_brand(mut self, card_brand: String) -> Self {
224        self.card_brand = Some(card_brand);
225
226        self
227    }
228
229    /// The maximum number of results to be returned in a single page.
230    /// It is possible to receive fewer results than the specified limit on a given page.
231    //
232    // The default value of 100 is also the maximum allowed value.
233    // If the provided value is greater than 100, it is ignored and the default value is used
234    // instead.
235    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// -------------------------------------------------------------------------------------------------
291// PaymentRequest implementation
292// -------------------------------------------------------------------------------------------------
293/// The representation of a payment to the square API
294#[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// -------------------------------------------------------------------------------------------------
376// CancelByIdempotencyKey implementation
377// -------------------------------------------------------------------------------------------------
378#[derive(Serialize, Debug, Deserialize)]
379struct CancelByIdempotencyKey {
380    idempotency_key: String,
381}
382
383// -------------------------------------------------------------------------------------------------
384// UpdatePaymentBody implementation
385// -------------------------------------------------------------------------------------------------
386#[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    // #[tokio::test]
529    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    // #[tokio::test]
620    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    // #[tokio::test]
681    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}