Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. from datetime import date
  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. num_years = 5
  76. def __init__(self, *args, **kwargs):
  77. # Allow the number of years to be specified
  78. if 'num_years' in kwargs:
  79. self.num_years = kwargs['num_years']
  80. del kwargs['num_years']
  81. errors = self.default_error_messages.copy()
  82. if 'error_messages' in kwargs:
  83. errors.update(kwargs['error_messages'])
  84. fields = (
  85. forms.ChoiceField(choices=self.month_choices(),
  86. error_messages={'invalid': errors['invalid_month']}),
  87. forms.ChoiceField(choices=self.year_choices(),
  88. error_messages={'invalid': errors['invalid_year']}),
  89. )
  90. super(BankcardMonthField, self).__init__(fields, *args, **kwargs)
  91. self.widget = BankcardMonthWidget(widgets = [fields[0].widget, fields[1].widget])
  92. def month_choices(self):
  93. return []
  94. def year_choices(self):
  95. return []
  96. class BankcardExpiryMonthField(BankcardMonthField):
  97. """
  98. Expiry month
  99. """
  100. def month_choices(self):
  101. return [("%.2d" % x, "%.2d" % x) for x in xrange(1, 13)]
  102. def year_choices(self):
  103. return [(x, x) for x in xrange(date.today().year, date.today().year+self.num_years)]
  104. def clean(self, value):
  105. expiry_date = super(BankcardExpiryMonthField, self).clean(value)
  106. if date.today() > expiry_date:
  107. raise forms.ValidationError("The expiration date you entered is in the past.")
  108. return expiry_date
  109. def compress(self, data_list):
  110. if data_list:
  111. if data_list[1] in forms.fields.EMPTY_VALUES:
  112. error = self.error_messages['invalid_year']
  113. raise forms.ValidationError(error)
  114. if data_list[0] in forms.fields.EMPTY_VALUES:
  115. error = self.error_messages['invalid_month']
  116. raise forms.ValidationError(error)
  117. year = int(data_list[1])
  118. month = int(data_list[0])
  119. # find last day of the month
  120. day = monthrange(year, month)[1]
  121. return date(year, month, day)
  122. return None
  123. class BankcardStartingMonthField(BankcardMonthField):
  124. """
  125. Starting month
  126. """
  127. def month_choices(self):
  128. months = [("%.2d" % x, "%.2d" % x) for x in xrange(1, 13)]
  129. months.insert(0, ("", "--"))
  130. return months
  131. def year_choices(self):
  132. years = [(x, x) for x in xrange( date.today().year - self.num_years, date.today().year)]
  133. years.insert(0, ("", "--"))
  134. return years
  135. def clean(self, value):
  136. starting_date = super(BankcardMonthField, self).clean(value)
  137. if starting_date and date.today() < starting_date:
  138. raise forms.ValidationError("The starting date you entered is in the future.")
  139. return starting_date
  140. def compress(self, data_list):
  141. if data_list:
  142. if data_list[1] in forms.fields.EMPTY_VALUES:
  143. error = self.error_messages['invalid_year']
  144. raise forms.ValidationError(error)
  145. if data_list[0] in forms.fields.EMPTY_VALUES:
  146. error = self.error_messages['invalid_month']
  147. raise forms.ValidationError(error)
  148. year = int(data_list[1])
  149. month = int(data_list[0])
  150. return date(year, month, 1)
  151. return None
  152. class BankcardForm(forms.ModelForm):
  153. number = BankcardNumberField(max_length=20, widget=forms.TextInput(attrs={'autocomplete':'off'}), label="Card number")
  154. name = forms.CharField(max_length=128, label="Name on card")
  155. cvv_number = forms.RegexField(required=True, label="CVV Number",
  156. regex=r'^\d{3,4}$', widget=forms.TextInput(attrs={'size': '5'}))
  157. start_month = BankcardStartingMonthField(label="Valid from", required=False)
  158. expiry_month = BankcardExpiryMonthField(required=True, label = "Valid to")
  159. class Meta:
  160. model = payment_models.Bankcard
  161. exclude = ('user', 'partner_reference')
  162. fields = ('number', 'name', 'start_month', 'expiry_month', 'cvv_number')
  163. def get_bankcard_obj(self):
  164. """
  165. Returns a Bankcard object for use in payment processing.
  166. """
  167. kwargs = {
  168. 'name': self.cleaned_data['name'],
  169. 'card_number': self.cleaned_data['number'],
  170. 'expiry_date': self.cleaned_data['expiry_month'].strftime("%m/%y"),
  171. 'cvv': self.cleaned_data['cvv_number'],
  172. }
  173. if self.cleaned_data['start_month']:
  174. kwargs['start_date'] = self.cleaned_data['start_month'].strftime("%m/%y")
  175. return Bankcard(**kwargs)
  176. class BillingAddressForm(forms.ModelForm):
  177. def __init__(self, *args, **kwargs):
  178. super(BillingAddressForm,self ).__init__(*args, **kwargs)
  179. self.set_country_queryset()
  180. def set_country_queryset(self):
  181. self.fields['country'].queryset = address_models.Country._default_manager.all()
  182. class Meta:
  183. model = order_models.BillingAddress
  184. exclude = ('search_text',)