"""
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(type, number):
""" Check that a credit card number matches the type and validates the Luhn Checksum """
type = type.strip().lower()
if ValidateCharacters(number):
number = StripToNumbers(number)
if CC_PATTERNS.has_key(type):
return ValidateDigits(type, number)
return ValidateLuhnChecksum(number)
return False
class CreditCardNumberField(forms.CharField):
""" A newforms widget 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 widget 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
Search This Blog
Thursday, November 26, 2009
Credit Card Validation
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment