Login

Newforms Validation of Credit Card Numbers

Author:
humphreymurray
Posted:
May 19, 2008
Language:
Python
Version:
.96
Score:
1 (after 1 ratings)

Some functions and newforms fields for validating credit card numbers, and their expiry dates.

In my project, I have all of the credit card functions in a file called creditcards.py

Just as an overview: To validate a credit card number there are a few steps: 1. Make sure the number only contains digits and spaces. ValidateCharacters() 2. Remove spaces so that only numbers are left. StripToNumbers() 3. Check that the number validates using the Luhn Checksum ValidateLuhnChecksum() 4. Check to see whether the number is valid for the type of card that is selected. This is annoying because you will need to look at another cleaned field before you can check this.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"""
    Provides functions & Fields for validating credit card numbers
    Thanks to David Shaw for the Luhn Checksum code 
    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/172845)
"""


import re        
from django import newforms as forms
import datetime
        
def ValidateLuhnChecksum(number_as_string):
    """ checks to make sure that the card passes a luhn mod-10 checksum """

    sum = 0
    num_digits = len(number_as_string)
    oddeven = num_digits & 1

    for i in range(0, num_digits):
        digit = int(number_as_string[i])

        if not (( i & 1 ) ^ oddeven ):
            digit = digit * 2
        if digit > 9:
            digit = digit - 9

        sum = sum + digit
        
    return ( (sum % 10) == 0 )

# Regex for valid card numbers
CC_PATTERNS = {
    'mastercard':   '^5[12345]([0-9]{14})$',
    'visa':         '^4([0-9]{12,15})$',
}

def ValidateCharacters(number):
    """ Checks to make sure string only contains valid characters """
    return re.compile('^[0-9 ]*$').match(number) != None
        
def StripToNumbers(number):
    """ remove spaces from the number """
    if ValidateCharacters(number):
        result = ''
        rx = re.compile('^[0-9]$')
        for d in number:
            if rx.match(d):
                result += d
        return result
    else:
        raise Exception('Number has invalid digits')

def ValidateDigits(type, number):
    """ Checks to make sure that the Digits match the CC pattern """
    regex = CC_PATTERNS.get(type.lower(), False)
    if regex:
        return re.compile(regex).match(number) != None
    else:
        return False

def ValidateCreditCard(clean, number):
    """ Check that a credit card number matches the type and validates the Luhn Checksum """
    clean = clean.strip().lower()
    if ValidateCharacters(number):
        number = StripToNumbers(number)
        if CC_PATTERNS.has_key(clean):
            return ValidateDigits(clean, number)
            return ValidateLuhnChecksum(number)
    return False

class CreditCardNumberField(forms.CharField):
    """ A newforms field for a creditcard number """
    def clean(self, value):
        
        value = forms.CharField.clean(self, value)
        if not ValidateCharacters(value):
            raise forms.ValidationError('Can only contain numbers and spaces.')
        value = StripToNumbers(value)
        if not ValidateLuhnChecksum(value):
            raise forms.ValidationError('Not a valid credit card number.')
        
        return value


class CreditCardExpiryField(forms.CharField):
    """ A newforms field for a creditcard expiry date """
    def clean(self, value):     
        value = forms.CharField.clean(self, value.strip())
        
        # Just check MM/YY Pattern
        r = re.compile('^([0-9][0-9])/([0-9][0-9])$')
        m = r.match(value)
        if m == None:
            raise forms.ValidationError('Must be in the format MM/YY. i.e. "11/10" for Nov 2010.')
        
        # Check that the month is 1-12
        month = int(m.groups()[0])
        if month < 1 or month > 12:
            raise forms.ValidationError('Month must be in the range 1 - 12.')
        
        # Check that the year is not too far into the future
        year = int(m.groups()[1])
        curr_year = datetime.datetime.now().year % 100
        max_year = curr_year + 10
        if year > max_year or year < curr_year:
            raise forms.ValidationError('Year must be in the range %s - %s.' % (str(curr_year).zfill(2), str(max_year).zfill(2),))

        return value   

# An example Form based on ModelForm.
class PaymentForm(forms.ModelForm):    
    cc_number = creditcards.CreditCardNumberField(required=False)
    cc_expiry = creditcards.CreditCardExpiryField()
   
    class Meta:
        model = Payment 
    
    """
        This function checks that the card number matches the card type.  
        If you don't want to do this, comment out this function.
    """
    def clean(self):
        if self.cleaned_data:
            if len(self.cleaned_data.items()) == len(self.fields):      
                if self.cleaned_data['method'] == 'cc':
                    the_type = self.cleaned_data.get('cc_type', '')
                    number = self.cleaned_data.get('cc_number', '')
                    if not ValidateDigits(the_type, number):
                        raise forms.ValidationError('Card Number is not a valid ' + the_type.upper() + ' card number.')
                    if not self.instance.is_payment_valid():
                        raise forms.ValidationError('Credit card payment could not be processed.  Reason is %s.  Check that card details are correct and try again.  If you still receive this error, check with your financial institution.' % (self.instance.gateway_resptxt))
        return self.cleaned_data

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 2 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 2 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 9 months, 2 weeks ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 10 months, 1 week ago
  5. Help text hyperlinks by sa2812 11 months ago

Comments

mk (on May 20, 2008):

Please note: If you want to use that snippet with mod_wsgi, you need to remove all print statements.

#

humphreymurray (on May 27, 2008):

Print statements removed... They were just there for when i was debugging :-)

#

donspaulding (on May 28, 2008):

You really ought to rename the type variable in your PaymentForm's clean method, as it's a reserved keyword in Python.

#

humphreymurray (on June 22, 2008):

thanx donspaulding... fixed

#

anger (on November 17, 2011):

there is an error on string 68 i think the second return must be before 'if'

#

Please login first before commenting.