- 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
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
Please note: If you want to use that snippet with mod_wsgi, you need to remove all print statements.
#
Print statements removed... They were just there for when i was debugging :-)
#
You really ought to rename the
type
variable in your PaymentForm'sclean
method, as it's a reserved keyword in Python.#
thanx donspaulding... fixed
#
there is an error on string 68 i think the second return must be before 'if'
#
Please login first before commenting.