From 12e81ea6a2e74691173895418f2129bb4d2752c2 Mon Sep 17 00:00:00 2001
From: "@im_8055" <38890773+Bhargavishnu@users.noreply.github.com>
Date: Tue, 26 Oct 2021 10:51:07 +0530
Subject: [PATCH] Add credit card string validator (#5583)

* Add credit card validator

* 

* Add return type hint

* Add test cases for validator function

* Add test cases

* Feature: Rename file

* Update strings/cc_validator.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update strings/cc_validator.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update strings/cc_validator.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Review: Fix redundant checks

* Review: Refactor

* Fix: Update test cases

* Refactor

* Update credit_card_validator.py

Co-authored-by: Christian Clauss <cclauss@me.com>
---
 strings/credit_card_validator.py | 105 +++++++++++++++++++++++++++++++
 1 file changed, 105 insertions(+)
 create mode 100644 strings/credit_card_validator.py

diff --git a/strings/credit_card_validator.py b/strings/credit_card_validator.py
new file mode 100644
index 000000000..3b5a1aae6
--- /dev/null
+++ b/strings/credit_card_validator.py
@@ -0,0 +1,105 @@
+"""
+Functions for testing the validity of credit card numbers.
+
+https://en.wikipedia.org/wiki/Luhn_algorithm
+"""
+
+
+def validate_initial_digits(credit_card_number: str) -> bool:
+    """
+    Function to validate initial digits of a given credit card number.
+    >>> valid = "4111111111111111 41111111111111 34 35 37 412345 523456 634567"
+    >>> all(validate_initial_digits(cc) for cc in valid.split())
+    True
+    >>> invalid = "32323 36111111111111"
+    >>> all(validate_initial_digits(cc) is False for cc in invalid.split())
+    True
+    """
+    if len(credit_card_number) < 2:
+        return False
+    return credit_card_number[0] in "456" or credit_card_number[1] in "457"
+
+
+def luhn_validation(credit_card_number: str) -> bool:
+    """
+    Function to luhn algorithm validation for a given credit card number.
+    >>> luhn_validation('4111111111111111')
+    True
+    >>> luhn_validation('36111111111111')
+    True
+    >>> luhn_validation('41111111111111')
+    False
+    """
+    cc_number = credit_card_number
+    total = 0
+    half_len = len(cc_number) - 2
+    for i in range(half_len, -1, -2):
+        #  double the value of every second digit
+        digit = int(cc_number[i])
+        digit *= 2
+        # If doubling of a number results in a two digit number
+        # i.e greater than 9(e.g., 6 × 2 = 12),
+        # then add the digits of the product (e.g., 12: 1 + 2 = 3, 15: 1 + 5 = 6),
+        # to get a single digit number.
+        if digit > 9:
+            digit %= 10
+            digit += 1
+        cc_number = cc_number[:i] + str(digit) + cc_number[i + 1 :]
+        total += digit
+
+    # Sum up the remaining digits
+    for i in range(len(cc_number) - 1, -1, -2):
+        total += int(cc_number[i])
+
+    return total % 10 == 0
+
+
+def validate_credit_card_number(credit_card_number: str) -> bool:
+    """
+    Function to validate the given credit card number.
+    >>> validate_credit_card_number('4111111111111111')
+    4111111111111111 is a valid credit card number.
+    True
+    >>> validate_credit_card_number('helloworld$')
+    helloworld$ is an invalid credit card number because it has nonnumerical characters.
+    False
+    >>> validate_credit_card_number('32323')
+    32323 is an invalid credit card number because of its length.
+    False
+    >>> validate_credit_card_number('32323323233232332323')
+    32323323233232332323 is an invalid credit card number because of its length.
+    False
+    >>> validate_credit_card_number('36111111111111')
+    36111111111111 is an invalid credit card number because of its first two digits.
+    False
+    >>> validate_credit_card_number('41111111111111')
+    41111111111111 is an invalid credit card number because it fails the Lhun check.
+    False
+    """
+    error_message = f"{credit_card_number} is an invalid credit card number because"
+    if not credit_card_number.isdigit():
+        print(f"{error_message} it has nonnumerical characters.")
+        return False
+
+    if not 13 <= len(credit_card_number) <= 16:
+        print(f"{error_message} of its length.")
+        return False
+
+    if not validate_initial_digits(credit_card_number):
+        print(f"{error_message} of its first two digits.")
+        return False
+
+    if not luhn_validation(credit_card_number):
+        print(f"{error_message} it fails the Lhun check.")
+        return False
+
+    print(f"{credit_card_number} is a valid credit card number.")
+    return True
+
+
+if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()
+    validate_credit_card_number("4111111111111111")
+    validate_credit_card_number("32323")
