[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky]
[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Validate credit card numbers in Python

[Validating test credit card numbers]

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.

  1. 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.
  2. Add up the doubled digits and the non-doubled digits.
  3. 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.

© 2024 Rocky Mountain Computer Consulting, Inc. All rights reserved.