You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

forms.py 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. from datetime import date, datetime
  2. from calendar import monthrange
  3. import re
  4. from django import forms
  5. from oscar.core.loading import import_module
  6. address_models = import_module('address.models', ['Country'])
  7. order_models = import_module('order.models', ['BillingAddress'])
  8. payment_models = import_module('payment.models', ['Bankcard'])
  9. import_module('payment.utils', ['Bankcard'], locals())
  10. VISA, MASTERCARD, AMEX, MAESTRO, DISCOVER = ('Visa', 'Mastercard', 'American Express', 'Maestro', 'Discover')
  11. def bankcard_type(number):
  12. u"""
  13. Returns the type of a bankcard based on its number.
  14. """
  15. number = str(number)
  16. if len(number) == 13:
  17. if number[0] == "4":
  18. return VISA
  19. elif len(number) == 14:
  20. if number[:2] == "36":
  21. return MASTERCARD
  22. elif len(number) == 15:
  23. if number[:2] in ("34", "37"):
  24. return AMEX
  25. elif len(number) == 16:
  26. if number[:4] == "6011":
  27. return DISCOVER
  28. if number[:2] in ("51", "52", "53", "54", "55"):
  29. return MASTERCARD
  30. if number[0] == "4":
  31. return VISA
  32. return None
  33. def luhn(card_number):
  34. u"""
  35. Tests whether a bankcard number passes the Luhn algorithm.
  36. """
  37. card_number = str(card_number)
  38. sum = 0
  39. num_digits = len(card_number)
  40. odd_even = num_digits & 1
  41. for i in range(0, num_digits):
  42. digit = int(card_number[i])
  43. if not (( i & 1 ) ^ odd_even ):
  44. digit = digit * 2
  45. if digit > 9:
  46. digit = digit - 9
  47. sum = sum + digit
  48. return (sum % 10) == 0
  49. class BankcardNumberField(forms.CharField):
  50. def clean(self, value):
  51. """Check if given CC number is valid and one of the
  52. card types we accept"""
  53. non_decimal = re.compile(r'\D+')
  54. value = non_decimal.sub('', value.strip())
  55. if value and not luhn(value):
  56. raise forms.ValidationError("Please enter a valid credit card number.")
  57. return super(BankcardNumberField, self).clean(value)
  58. class BankcardMonthWidget(forms.MultiWidget):
  59. """
  60. Widget containing two select boxes for selecting the month and year
  61. """
  62. def decompress(self, value):
  63. return [value.month, value.year] if value else [None, None]
  64. def format_output(self, rendered_widgets):
  65. html = u' '.join(rendered_widgets)
  66. return u'<span style="white-space: nowrap">%s</span>' % html
  67. class BankcardMonthField(forms.MultiValueField):
  68. """
  69. A modified version of the snippet: http://djangosnippets.org/snippets/907/
  70. """
  71. default_error_messages = {
  72. 'invalid_month': u'Enter a valid month.',
  73. 'invalid_year': u'Enter a valid year.',
  74. }
  75. def __init__(self, *args, **kwargs):
  76. errors = self.default_error_messages.copy()
  77. if 'error_messages' in kwargs:
  78. errors.update(kwargs['error_messages'])
  79. fields = (
  80. forms.ChoiceField(choices=self.month_choices(),
  81. error_messages={'invalid': errors['invalid_month']}),
  82. forms.ChoiceField(choices=self.year_choices(),
  83. error_messages={'invalid': errors['invalid_year']}),
  84. )
  85. super(BankcardMonthField, self).__init__(fields, *args, **kwargs)
  86. self.widget = BankcardMonthWidget(widgets = [fields[0].widget, fields[1].widget])
  87. def month_choices(self):
  88. return []
  89. def year_choices(self):
  90. return []
  91. class BankcardExpiryMonthField(BankcardMonthField):
  92. """
  93. Expiry month
  94. """
  95. def month_choices(self):
  96. return [("%.2d" % x, "%.2d" % x) for x in xrange(1, 13)]
  97. def year_choices(self):
  98. return [(x, x) for x in xrange( date.today().year, date.today().year+5)]
  99. def clean(self, value):
  100. expiry_date = super(BankcardExpiryMonthField, self).clean(value)
  101. if date.today() > expiry_date:
  102. raise forms.ValidationError("The expiration date you entered is in the past.")
  103. return expiry_date
  104. def compress(self, data_list):
  105. if data_list:
  106. if data_list[1] in forms.fields.EMPTY_VALUES:
  107. error = self.error_messages['invalid_year']
  108. raise forms.ValidationError(error)
  109. if data_list[0] in forms.fields.EMPTY_VALUES:
  110. error = self.error_messages['invalid_month']
  111. raise forms.ValidationError(error)
  112. year = int(data_list[1])
  113. month = int(data_list[0])
  114. # find last day of the month
  115. day = monthrange(year, month)[1]
  116. return date(year, month, day)
  117. return None
  118. class BankcardStartingMonthField(BankcardMonthField):
  119. """
  120. Starting month
  121. """
  122. def month_choices(self):
  123. months = [("%.2d" % x, "%.2d" % x) for x in xrange(1, 13)]
  124. months.insert(0, ("", "--"))
  125. return months
  126. def year_choices(self):
  127. years = [(x, x) for x in xrange( date.today().year - 5, date.today().year)]
  128. years.insert(0, ("", "--"))
  129. return years
  130. def clean(self, value):
  131. starting_date = super(BankcardMonthField, self).clean(value)
  132. if starting_date and date.today() < starting_date:
  133. raise forms.ValidationError("The starting date you entered is in the future.")
  134. return starting_date
  135. def compress(self, data_list):
  136. if data_list:
  137. if data_list[1] in forms.fields.EMPTY_VALUES:
  138. error = self.error_messages['invalid_year']
  139. raise forms.ValidationError(error)
  140. if data_list[0] in forms.fields.EMPTY_VALUES:
  141. error = self.error_messages['invalid_month']
  142. raise forms.ValidationError(error)
  143. year = int(data_list[1])
  144. month = int(data_list[0])
  145. return date(year, month, 1)
  146. return None
  147. class BankcardForm(forms.ModelForm):
  148. number = BankcardNumberField(max_length=20, widget=forms.TextInput(attrs={'autocomplete':'off'}), label="Card number")
  149. name = forms.CharField(max_length=128, label="Name on card")
  150. ccv_number = forms.IntegerField(required=True, label="CCV Number",
  151. max_value = 99999, widget=forms.TextInput(attrs={'size': '5'}))
  152. start_month = BankcardStartingMonthField(label="Valid from", required=False)
  153. expiry_month = BankcardExpiryMonthField(required=True, label = "Valid to")
  154. class Meta:
  155. model = payment_models.Bankcard
  156. exclude = ('user', 'partner_reference')
  157. fields = ('number', 'name', 'start_month', 'expiry_month', 'ccv_number')
  158. def get_bankcard_obj(self):
  159. """
  160. Returns a Bankcard object for use in payment processing.
  161. """
  162. kwargs = {
  163. 'card_number': self.cleaned_data['number'],
  164. 'expiry_date': self.cleaned_data['expiry_month'].strftime("%m/%y"),
  165. 'ccv': self.cleaned_data['ccv_number'],
  166. }
  167. if self.cleaned_data['start_month']:
  168. kwargs['start_date'] = self.cleaned_data['start_month'].strftime("%m/%y")
  169. return Bankcard(**kwargs)
  170. class BillingAddressForm(forms.ModelForm):
  171. def __init__(self, *args, **kwargs):
  172. super(BillingAddressForm,self ).__init__(*args, **kwargs)
  173. self.set_country_queryset()
  174. def set_country_queryset(self):
  175. self.fields['country'].queryset = address_models.Country._default_manager.all()
  176. class Meta:
  177. model = order_models.BillingAddress