Working with data

Day 9 Project: Credit Card Validator

Welcome to the day 9 project for the 30 Days of Python series! For this project, we're going to be writing a simple credit card validator. When the program is complete, we're going to be able to determine whether a given card number is valid or not.

It's actually a lot easier than you might think, and I'm going to explain the algorithm we're going to use in detail to help you out. You'll find this explanation below, along with the project brief. After that, we'll present a model solution as part of a code along walkthrough.

As always, try your best to do this on your own, but if you get really stuck, follow along with the solution until you feel like you can finish it yourself. Remember, you can always look back at your notes, or back the previous days' posts if you forget how something works!

A quick explanation of the algorithm

The algorithm we're going to use to verify card numbers is called the Luhn algorithm, or Luhn formula. This algorithm is actually used in real-life applications to test credit or debit card numbers as well as SIM card serial numbers.

The purpose of the algorithm is to identify potentially mistyped numbers, because it can determine whether or not it's possible for a given number to be the number for a valid card.

The way we're going to use the algorithm is as follows:

  1. Remove the rightmost digit from the card number. This number is called the checking digit, and it will be excluded from most of our calculations.
  2. Reverse the order of the remaining digits.
  3. For this sequence of reversed digits, take the digits at each of the even indices (0, 2, 4, 6, etc.) and double them. If any of the results are greater than 9, subtract 9 from those numbers.
  4. Add together all of the results and add the checking digit.
  5. If the result is divisible by 10, the number is a valid card number. If it's not, the card number is not valid.

Let's look at this step by step for a valid number so we can see this in action. The number we're going to use is 5893804115457289, which is a valid Maestro card number, but not one which is in use.

Number Operation
5893804115457289 Starting number
589380411545728X Remove the last digit
827545114083985X Reverse the remaining digits
16214585218016318810X Double digits at even indices
725585218073981X Subtract 9 if over 9

Now we sum these digits and add the checking digit:

7 + 2 + 5 + 5 + 8 + 5 + 2 + 1 + 8 + 0 + 7 + 3 + 9 + 8 + 1 + 9

If we perform this series if additions, we get 80. 80 is divisible by 10, so the card number is valid.

The brief

The program you write for this project should do the following:

1) It should be able to accept a card number from the user. For this project, you can assume that the number will be entered as a single string of characters (i.e. there won't be any spaces between the numbers). However, you should be able to accept a card number with spaces at the start or end of the string.

If you want to challenge yourself, you should try to be more versatile with regards to the format that you accept card numbers in.

You may want to turn the user's input into a list of numbers, as that will make it easier to work with.

2) The program should validate that card number using the Luhn algorithm described above. You should implement this algorithm yourself.

After removing the checking digit and reversing the card number, you'll need a for loop to go over the credit card numbers. As you go through each digit, you must find a way to determine whether a digit is in an odd or an even position. Remember you can check the model solution if you get stuck!

3) Once the validation is complete, the program should inform the user whether or not the card number is valid by printing a string to the console.

When you need to get to the step where you reverse the numbers you could use the reversed function, which will accept any sequence type:

language = "Python"
numbers = [1, 2, 3, 4, 5]
letters = ("a", "b", "c", "d", "e")

language = reversed(language)    # 'nohtyP'
numbers = reversed(numbers)      # [5, 4, 3, 2, 1]
letters = reversed(letters)      # ('e', 'd', 'c', 'b', 'a')

reversed will give us back a lazy type (like range), so we can't directly print it; however, it is iterable. We can therefore use the result in a for loop, for example.

If our numbers are in a list, we can use the reverse method. This directly modifies the original list:

numbers = [1, 2, 3, 4, 5]
numbers.reverse()

print(numbers)  # [5, 4, 3, 2, 1]

You can use whichever technique you prefer.

When testing your solution, you can use your own card number, or you can find valid card numbers online that are used for testing payment methods. For example, Stripe has a range of test card numbers you can use.

With that, you're ready to tackle the exercise. Good luck!

Our solution

Below you'll find a written walkthrough for our solution, but we have a link to a video version you'd prefer to watch a code-along video instead.

As always, we're going to break write out solution in small chunks, and I suggest you do the same, testing regularly to make sure you haven't made an error early on. If you try to write everything in one go, it can very difficult to track down these earlier errors, because they get buried.

Our first step is just going to be accepting a card number from the user:

card_number = input("Please enter a card number: ")

Since we need to be able to accept card numbers where spaces have been added at the start or end, we need to do a small amount of processing on the string we get back from the user. In this case strip is all we need, which will take care of any extra whitespace.

card_number = input("Please enter a card number: ").strip()

For this solution, I'm going to be creating a list from this string, and I'm doing this for a couple of reasons. First, the reverse method is a very clean way of reversing the card number, and second, the pop method allows me to very neatly remove and store the check digit.

Let's start by converting the card_number:

card_number = list(input("Please enter a card number: ").strip())

No need to use split here, because I just want to split every character in the string into different list items. This is the default behaviour if we pass a string to the list function.

Now that we have our list, let's extract our check digit and reverse the remaining numbers:

card_number = list(input("Please enter a card number: ").strip())

# Remove the last digit from the card number
check_digit = card_number.pop()

# Reverse the order of the remaining numbers
card_number.reverse()

Now that we have our reversed numbers, we need to take the digit at every even index and double it. If the number ends up being over 9, we need to subtract 9 from the result.

I think the easiest way to tackle this is to use a counter for the index. We can then use a for loop to increment this counter while iterating over the card_number list.

