def checksum(card_without_check):
card_without_check = card_without_check[-1::-1]
def numbers(string):
return [int(x) for x in string]
print(card_without_check)
odd_numbers = numbers(card_without_check[0::2])
even_numbers = numbers(card_without_check[1::2])
odd_numbers = [x * 2 for x in odd_numbers]
odd_numbers = [x - 9 if x > 9 else x for x in odd_numbers]
print(even_numbers)
print(odd_numbers)
return sum(odd_numbers) + sum(even_numbers)
def check(checksum, check):
return checksum % 10 == int(check)
card_number = input("Enter card number:\n")
print(checksum(card_number[:-1]))
print("Card is", check(checksum(card_number[:-1]), card_number[-1]))
This algorithm appears to work on examples like "4556737586899855", but not on examples like "30569309025904". I've followed the process and can't find flaws in how it's handling the numbers, I'm probably just missing some piece of the puzzle here.
I'm following the outline here and have used examples here.
I used this solution for a codeeval problem based on Luhn's formula:
def checksum(n):
nums = reversed(list(map(int, n)))
doubled = (ele * 2 if ind % 2 else ele for ind, ele in enumerate(nums))
return not sum(sum(map(int, str(ele))) for ele in doubled) % 10
The steps are listed in the problem description:
From the rightmost digit, which is the check digit, moving left, double the value of every second digit; if the product of this doubling operation is greater than 9 (for example, 7×2=14), then sum the digits of the products (for example, 12:1+2=3, 14:1+4=5).
Take the sum of all the digits.
If the total modulo 10 is equal to 0 (if the total ends in zero) then, according to the Luhn formula, the number is valid; otherwise, it is not valid.
This is the code for MOD10 (the Luhn algorithm) that I wrote from the description on (the Norwegian) Wikipedia:
def weights(n, base):
"""Repeating series of numbers from base.
For kontroll_10 it returns the series: 2,1,2,1,2,1...
which is equivalent to multiplying every other digit
by two (code is originally from a broader checsum module).
"""
for i in range(n):
yield base[i % len(base)]
def luhn_algorithm(s):
"""Also known as the MOD10 algorithm.
"""
digits = map(int, list(s))[::-1] # reversed
_weights = weights(len(digits), [2, 1])
products = ''.join(str(s * v) for (s, v) in zip(digits, _weights))
sum_of_digits = sum(int(c) for c in products)
check_digit = sum_of_digits % 10
if check_digit == 0:
checksum = check_digit
else:
checksum = 10 - check_digit
return str(checksum)
Looks like you might have skipped the special handling when the entallsiffer
is zero, and you're also not subtracting from 10, meaning you'll only get the correct answer when the control/checksum digit is 5
.
You nearly got it right, except for the initial cutting off of the last digit. You cannot reverse and slice one off in one slice notation but in two:
card_without_check = card[::-1][1:]
And then in the call to the checksum() routine the logic went overboard. Try this:
def checksum(card):
def numbers(string):
return [int(x) for x in string]
card_without_check = card[::-1][1:]
print(card_without_check)
odd_numbers = numbers(card_without_check[0::2])
even_numbers = numbers(card_without_check[1::2])
odd_numbers = [x * 2 for x in odd_numbers]
odd_numbers = [x - 9 if x > 9 else x for x in odd_numbers]
print odd_numbers
print even_numbers
return (sum(odd_numbers) + sum(even_numbers)) % 10
def check(card):
return checksum(card) == int(card[-1])
def main():
card = str(input("Enter card number:\n"))
print "Valid? ", check(card)
An input of 4556737586899855 yields:
Valid? 589986857376554
[1, 9, 7, 7, 5, 5, 1, 8]
[8, 9, 6, 5, 3, 6, 5]
True
Here is a credit card utils library I wrote that covers luhn checksum as well as other credit card checks.
import re
import logging
import datetime
VISA_CC = 'Visa' # Visa
MASTERCARD_CC = 'MasterCard' # MasterCard
AMEX_CC = 'American Express' # American Express
JCB_CC = 'JCB'
DISCOVER_CC = 'DISCOVER'
DINERS_CC = 'DINERS'
MAESTRO_CC = 'MAESTRO'
LASER_CC = 'LASER'
OTHER_CC = '' # UNKNOWN
# Set which cards you accept
ACCEPTED_CARDS = [VISA_CC, MASTERCARD_CC, AMEX_CC]
def is_american_express(cc_number):
"""Checks if the card is an american express. If us billing address country code, & is_amex, use vpos
https://en.wikipedia.org/wiki/Bank_card_number#cite_note-GenCardFeatures-3
:param cc_number: unicode card number
"""
return bool(re.match(r'^3[47][0-9]{13}$', cc_number))
def is_visa(cc_number):
"""Checks if the card is a visa, begins with 4 and 12 or 15 additional digits.
:param cc_number: unicode card number
"""
# Standard Visa is 13 or 16, debit can be 19
if bool(re.match(r'^4', cc_number)) and len(cc_number) in [13, 16, 19]:
return True
return False
def is_mastercard(cc_number):
"""Checks if the card is a mastercard. Begins with 51-55 or 2221-2720 and 16 in length.
:param cc_number: unicode card number
"""
if len(cc_number) == 16 and cc_number.isdigit(): # Check digit, before cast to int
return bool(re.match(r'^5[1-5]', cc_number)) or int(cc_number[:4]) in range(2221, 2721)
return False
def is_discover(cc_number):
"""Checks if the card is discover, re would be too hard to maintain. Not a supported card.
:param cc_number: unicode card number
"""
if len(cc_number) == 16:
try:
# return bool(cc_number[:4] == '6011' or cc_number[:2] == '65' or cc_number[:6] in range(622126, 622926))
return bool(cc_number[:4] == '6011' or cc_number[:2] == '65' or 622126 <= int(cc_number[:6]) <= 622925)
except ValueError:
return False
return False
def is_jcb(cc_number):
"""Checks if the card is a jcb. Not a supported card.
:param cc_number: unicode card number
"""
# return bool(re.match(r'^(?:2131|1800|35\d{3})\d{11}$', cc_number)) # wikipedia
return bool(re.match(r'^35(2[89]|[3-8][0-9])[0-9]{12}$', cc_number)) # PawelDecowski
def is_diners_club(cc_number):
"""Checks if the card is a diners club. Not a supported card.
:param cc_number: unicode card number
"""
return bool(re.match(r'^3(?:0[0-6]|[68][0-9])[0-9]{11}$', cc_number)) # 0-5 = carte blance, 6 = international
def is_laser(cc_number):
"""Checks if the card is laser. Not a supported card.
:param cc_number: unicode card number
"""
return bool(re.match(r'^(6304|670[69]|6771)', cc_number))
def is_maestro(cc_number):
"""Checks if the card is maestro. Not a supported card.
:param cc_number: unicode card number
"""
possible_lengths = [12, 13, 14, 15, 16, 17, 18, 19]
return bool(re.match(r'^(50|5[6-9]|6[0-9])', cc_number)) and len(cc_number) in possible_lengths
# Child cards
def is_visa_electron(cc_number):
"""Child of visa. Checks if the card is a visa electron. Not a supported card.
:param cc_number: unicode card number
"""
return bool(re.match(r'^(4026|417500|4508|4844|491(3|7))', cc_number)) and len(cc_number) == 16
def is_total_rewards_visa(cc_number):
"""Child of visa. Checks if the card is a Total Rewards Visa. Not a supported card.
:param cc_number: unicode card number
"""
return bool(re.match(r'^41277777[0-9]{8}$', cc_number))
def is_diners_club_carte_blanche(cc_number):
"""Child card of diners. Checks if the card is a diners club carte blance. Not a supported card.
:param cc_number: unicode card number
"""
return bool(re.match(r'^30[0-5][0-9]{11}$', cc_number)) # github PawelDecowski, jquery-creditcardvalidator
def is_diners_club_carte_international(cc_number):
"""Child card of diners. Checks if the card is a diners club international. Not a supported card.
:param cc_number: unicode card number
"""
return bool(re.match(r'^36[0-9]{12}$', cc_number)) # jquery-creditcardvalidator
def get_card_type_by_number(cc_number):
"""Return card type constant by credit card number
:param cc_number: unicode card number
"""
if is_visa(cc_number):
return VISA_CC
if is_mastercard(cc_number):
return MASTERCARD_CC
if is_american_express(cc_number):
return AMEX_CC
if is_discover(cc_number):
return DISCOVER_CC
if is_jcb(cc_number):
return JCB_CC
if is_diners_club(cc_number):
return DINERS_CC
if is_laser(cc_number): # Check before maestro, as its inner range
return LASER_CC
if is_maestro(cc_number):
return MAESTRO_CC
return OTHER_CC
def get_card_type_by_name(cc_type):
"""Return card type constant by string
:param cc_type: dirty string card type or name
"""
cc_type_str = cc_type.replace(' ', '').replace('-', '').lower()
# Visa
if 'visa' in cc_type_str:
return VISA_CC
# MasterCard
if 'mc' in cc_type_str or 'mastercard' in cc_type_str:
return MASTERCARD_CC
# American Express
if cc_type_str in ('americanexpress', 'amex'):
return AMEX_CC
# Discover
if 'discover' in cc_type_str:
return DISCOVER_CC
# JCB
if 'jcb' in cc_type_str:
return JCB_CC
# Diners
if 'diners' in cc_type_str:
return DINERS_CC
# Maestro
if 'maestro' in cc_type_str:
return MAESTRO_CC
# Laser
if 'laser' in cc_type_str:
return LASER_CC
# Other Unsupported Cards Dankort, Union, Cartebleue, Airplus etc..
return OTHER_CC
def credit_card_luhn_checksum(card_number):
"""Credit card luhn checksum
:param card_number: unicode card number
"""
def digits_of(cc):
return [int(_digit) for _digit in str(cc)]
digits = digits_of(card_number)
odd_digits = digits[-1::-2]
even_digits = digits[-2::-2]
checksum = sum(odd_digits)
for digit in even_digits:
checksum += sum(digits_of(digit * 2))
return checksum % 10
def is_valid_cvv(card_type, cvv):
"""Validates the cvv based on card type
:param cvv: card cvv security code
:param card_type: string card type
"""
if card_type == AMEX_CC:
return len(str(cvv)) == 4
else:
return len(str(cvv)) == 3
def is_cc_luhn_valid(card_number):
"""Returns true if the luhn code is 0
:param card_number: unicode string for card_number, cannot be other type.
"""
is_valid_cc = card_number.isdecimal() and credit_card_luhn_checksum(card_number) == 0
if not is_valid_cc:
logging.error("Invalid Credit Card Number, fails luhn: {}".format(card_number))
return is_valid_cc
def is_valid_cc_expiry(expiry_month, expiry_year):
"""Returns true if the card expiry is not good.
Edge case: It's end of month, the expiry is on the current month and user is in a different timezone.
:param expiry_year: unicode two digit year
:param expiry_month: unicode two digit month
"""
try:
today = datetime.date.today()
cur_month, cur_year = today.month, int(str(today.year)[2:])
expiry_month, expiry_year = int(expiry_month), int(expiry_year)
is_invalid_year = expiry_year < cur_year
is_invalid_month = False
if not is_invalid_year:
is_invalid_month = ((expiry_month < cur_month and cur_year == expiry_year) or
expiry_month not in range(1, 13))
if is_invalid_year or is_invalid_month:
logging.info("Invalid credit card expiry {}/{}.".format(expiry_month, expiry_year))
return False
except ValueError:
logging.error("Could not calculate valid expiry for month year {}/{}.".format(expiry_month, expiry_year))
return False
return True
def is_supported_credit_card(card_type):
"""Checks if card type is in accepted cards
:param card_type: string card type
"""
if card_type in ACCEPTED_CARDS:
return True
logging.error("Card type not supported, {}.".format(card_type))
return False # (OTHER_CC, DISCOVER_CC)
def cc_card_to_mask(cc_number, show_first=6, show_last=4):
"""Returns masked credit card number
:param show_last: beginning of card, chars not to mask
:param show_first: end of card, chars not to mask
:param cc_number: unicode card number
"""
cc_number = str(cc_number)
if cc_number:
return "{}{}{}".format(cc_number[:show_first],
"X" * (len(cc_number) - show_first - show_last),
cc_number[show_last * -1:])
else:
return ""
def string_to_full_mask(cc_field):
"""Returns credit card field or any string converted to a full mask.
I.e. cvv, expiry month, expiry year, password.
:param cc_field: a generic credit card field, other than cc card no
"""
try:
cc_field = cc_field.strip()
return "X" * len(cc_field)
except (TypeError, AttributeError):
return ""
Please feel free to comment if there is anything missing that is useful. Cheers.