Skip to content

Instantly share code, notes, and snippets.

@meric
Created March 1, 2017 12:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save meric/748dcf61563384d3ead44799d1eb97a2 to your computer and use it in GitHub Desktop.
Save meric/748dcf61563384d3ead44799d1eb97a2 to your computer and use it in GitHub Desktop.
credit card validation
"""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