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
|
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
typevariable in your PaymentForm'scleanmethod, as it's a reserved keyword in Python.#
thanx donspaulding... fixed
#
I modified this snippet a bit for my own use, but here are the regexes I ended up with.
regex's stolen shamelessly from http://www.regular-expressions.info/creditcard.html (who stole them from somewhere else)
#
In the code comments you say "A newforms widget for..." but they're not widgets, they're fields.
A widget would be handy for the MM/YY date field though, such as: this or this
nice work, thanks!
#
there is an error on string 68 i think the second return must be before 'if'
#