Created
March 1, 2017 12:41
-
-
Save meric/748dcf61563384d3ead44799d1eb97a2 to your computer and use it in GitHub Desktop.
credit card validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Implement credit card validation in response to | |
https://gist.github.com/cam-stitt/5ce63c0c2090a46b2263 | |
Run using Python 3.4+. | |
Sample Usage: | |
cat << EOF | python3 challenge.py | |
4111111111111111 | |
4111111111111 | |
4012888888881881 | |
378282246310005 | |
6011111111111117 | |
5105105105105100 | |
5105 1051 0510 5106 | |
9111111111111111 | |
EOF | |
Visa: 4111111111111111 (valid) | |
Visa: 4111111111111 (invalid) | |
Visa: 4012888888881881 (valid) | |
AMEX: 378282246310005 (valid) | |
Discover: 6011111111111117 (valid) | |
MasterCard: 5105105105105100 (valid) | |
MasterCard: 5105105105105106 (invalid) | |
Unknown: 9111111111111111 (invalid) | |
""" | |
import re | |
import sys | |
""""Define card types in the `CARD_TYPES` dictionary. Each string card type is | |
the key and the value should be a a dictionary with a `matching` key and a | |
`validator` key. The `validator` key should be a function that takes a string | |
card number and return False or None if the card number is invalid for that | |
type, otherwise return a value that evaluates to True. The `matching` key | |
should be a function that takes a card_number and return True if it matches | |
that card type. | |
Use functions instead of regex directly in case it gets tricky to implement new | |
card type validations using regex. | |
The downside of regex and lambdas is a regex is quite opaque and makes it | |
difficult to analyse it. E.g. difficult to check the lambda is checking for | |
length == 15. | |
""" | |
CARD_TYPES = { | |
"AMEX": { | |
"matching": lambda card_number: re.match(r"^(34|37)", card_number), | |
"validator": lambda card_number: len(card_number) == 15 | |
}, | |
"Discover": { | |
"matching": lambda card_number: re.match(r"^6011", card_number), | |
"validator": lambda card_number: len(card_number) == 16 | |
}, | |
"MasterCard": { | |
"matching": lambda card_number: re.match(r"^5[1-5]", card_number), | |
"validator": lambda card_number: len(card_number) == 16 | |
}, | |
"Visa": { | |
"matching": lambda card_number: re.match(r"^4", card_number), | |
"validator": lambda card_number: | |
len(card_number) == 13 or len(card_number) == 16 | |
} | |
} | |
def match_card_type(card_number): | |
"""Return matching `card_type for `card_number`. | |
It's possible to implement some sort of reverse hash look up for beginning | |
numbers for each card_type but for low number of card types, I think a | |
simple O(N) iteration is sufficient and easy to understand.""" | |
for card_type, config in CARD_TYPES.items(): | |
if config['matching'](card_number): | |
return card_type | |
def validate_card_number_for_card_type(card_type, card_number): | |
"""Return True if `card_number` is valid for card_type""" | |
return CARD_TYPES[card_type]["validator"](card_number) | |
def validate_luhn(card_number): | |
"""Return True if `card_number` is valid according to Luhn algorithm. | |
See https://gist.github.com/cam-stitt/5ce63c0c2090a46b2263. | |
""" | |
reversed_numbers = [int(n) for n in reversed(card_number)] | |
numbers = [] | |
for index, number in zip(range(0, len(reversed_numbers)), | |
reversed_numbers): | |
if index % 2 == 1: | |
numbers.append(str(number * 2)) | |
else: | |
numbers.append(str(number)) | |
if sum(int(n) for n in "".join(numbers)) % 10 == 0: | |
return True | |
def validate_card(card_number): | |
"""Return card_type and whether the card_number is valid and is valid for | |
that card_type. | |
""" | |
card_type = match_card_type(card_number) | |
is_valid = (card_type and | |
validate_card_number_for_card_type(card_type, card_number) and | |
validate_luhn(card_number)) | |
return card_type, is_valid | |
non_digit = re.compile(r'[^\d]+') | |
for line in sys.stdin: | |
card_number = non_digit.sub('', line) | |
card_type, is_valid = validate_card(card_number) | |
# Ideally the adjustment should take into account the length of longest | |
# (card_type + its maximum expected length)... | |
# It is too late into the night to re-think whether to use anonymous | |
# functions to valid card length or to use a list of valid lengths. | |
# Or better yet - ask the sponsor of the project. | |
# For now, we hardcode 29. | |
print(("%s: %s" % (card_type or "Unknown", card_number)).ljust(29) + | |
"(%s)" % ("valid" if is_valid else "invalid")) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment