dd_sds/secondary_validation/
luhn_checksum.rs

1use crate::secondary_validation::{get_previous_digit, Validator};
2
3pub struct LuhnChecksum;
4
5impl Validator for LuhnChecksum {
6    fn is_valid_match(&self, regex_match: &str) -> bool {
7        let mut input_iter = regex_match.chars();
8
9        if let Some(checksum) = get_previous_digit(&mut input_iter) {
10            let mut sum: u32 = 0;
11            let mut is_odd = false;
12            while let Some(digit) = get_previous_digit(&mut input_iter) {
13                if is_odd {
14                    sum += digit
15                } else if digit > 4 {
16                    sum += digit * 2 - 9;
17                } else {
18                    sum += digit * 2
19                }
20                is_odd = !is_odd;
21            }
22            return (10 - (sum % 10)) % 10 == checksum;
23        }
24        false
25    }
26}
27
28#[cfg(test)]
29mod test {
30    use crate::secondary_validation::*;
31
32    #[test]
33    fn validate_various_credit_cards() {
34        let credit_cards = vec![
35            // source https://www.paypalobjects.com/en_AU/vhelp/paypalmanager_help/credit_card_numbers.htm
36            // American Express
37            "3782 822463 10005",
38            "3714 4963 5398 431",
39            // American Express Corporate
40            "378734493671000",
41            // Australian BankCard
42            "5610591081018250",
43            // Diners Club
44            "3056 930902 5904",
45            "3852 0000 0232 37",
46            // Discover
47            "6011111111111117",
48            "6011 0009 9013 9424",
49            // JCB
50            "3530111333300000",
51            "35660020 20360505",
52            // MasterCard
53            "5555555555554444",
54            "5105 1051 0510 5100",
55            // Visa
56            "4111 1111 1111 1111",
57            "40128888 88881881",
58            "4222222222222",
59            // Dankort (PBS)
60            "5019717010103742",
61            // Switch/Solo (Paymentech)
62            "6331101999990016",
63        ];
64        for credit_card in credit_cards {
65            println!("credit card input: {credit_card}");
66            assert!(LuhnChecksum.is_valid_match(credit_card));
67
68            let (split_credit_card, last_digit) = credit_card.split_at(credit_card.len() - 1);
69            let mut wrong_credit_card = split_credit_card.to_string();
70            wrong_credit_card
71                .push_str(&((last_digit.parse::<u32>().unwrap() + 1) * 2 % 10).to_string());
72
73            println!("wrong credit card input: {wrong_credit_card}");
74
75            assert!(!LuhnChecksum.is_valid_match(&wrong_credit_card));
76        }
77    }
78
79    #[test]
80    fn skip_non_digit_characters() {
81        assert!(LuhnChecksum.is_valid_match("378282246310005"));
82        // Same credit card with space and non-digit characters
83        assert!(LuhnChecksum.is_valid_match("3 7 8 2 8 2 2 4ABC, 6 3 1 🎅0 0 0 5"));
84    }
85}