square_ox/api/
cards.rs

1/*!
2Cards functionality of the [Square API](https://developer.squareup.com).
3 */
4
5use crate::client::SquareClient;
6use crate::api::{Verb, SquareAPI};
7use crate::errors::{CardBuildError, SquareError, ValidationError};
8use crate::response::SquareResponse;
9use crate::objects::{Address, Card};
10
11use serde::{Deserialize, Serialize};
12use uuid::Uuid;
13use square_ox_derive::Builder;
14use crate::builder::{AddField, Builder, ParentBuilder, Validate, Buildable};
15use crate::objects::enums::SortOrder;
16
17impl SquareClient {
18    pub fn cards(&self) -> Cards {
19        Cards {
20            client: &self,
21        }
22    }
23}
24
25pub struct Cards<'a> {
26    client: &'a SquareClient,
27}
28
29impl<'a> Cards<'a> {
30    /// Find a specific [Card](Card) by querying the [Square API](https://developer.squareup.com)
31    /// with the card_id.
32    /// # Arguments:
33    /// * `card_id` - The id of the card you are looking to retrieve
34    ///
35    /// # Example
36    /// ```rust
37    ///use square_ox::{
38    ///    response::{SquareResponse, ResponseError},
39    ///    client::SquareClient
40    ///    };
41    ///
42    /// async {
43    ///     let locations = SquareClient::new("some_token")
44    ///         .cards()
45    ///         .retrieve("some_id".to_string())
46    ///         .await;
47    /// };
48    pub async fn retrieve(self, card_id: String)
49                               -> Result<SquareResponse, SquareError> {
50        self.client.request(
51            Verb::GET,
52            SquareAPI::Cards(format!("/{}", card_id)),
53            None::<&Card>,
54            None,
55        ).await
56    }
57
58    /// See which [Card](Card)s are on file by requesting the information from the
59    /// [Square API](https://developer.squareup.com) and receiving them formatted as a
60    /// list of [Card](Card)s.
61    /// # Example
62    /// ```rust
63    ///use square_ox::{
64    ///    response::{SquareResponse, ResponseError},
65    ///    client::SquareClient
66    ///    };
67    ///
68    /// async {
69    ///     let locations = SquareClient::new("some_token")
70    ///         .cards()
71    ///         .list(None)
72    ///         .await;
73    /// };
74    /// ```
75    pub async fn list(self, search_query: Option<Vec<(String, String)>>)
76                            -> Result<SquareResponse, SquareError> {
77        self.client.request(
78            Verb::GET,
79            SquareAPI::Cards("".to_string()),
80            None::<&Card>,
81            search_query,
82        ).await
83    }
84
85    /// Create a new [Card](Card) registered at the [Square API](https://developer.squareup.com).
86    /// # Arguments:
87    /// * `card` - A [Card](Card) wrapped in a [CardWrapper](CardWrapper)
88    ///
89    /// # Example
90    /// ```rust
91    ///  use square_ox::{
92    ///     response::{SquareResponse, ResponseError},
93    ///     client::SquareClient,
94    ///     api::cards::CardWrapper,
95    ///     builder::Builder
96    ///  };
97    ///
98    /// async {
99    ///     let card = Builder::from(CardWrapper::default())
100    ///     .source_id("some_id".to_string())
101    ///     .customer_id("some_id".to_string())
102    ///     .build()
103    ///     .await
104    ///     .unwrap();
105    ///
106    ///     let locations = SquareClient::new("some_token")
107    ///         .cards()
108    ///         .create(card)
109    ///         .await;
110    /// };
111    /// ```
112    pub async fn create(self, card: CardWrapper)
113                             -> Result<SquareResponse, SquareError> {
114        self.client.request(
115            Verb::POST,
116            SquareAPI::Cards("".to_string()),
117            Some(&card),
118            None,
119        ).await
120    }
121
122    /// Disable [Card](Card) at the [Square API](https://developer.squareup.com).
123    /// # Arguments:
124    /// * `card_id` - The id of the [Card](Card) you want to disable.
125    ///
126    /// # Example
127    /// ```rust
128    ///use square_ox::{
129    ///    response::{SquareResponse, ResponseError},
130    ///    client::SquareClient
131    ///    };
132    ///
133    /// async {
134    ///     let locations = SquareClient::new("some_token")
135    ///         .cards()
136    ///         .disable("some_id".to_string())
137    ///         .await;
138    /// };
139    /// ```
140    pub async fn disable(self, card_id: String)
141                              -> Result<SquareResponse, SquareError> {
142        self.client.request(
143            Verb::POST,
144            SquareAPI::Cards(format!("/{}/disable", card_id)),
145            None::<&Card>,
146            None,
147        ).await
148    }
149}
150
151#[derive(Default)]
152pub struct ListCardsQueryBuilder {
153    cursor: Option<String>,
154    customer_id: Option<String>,
155    include_disabled: Option<bool>,
156    reference_id: Option<String>,
157    sort_order: Option<SortOrder>,
158}
159
160impl ListCardsQueryBuilder {
161    pub fn new() -> Self {
162        Default::default()
163    }
164
165    pub fn cursor(mut self, cursor: String) -> Self {
166        self.cursor = Some(cursor);
167
168        self
169    }
170
171    pub fn customer_id(mut self, customer_id: String) -> Self {
172        self.customer_id = Some(customer_id);
173
174        self
175    }
176
177    pub fn include_disabled(mut self) -> Self {
178        self.include_disabled = Some(true);
179
180        self
181    }
182
183    pub fn exclude_disabled(mut self) -> Self {
184        self.include_disabled = Some(false);
185
186        self
187    }
188
189    pub fn reference_id(mut self, reference_id: String) -> Self {
190        self.reference_id = Some(reference_id);
191
192        self
193    }
194
195    pub fn sort_ascending(mut self) -> Self {
196        self.sort_order = Some(SortOrder::Asc);
197
198        self
199    }
200
201    pub fn sort_descending(mut self) -> Self {
202        self.sort_order = Some(SortOrder::Desc);
203
204        self
205    }
206
207    pub async fn build(self) -> Vec<(String, String)> {
208        let ListCardsQueryBuilder {
209            cursor,
210            customer_id,
211            include_disabled,
212            reference_id,
213            sort_order,
214        } = self;
215
216        let mut res = vec![];
217
218        if let Some(cursor) = cursor {
219            res.push(("cursor".to_string(), cursor))
220        }
221        if let Some(customer_id) = customer_id {
222            res.push(("customer_id".to_string(), customer_id))
223        }
224        if let Some(include_disabled) = include_disabled {
225            res.push(("include_disabled".to_string(), include_disabled.to_string()))
226        }
227        if let Some(reference_id) = reference_id {
228            res.push(("reference_id".to_string(), reference_id))
229        }
230        if let Some(sort_order) = sort_order {
231            res.push(("sort_order".to_string(), sort_order.to_string()))
232        }
233
234        res
235    }
236}
237
238#[derive(Clone, Serialize, Debug, Deserialize, Default, Builder)]
239pub struct CardWrapper {
240    pub(crate) card: Card,
241    #[builder_rand("uuid")]
242    #[builder_into]
243    pub(crate) idempotency_key: Option<String>,
244    #[builder_validate("is_some")]
245    #[builder_into]
246    pub(crate) source_id: Option<String>,
247    #[builder_vis("private")]
248    pub(crate) verification_token: Option<String>,
249}
250
251#[cfg(test)]
252mod test_cards {
253    use super::*;
254
255    #[tokio::test]
256    async fn test_list_cards_query_builder() {
257        let expected = vec![
258            ("cursor".to_string(), "dwcsdaw2390rec92".to_string()),
259            ("include_disabled".to_string(), "false".to_string()),
260            ("sort_order".to_string(), "ASC".to_string()),
261        ];
262        let actual = ListCardsQueryBuilder::new()
263            .exclude_disabled()
264            .sort_ascending()
265            .cursor("dwcsdaw2390rec92".to_string())
266            .build()
267            .await;
268
269        assert_eq!(expected, actual)
270    }
271
272    #[tokio::test]
273    async fn test_list_cards() {
274        use dotenv::dotenv;
275        use std::env;
276
277        dotenv().ok();
278        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
279        let sut = SquareClient::new(&access_token);
280
281        let input = vec![
282            ("include_disabled".to_string(), "false".to_string()),
283            ("sort_order".to_string(), "ASC".to_string()),
284        ];
285
286        let res = sut.cards()
287            .list(Some(input))
288            .await;
289
290        assert!(res.is_ok())
291
292    }
293
294    #[tokio::test]
295    async fn test_retrieve_card() {
296        use dotenv::dotenv;
297        use std::env;
298
299        dotenv().ok();
300        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
301        let sut = SquareClient::new(&access_token);
302
303        let res = sut.cards()
304            .retrieve("ccof:Es7R2xLyCWzmrKGI4GB".to_string())
305            .await;
306
307        assert!(res.is_ok())
308
309    }
310
311    #[tokio::test]
312    async fn test_card_wrapper_builder() {
313        let expected = CardWrapper {
314            card: Card {
315                id: None,
316                billing_address: None,
317                bin: None,
318                card_brand: None,
319                card_co_brand: None,
320                card_type: None,
321                cardholder_name: None,
322                customer_id: Some("EDH2RWZCFCRGZCZ99GMG8ZF59R".to_string()),
323                enabled: None,
324                exp_month: None,
325                exp_year: None,
326                fingerprint: None,
327                last_4: None,
328                merchant_id: None,
329                prepaid_type: None,
330                reference_id: None,
331                version: None
332            },
333            idempotency_key: None,
334            source_id: Some("cnon:card-nonce-ok".to_string()),
335            verification_token: None
336        };
337
338        let mut actual = Builder::from(CardWrapper::default())
339            .card(
340                Builder::from(Card::default())
341                    .customer_id("EDH2RWZCFCRGZCZ99GMG8ZF59R".to_string())
342                    .build()
343                    .unwrap()
344            )
345            .source_id("cnon:card-nonce-ok".to_string())
346            .build()
347            .unwrap();
348
349        assert!(actual.idempotency_key.is_some());
350
351        actual.idempotency_key = None;
352
353        assert_eq!(format!("{:?}", expected), format!("{:?}", actual));
354    }
355
356    #[tokio::test]
357    async fn test_card_wrapper_builder_fail() {
358        let res = Builder::from(CardWrapper::default())
359            .card(
360                Builder::from(Card::default())
361                    .customer_id("EDH2RWZCFCRGZCZ99GMG8ZF59R".to_string())
362                    .build()
363                    .unwrap()
364            )
365            .build();
366
367        assert!(res.is_err())
368    }
369
370    // #[tokio::test]
371    async fn test_create_card() {
372        use dotenv::dotenv;
373        use std::env;
374
375        dotenv().ok();
376        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
377        let sut = SquareClient::new(&access_token);
378
379        let input = CardWrapper {
380            card: Card {
381                id: None,
382                billing_address: None,
383                bin: None,
384                card_brand: None,
385                card_co_brand: None,
386                card_type: None,
387                cardholder_name: None,
388                customer_id: Some("EDH2RWZCFCRGZCZ99GMG8ZF59R".to_string()),
389                enabled: None,
390                exp_month: None,
391                exp_year: None,
392                fingerprint: None,
393                last_4: None,
394                merchant_id: None,
395                prepaid_type: None,
396                reference_id: None,
397                version: None
398            },
399            idempotency_key: Some(Uuid::new_v4().to_string()),
400            source_id: Some("cnon:card-nonce-ok".to_string()),
401            verification_token: None
402        };
403
404        let res = sut.cards()
405            .create(input)
406            .await;
407
408        assert!(res.is_ok())
409    }
410
411    #[tokio::test]
412    async fn test_disable_card() {
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 res = sut.cards()
421            .disable("ccof:ce0ogxL3KIHfNd4Z4GB".to_string())
422            .await;
423
424        assert!(res.is_ok())
425
426    }
427
428
429}