Inside the for loop, we can then check whether or not the index is divisible by 2. If it is, we know we have an even index. If you need a refresher on how to check if a number is divisible by another, we spoke about this in yesterday's Fizz Buzz project.

card_number = list(input("Please enter a card number: ").strip())

# Remove the last digit from the card number
check_digit = card_number.pop()

# Reverse the order of the remaining numbers
card_number.reverse()

index = 0

for digit in card_number:
    if index % 2 == 0:
        print("Even index")
    else:
        print("Odd index")

    # Increment the index counter for each iteration
    index = index + 1

We can also make use of the enumerate function that we learnt about today so that we don't have to keep track of this counter ourselves:

card_number = list(input("Please enter a card number: ").strip())

# Remove the last digit from the card number
check_digit = card_number.pop()

# Reverse the order of the remaining numbers
card_number.reverse()

for index, digit in enumerate(card_number):
    if index % 2 == 0:
        print("Even index")
    else:
        print("Odd index")

Now that we have this part of the loop set up, we can replace these print calls with something useful.

The first thing we need to add is a place to store our modified digits. For this, we're going to create an empty list called processed_digits. We're then going to populate this list by appending items from inside the for loop.

card_number = list(input("Please enter a card number: ").strip())

# Remove the last digit from the card number
check_digit = card_number.pop()

# Reverse the order of the remaining numbers
card_number.reverse()

processed_digits = []

for index, digit in enumerate(card_number):
    if index % 2 == 0:
        print("Even index")
    else:
        print("Odd index")

For odd indices, our task is quite simple. We just need to convert the digit to an integer (remember we currently have a list of strings), and then we need to call append to add the integer to processed_digits.

card_number = list(input("Please enter a card number: ").strip())

# Remove the last digit from the card number
check_digit = card_number.pop()

# Reverse the order of the remaining numbers
card_number.reverse()

processed_digits = []

for index, digit in enumerate(card_number):
    if index % 2 == 0:
        print("Even index")
    else:
        processed_digits.append(int(digit))

For even indices, we need to perform a few different steps. First, we need to convert the digit to an integer and double the value.

We then need to check if the result of this operation is greater than 9. If it is, we need to subtract 9 from the result. We then need to add this number to the processed_digits.

card_number = list(input("Please enter a card number: ").strip())

# Remove the last digit from the card number
check_digit = card_number.pop()

# Reverse the order of the remaining numbers
card_number.reverse()

processed_digits = []

for index, digit in enumerate(card_number):
    if index % 2 == 0:
        doubled_digit = int(digit) * 2

        # Subtract 9 from any results that are greater than 9
        if doubled_digit > 9:
            doubled_digit = doubled_digit - 9

        processed_digits.append(doubled_digit)
    else:
        processed_digits.append(int(digit))

Awesome! Now we have our list of processed digits, so all that's left is to add them together and check the result.

To add the numbers digits together, we're going to use a for loop again. This time we're going to create a variable called total, and we're going to set an initial value to the check_digit.

For each iteration of the loop, we're then going to add another digit to this total, and we'll end up with the sum of all of our processed digits, as well as the check digit.

card_number = list(input("Please enter a card number: ").strip())

# Remove the last digit from the card number
check_digit = card_number.pop()

# Reverse the order of the remaining numbers
card_number.reverse()

processed_digits = []

for index, digit in enumerate(card_number):
    if index % 2 == 0:
        doubled_digit = int(digit) * 2

        # Subtract 9 from any results that are greater than 9       
        if doubled_digit > 9:
            doubled_digit = doubled_digit - 9

        processed_digits.append(doubled_digit)
    else:
        processed_digits.append(int(digit))

total = int(check_digit)

for digit in processed_digits:
    total = total + digit

If we try our initial card number (5893804115457289), we should get a total of 80.

While this works, there's a much simpler way of adding the numbers in an iterable. We can just pass the iterable to sum:

card_number = list(input("Please enter a card number: ").strip())

# Remove the last digit from the card number
check_digit = card_number.pop()

# Reverse the order of the remaining numbers
card_number.reverse()

processed_digits = []

for index, digit in enumerate(card_number):
    if index % 2 == 0:
        doubled_digit = int(digit) * 2

        # Subtract 9 from any results that are greater than 9       
        if doubled_digit > 9:
            doubled_digit = doubled_digit - 9

        processed_digits.append(doubled_digit)
    else:
        processed_digits.append(int(digit))

total = int(check_digit) + sum(processed_digits)

Not only is this much shorter, it's also quite a lot easier to read and understand.

Now that we have the sum of the card digits, we just need to test whether or not the total is divisible by 10. We can use the same method as we did for testing even indices.

card_number = list(input("Please enter a card number: ").strip())

# Remove the last digit from the card number
check_digit = card_number.pop()

# Reverse the order of the remaining numbers
card_number.reverse()

processed_digits = []

for index, digit in enumerate(card_number):
    if index % 2 == 0:
        doubled_digit = int(digit) * 2

        # Subtract 9 from any results that are greater than 9       
        if doubled_digit > 9:
            doubled_digit = doubled_digit - 9

        processed_digits.append(doubled_digit)
    else:
        processed_digits.append(int(digit))

total = int(check_digit) + sum(processed_digits)

# Verify that the sum of the digits is divisible by 10
if total % 10 == 0:
    print("Valid!")
else:
    print("Invalid!")

With that, we're done! We have a fully functioning validator for card numbers.

Additional Resources

If you're interested in learning more about the sum function we used above, you can find information in the official documentation.