Title: Validate credit card numbers in Python
Have you ever wondered how a website knows when you mistype your credit card number? The answer is checksums. A checksum is a digit or digits added to a value to verify that the number was typed correctly.
For example, a checksum might add up all of the digits in a number and then append the ones digit of the result. To calculate the checksum for the number 1729346, you add up 1 + 7 + 2 + 9 + 3 + 4 + 6 = 32. The last digit of the sum is 2 so the checksum is 2 and the encoded number would be 17293462.
Credit Card Checksums
Normally checksum rules are more complicated. You can see a description of the credit card checksum rules, which is called the Luhn Mod-10 Method, at this Cybersource article but here's the way it works.
- Starting at the rightmost digit before the check digit, move left doubling every other digit. If the result is greater than 9, add the digits.
- Add up the doubled digits and the non-doubled digits.
- Subtract the total from the smallest multiple of 10 that's greater than or equal to the sum.
Here's an example.
371449635398431
Double these: ^ ^ ^ ^ ^ ^ ^
Doubled: 5 8 9 6 6 7 6
Non-doubled: 3 1 4 6 5 9 4
Add: 5 + 8 + 9 + 6 + 6 + 7 + 6 + 3 + 1 + 4 + 6 + 5 + 9 + 4 = 79
Subtract: 80 - 79 = 1
That means 1 is the check digit so 371449635398431 is a valid credit card number.
Python Code
The following checksum function calculates a credit card number checksum.
def checksum(number):
'''Calculate a credit card checksum.'''
clean = number.replace(' ', '')
reversed = clean[:-1][::-1]
sum = 0
double_it = True
for ch in reversed:
value = int(ch)
if double_it:
value *= 2
if value >= 10:
value = (value // 10) + (value % 10)
sum += value
double_it = not double_it
sum = 10 - sum % 10
if sum == 10:
sum = 0
return sum
This function simply follows the steps described above.
The following card_is_valid function uses checksum to see if a credit card number seems valid.
def card_is_valid(number):
'''Return True if the credit card number is valid.'''
sum = checksum(number)
return int(number[-1]) == sum
This function calls checksum to get the number's checksum. It then returns True if the checksum matches the number's last digit.
Card Companies
Companies that issue credit cards are given a set of BINs (Bank Identification Numbers) that they are allowed to use. For example, any number beginning with 324000 is an American Express card number. You can find information about BINs at the article What are Bank Identification Numbers (BINs) & How Do They Work? You can find a database of BINs at Card Schemes at BinTable database if you're willing to follow some links and dismiss a bunch of annoying ads.
This example uses the following class to store BIN information.
class bin:
def __init__(self, brand, min_bin, max_bin):
self.brand = brand
self.min_bin = min_bin
self.max_bin = max_bin
def matches(self, number):
'''Return True if this number matches the bin.'''
bin_length = len(self.min_bin)
return self.min_bin <= number[:bin_length] <= self.max_bin
The bin class holds minimum and maximum number prefixes so it can represent a range of BINs. For example, numbers that start with anything between 622126 and 622925 are Discover Cards.
The class's constructor simply saves the card name and BIN range. The matches method return True if a given number lies between the minimum and maximum BIN values. The calculation is a little odd because the numbers are treated as strings and not all credit card numbers have the same length.
The following find_brand function builds a small BIN database and then uses it to return a card's brand.
def find_brand(number):
# Example bins from https://chargebacks911.com/bank-identification-numbers/
# There's a more complete list at https://bintable.com/card-schemes, although
# it's hard to use and the site has many spam advertizing/download links.
bins = [
bin('Visa', '4', '4'),
bin('AMEX', '34', '37'),
bin('Diner's Club', '36', '36'),
bin('MasterCard', '51', '55'),
bin('Discover Card', '6011', '6011'),
bin('Discover Card', '622126', '622925'),
bin('Discover Card', '644', '649'),
bin('Discover Card', '65', '65'),
]
for a_bin in bins:
if a_bin.matches(number):
return a_bin.brand
return None
Main Program
The main program uses the following code to test some credit card numbers.
numbers = [
'378282246310005',
'371449635398431',
'378734493671000',
'5610591081018250',
'30569309025904',
'38520000023237',
'6011111111111117',
'6011000990139424',
'3530111333300000',
'3566002020360505',
'5555555555554444',
'5105105105105100',
'4111111111111111',
'4012888888881881',
'4222222222222',
'76009244561', # This one seems to be wrong.
'5019717010103742',
'6331101999990016',
]
for number in numbers:
sum = checksum(number)
is_valid = " valid " if card_is_valid(number) else "INVALID"
brand = find_brand(number)
print(f'{number:<16} ({sum}) is {is_valid}, {brand}')
This code first defines some numbers take from this list of test numbers. It then loops through the numbers and displays them, whether they are valid or not, and their brand if known.
Download the example to see additional details.
|