from django import forms | |
from django.forms.widgets import TextInput | |
from django.utils.translation import ugettext_lazy as _ | |
class TelephoneInput(TextInput): | |
# switch input type to type tel so that the numeric keyboard shows on mobile devices | |
input_type = 'tel' | |
class CreditCardField(forms.CharField): | |
# validates almost all of the example cards from PayPal | |
# https://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm | |
cards = [ | |
{ | |
'type': 'maestro', | |
'patterns': [5018, 502, 503, 506, 56, 58, 639, 6220, 67], | |
'length': [12, 13, 14, 15, 16, 17, 18, 19], | |
'cvvLength': [3], | |
'luhn': True | |
}, { | |
'type': 'forbrugsforeningen', | |
'patterns': [600], | |
'length': [16], | |
'cvvLength': [3], | |
'luhn': True | |
}, { | |
'type': 'dankort', | |
'patterns': [5019], | |
'length': [16], | |
'cvvLength': [3], | |
'luhn': True | |
}, { | |
'type': 'visa', | |
'patterns': [4], | |
'length': [13, 16], | |
'cvvLength': [3], | |
'luhn': True | |
}, { | |
'type': 'mastercard', | |
'patterns': [51, 52, 53, 54, 55, 22, 23, 24, 25, 26, 27], | |
'length': [16], | |
'cvvLength': [3], | |
'luhn': True | |
}, { | |
'type': 'amex', | |
'patterns': [34, 37], | |
'length': [15], | |
'cvvLength': [3, 4], | |
'luhn': True | |
}, { | |
'type': 'dinersclub', | |
'patterns': [30, 36, 38, 39], | |
'length': [14], | |
'cvvLength': [3], | |
'luhn': True | |
}, { | |
'type': 'discover', | |
'patterns': [60, 64, 65, 622], | |
'length': [16], | |
'cvvLength': [3], | |
'luhn': True | |
}, { | |
'type': 'unionpay', | |
'patterns': [62, 88], | |
'length': [16, 17, 18, 19], | |
'cvvLength': [3], | |
'luhn': False | |
}, { | |
'type': 'jcb', | |
'patterns': [35], | |
'length': [16], | |
'cvvLength': [3], | |
'luhn': True | |
} | |
] | |
def __init__(self, placeholder=None, *args, **kwargs): | |
super(CreditCardField, self).__init__( | |
# override default widget | |
widget=TelephoneInput(attrs={ | |
'placeholder': placeholder | |
}) | |
, *args, **kwargs) | |
default_error_messages = { | |
'invalid': _(u'The credit card number is invalid'), | |
} | |
def clean(self, value): | |
# ensure no spaces or dashes | |
value = value.replace(' ', '').replace('-', '') | |
# get the card type and its specs | |
card = self.card_from_number(value) | |
# if no card found, invalid | |
if not card: | |
raise forms.ValidationError(self.error_messages['invalid']) | |
# check the length | |
if not len(value) in card['length']: | |
raise forms.ValidationError(self.error_messages['invalid']) | |
# test luhn if necessary | |
if card['luhn']: | |
if not self.validate_mod10(value): | |
raise forms.ValidationError(self.error_messages['invalid']) | |
return value | |
def card_from_number(self, num): | |
# find this card, based on the card number, in the defined set of cards | |
for card in self.cards: | |
for pattern in card['patterns']: | |
if (str(pattern) == str(num)[:len(str(pattern))]): | |
return card | |
def validate_mod10(self, num): | |
# validate card number using the Luhn (mod 10) algorithm | |
checksum, factor = 0, 1 | |
for c in reversed(num): | |
for c in str(factor * int(c)): | |
checksum += int(c) | |
factor = 3 - factor | |
return checksum % 10 == 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment