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

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