mock_rs/provider/
payment.rs

1use crate::{
2    calculator::{iban::checksum, luhn},
3    helpers::{
4        base::{numerify, random_digit, random_element, random_index, random_key, random_letter},
5        miscellaneous::boolean,
6    },
7};
8use chrono::{Datelike, TimeZone, Utc};
9use rand::Rng;
10use std::collections::HashMap;
11
12pub trait PaymentTrait<'a> {
13    fn card_vendors() -> Vec<&'a str> {
14        vec![
15            "Visa",
16            "MasterCard",
17            "American Express",
18            "Discover Card",
19            "Visa Retired",
20            "JCB",
21        ]
22    }
23
24    /// List of card brand masks for generating valid credit card numbers
25    fn card_params() -> HashMap<&'a str, Vec<&'a str>> {
26        let mut card_params = HashMap::new();
27
28        card_params.insert(
29            "Visa",
30            vec![
31                "4539###########",
32                "4556###########",
33                "4916###########",
34                "4532###########",
35                "4929###########",
36                "40240071#######",
37                "4485###########",
38                "4716###########",
39                "4##############",
40            ],
41        );
42
43        card_params.insert(
44            "Visa Retired",
45            vec![
46                "4539########",
47                "4556########",
48                "4916########",
49                "4532########",
50                "4929########",
51                "40240071####",
52                "4485########",
53                "4716########",
54                "4###########",
55            ],
56        );
57
58        card_params.insert(
59            "MasterCard",
60            vec![
61                "2221###########",
62                "23#############",
63                "24#############",
64                "25#############",
65                "26#############",
66                "2720###########",
67                "51#############",
68                "52#############",
69                "53#############",
70                "54#############",
71                "55#############",
72            ],
73        );
74
75        card_params.insert("America Express", vec!["34############", "37############"]);
76
77        card_params.insert("Discover Card", vec!["6011###########"]);
78
79        card_params.insert("JCB", vec!["3528###########", "3589###########"]);
80
81        card_params
82    }
83
84    fn iban_formats() -> HashMap<&'a str, Vec<(char, u8)>> {
85        let mut iban_formats = HashMap::new();
86
87        iban_formats.insert("AD", vec![('n', 4), ('n', 4), ('c', 12)]);
88        iban_formats.insert("AE", vec![('n', 3), ('n', 16)]);
89        iban_formats.insert("AL", vec![('n', 8), ('c', 16)]);
90        iban_formats.insert("AT", vec![('n', 5), ('n', 11)]);
91        iban_formats.insert("AZ", vec![('a', 4), ('c', 20)]);
92        iban_formats.insert("BA", vec![('n', 3), ('n', 3), ('n', 8), ('n', 2)]);
93        iban_formats.insert("BE", vec![('n', 3), ('n', 7), ('n', 2)]);
94        iban_formats.insert("BG", vec![('a', 4), ('n', 4), ('n', 2), ('c', 8)]);
95        iban_formats.insert("BH", vec![('a', 4), ('c', 14)]);
96        iban_formats.insert(
97            "BR",
98            vec![('n', 8), ('n', 5), ('n', 10), ('a', 1), ('c', 1)],
99        );
100        iban_formats.insert("CH", vec![('n', 5), ('c', 12)]);
101        iban_formats.insert("CR", vec![('n', 4), ('n', 14)]);
102        iban_formats.insert("CY", vec![('n', 3), ('n', 5), ('c', 16)]);
103        iban_formats.insert("CZ", vec![('n', 4), ('n', 6), ('n', 10)]);
104        iban_formats.insert("DE", vec![('n', 8), ('n', 10)]);
105        iban_formats.insert("DK", vec![('n', 4), ('n', 9), ('n', 1)]);
106        iban_formats.insert("DO", vec![('c', 4), ('n', 20)]);
107        iban_formats.insert("EE", vec![('n', 2), ('n', 2), ('n', 11), ('n', 1)]);
108        iban_formats.insert("EG", vec![('n', 4), ('n', 4), ('n', 17)]);
109        iban_formats.insert(
110            "ES",
111            vec![('n', 4), ('n', 4), ('n', 1), ('n', 1), ('n', 10)],
112        );
113        iban_formats.insert("FI", vec![('n', 6), ('n', 7), ('n', 1)]);
114        iban_formats.insert("FR", vec![('n', 5), ('n', 5), ('c', 11), ('n', 2)]);
115        iban_formats.insert("GB", vec![('a', 4), ('n', 6), ('n', 8)]);
116        iban_formats.insert("GE", vec![('a', 2), ('n', 16)]);
117        iban_formats.insert("GI", vec![('a', 4), ('c', 15)]);
118        iban_formats.insert("GR", vec![('n', 3), ('n', 4), ('c', 16)]);
119        iban_formats.insert("GT", vec![('c', 4), ('c', 20)]);
120        iban_formats.insert("HR", vec![('n', 7), ('n', 10)]);
121        iban_formats.insert(
122            "HU",
123            vec![('n', 3), ('n', 4), ('n', 1), ('n', 15), ('n', 1)],
124        );
125        iban_formats.insert("IE", vec![('a', 4), ('n', 6), ('n', 8)]);
126        iban_formats.insert("IL", vec![('n', 3), ('n', 3), ('n', 13)]);
127        iban_formats.insert("IS", vec![('n', 4), ('n', 2), ('n', 6), ('n', 10)]);
128        iban_formats.insert("IT", vec![('a', 1), ('n', 5), ('n', 5), ('c', 12)]);
129        iban_formats.insert("KW", vec![('a', 4), ('n', 22)]);
130        iban_formats.insert("KZ", vec![('n', 3), ('c', 13)]);
131        iban_formats.insert("LB", vec![('n', 4), ('c', 20)]);
132        iban_formats.insert("LI", vec![('n', 5), ('c', 12)]);
133        iban_formats.insert("LT", vec![('n', 5), ('n', 11)]);
134        iban_formats.insert("LU", vec![('n', 3), ('c', 13)]);
135        iban_formats.insert("LV", vec![('a', 4), ('c', 13)]);
136        iban_formats.insert("MC", vec![('n', 5), ('n', 5), ('c', 11), ('n', 2)]);
137        iban_formats.insert("MD", vec![('c', 2), ('c', 18)]);
138        iban_formats.insert("ME", vec![('n', 3), ('n', 13), ('n', 2)]);
139        iban_formats.insert("MK", vec![('n', 3), ('c', 10), ('n', 2)]);
140        iban_formats.insert("MR", vec![('n', 5), ('n', 5), ('n', 11), ('n', 2)]);
141        iban_formats.insert("MT", vec![('a', 4), ('n', 5), ('c', 18)]);
142        iban_formats.insert(
143            "MU",
144            vec![('a', 4), ('n', 2), ('n', 2), ('n', 12), ('n', 3), ('a', 3)],
145        );
146        iban_formats.insert("NL", vec![('a', 4), ('n', 10)]);
147        iban_formats.insert("NO", vec![('n', 4), ('n', 6), ('n', 1)]);
148        iban_formats.insert("PK", vec![('a', 4), ('c', 16)]);
149        iban_formats.insert("PL", vec![('n', 8), ('n', 16)]);
150        iban_formats.insert("PS", vec![('a', 4), ('c', 21)]);
151        iban_formats.insert("PT", vec![('n', 4), ('n', 4), ('n', 11), ('n', 2)]);
152        iban_formats.insert("RO", vec![('a', 4), ('c', 16)]);
153        iban_formats.insert("RS", vec![('n', 3), ('n', 13), ('n', 2)]);
154        iban_formats.insert("SA", vec![('n', 2), ('c', 18)]);
155        iban_formats.insert("SE", vec![('n', 3), ('n', 16), ('n', 1)]);
156        iban_formats.insert("SI", vec![('n', 5), ('n', 8), ('n', 2)]);
157        iban_formats.insert("SK", vec![('n', 4), ('n', 6), ('n', 10)]);
158        iban_formats.insert("SM", vec![('a', 1), ('n', 5), ('n', 5), ('c', 12)]);
159        iban_formats.insert("TN", vec![('n', 2), ('n', 3), ('n', 13), ('n', 2)]);
160        iban_formats.insert("TR", vec![('n', 5), ('n', 1), ('c', 16)]);
161        iban_formats.insert("VG", vec![('a', 4), ('n', 16)]);
162
163        iban_formats
164    }
165
166    /// Returns a credit card vendor name
167    fn credit_card_type() -> String {
168        random_element(&Self::card_vendors()).to_string()
169    }
170
171    /// Returns the String of a credit card number.
172    fn credit_card_number(
173        credit_card_type: Option<&str>,
174        formatted: Option<bool>,
175        separator: Option<char>,
176    ) -> String {
177        let card_number;
178        let default_card_param = vec!["2221###########"];
179
180        let card_type = if let Some(card_type) = credit_card_type {
181            if !Self::card_vendors().contains(&card_type) {
182                panic!("{} is not a valid credit_card_type \n This is a list of the valid card types {:?}", 
183                card_type, Self::card_vendors())
184            }
185            card_type
186        } else {
187            let card_vendors = Self::card_vendors();
188            let random_index = random_index(&card_vendors);
189            let card_type = card_vendors[random_index];
190            card_type
191        };
192
193        let card_params = Self::card_params();
194
195        let card_param = card_params.get(&card_type).unwrap_or(&default_card_param);
196
197        let mask = random_element(&card_param);
198
199        let mut number = numerify(Some(mask));
200        number = format!("{}{}", number, luhn::compute_check_digit(&number));
201
202        card_number = if let Some(true) = formatted {
203            let separator = separator.unwrap_or('-');
204            let p1 = number.chars().take(4).collect::<String>();
205            let p2 = number.chars().skip(4).take(4).collect::<String>();
206            let p3 = number.chars().skip(8).take(4).collect::<String>();
207            let p4 = number.chars().skip(12).collect::<String>();
208            format!(
209                "{}{}{}{}{}{}{}",
210                p1, separator, p2, separator, p3, separator, p4
211            )
212        } else {
213            number
214        };
215
216        card_number
217    }
218
219    /// Returns a credit card expiration date.
220    /// The expiration date is in the format MM/YY.
221    fn credit_card_expiration_date(valid: Option<bool>) -> String {
222        let valid = valid.unwrap_or(true);
223
224        let random_day: u32 = rand::thread_rng().gen_range(1..28);
225        let random_month: u32 = rand::thread_rng().gen_range(1..12);
226
227        if valid {
228            let now = Utc::now().year();
229            let end = now + 3;
230            let random_year: i32 = rand::thread_rng().gen_range(now..end);
231
232            let date = Utc
233                .ymd(random_year, random_month, random_day)
234                .format("%m/%y");
235
236            return format!("{}", date);
237        }
238
239        let now = Utc::now().year();
240        let end = now - 3;
241        let random_year: i32 = rand::thread_rng().gen_range(end..now);
242
243        let date = Utc
244            .ymd(random_year, random_month, random_day)
245            .format("%m/%y");
246
247        format!("{}", date)
248    }
249
250    /// Returns credit card details with
251    ///
252    /// * credit_card_type
253    /// * credit_card_number
254    /// * credit_card_expiration_date
255    /// * holder_name
256    ///
257    /// Returns a HashMap
258    fn credit_card_details(valid: Option<bool>) -> HashMap<&'a str, String> {
259        let credit_card_type = Self::credit_card_type();
260        let credit_card_number = Self::credit_card_number(Some(&credit_card_type), None, None);
261        let name = "Hello";
262        let expiration_date = Self::credit_card_expiration_date(valid);
263
264        let mut details = HashMap::new();
265        details.insert("credit_card_type", credit_card_type);
266        details.insert("credit_card_number", credit_card_number);
267        details.insert("holder_name", name.to_owned());
268        details.insert("expiration_date", expiration_date);
269        details
270    }
271
272    /// Get International Bank Account Number (IBAN)
273    fn iban(country_code: Option<&str>, prefix: Option<&str>, mut length: Option<u8>) -> String {
274        let country_code = country_code
275            .unwrap_or(&random_key(&Self::iban_formats()).to_string())
276            .to_uppercase();
277
278        let iban_formats = Self::iban_formats();
279
280        let mut format = if iban_formats.contains_key(country_code.as_str()) {
281            iban_formats.get(country_code.as_str())
282        } else {
283            None
284        };
285
286        if let None = length {
287            length = if let None = format {
288                Some(24)
289            } else {
290                let mut length = 0;
291
292                for part in format.unwrap() {
293                    let (_class, group_count) = part;
294
295                    length = length + group_count;
296                }
297
298                Some(length)
299            };
300        }
301
302        let default_format = &vec![('n', length.unwrap())];
303
304        if format == None {
305            format = Some(default_format);
306        }
307
308        let mut expanded_format = "".to_string();
309
310        for item in format.unwrap() {
311            let (class, length) = item;
312            let format_part = class.to_string().as_str().repeat(*length as usize);
313            expanded_format.push_str(&format_part);
314        }
315
316        let mut result = prefix.unwrap_or("").to_string();
317        expanded_format = (&expanded_format[result.len()..]).to_string();
318
319        for class in expanded_format.chars() {
320            match class {
321                'c' => {
322                    if boolean(None) {
323                        result.push(random_digit() as char)
324                    } else {
325                        result.push_str(&random_letter().to_string().as_str().to_uppercase())
326                    };
327                }
328                'a' => result.push_str(&random_letter().to_string().as_str().to_uppercase()),
329                'n' => result.push(random_digit() as char),
330                _ => result.push_str(&random_letter().to_string().as_str().to_uppercase()),
331            }
332        }
333
334        let checksum = checksum(
335            format!(
336                "{country_code}00{result}",
337                country_code = country_code,
338                result = result
339            )
340            .as_str(),
341        );
342
343        format!("{}{}{}", country_code, checksum, result)
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350    struct TestPay {}
351    impl PaymentTrait<'_> for TestPay {}
352
353    #[test]
354    fn test_credit_card_type() {
355        let card_type = TestPay::credit_card_type();
356        assert!(TestPay::card_vendors().contains(&card_type.as_str()));
357    }
358
359    #[test]
360    fn test_credit_card_number() {
361        let card_number = TestPay::credit_card_number(None, None, None);
362        assert_eq!(card_number.len(), 16);
363    }
364
365    #[test]
366    fn test_credit_card_number_with_type() {
367        let card_number = TestPay::credit_card_number(Some("MasterCard"), None, None);
368        assert_eq!(card_number.len(), 16);
369    }
370
371    // TODO: Look into why some of these tests fail sometimes
372    #[test]
373    fn test_credit_card_number_with_separator() {
374        let card_number = TestPay::credit_card_number(None, Some(true), Some('-'));
375        println!("{}", card_number);
376        assert_eq!(card_number.len(), 19);
377    }
378
379    #[test]
380    fn test_card_expiration_date_with_valid_set_to_true() {
381        let date = TestPay::credit_card_expiration_date(Some(true));
382        assert_eq!(date.len(), 5);
383    }
384
385    #[test]
386    fn test_card_expiration_date_with_valid_set_to_false() {
387        let date = TestPay::credit_card_expiration_date(Some(false));
388        assert_eq!(date.len(), 5);
389    }
390
391    #[test]
392    fn test_card_details() {
393        let details = TestPay::credit_card_details(Some(true));
394        println!("{:?}", details);
395        assert_eq!(details.len(), 4);
396    }
397
398    #[test]
399    fn iban() {
400        let iban = TestPay::iban(None, None, None);
401        println!("{}", iban);
402        assert!(iban.len() > 0);
403    }
404}