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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. from datetime import date
  2. from calendar import monthrange
  3. import re
  4. from django import forms
  5. from django.core.exceptions import ImproperlyConfigured
  6. from django.utils.translation import ugettext_lazy as _
  7. from oscar.core.loading import get_model
  8. from oscar.apps.address.forms import AbstractAddressForm
  9. from oscar.views.generic import PhoneNumberMixin
  10. from . import bankcards
  11. Country = get_model('address', 'Country')
  12. BillingAddress = get_model('order', 'BillingAddress')
  13. Bankcard = get_model('payment', 'Bankcard')
  14. # List of card names for all the card types supported in payment.bankcards
  15. VALID_CARDS = set([card_type[0] for card_type in bankcards.CARD_TYPES])
  16. class BankcardNumberField(forms.CharField):
  17. def __init__(self, *args, **kwargs):
  18. _kwargs = {
  19. 'max_length': 20,
  20. 'widget': forms.TextInput(attrs={'autocomplete': 'off'}),
  21. 'label': _("Card number")
  22. }
  23. if 'types' in kwargs:
  24. self.accepted_cards = set(kwargs.pop('types'))
  25. difference = self.accepted_cards - VALID_CARDS
  26. if difference:
  27. raise ImproperlyConfigured('The following accepted_cards are '
  28. 'unknown: %s' % difference)
  29. _kwargs.update(kwargs)
  30. super(BankcardNumberField, self).__init__(*args, **_kwargs)
  31. def clean(self, value):
  32. """
  33. Check if given CC number is valid and one of the
  34. card types we accept
  35. """
  36. non_decimal = re.compile(r'\D+')
  37. value = non_decimal.sub('', value.strip())
  38. if value and not bankcards.luhn(value):
  39. raise forms.ValidationError(
  40. _("Please enter a valid credit card number."))
  41. if hasattr(self, 'accepted_cards'):
  42. card_type = bankcards.bankcard_type(value)
  43. if card_type not in self.accepted_cards:
  44. raise forms.ValidationError(
  45. _("%s cards are not accepted." % card_type))
  46. return super(BankcardNumberField, self).clean(value)
  47. class BankcardMonthWidget(forms.MultiWidget):
  48. """
  49. Widget containing two select boxes for selecting the month and year
  50. """
  51. def decompress(self, value):
  52. return [value.month, value.year] if value else [None, None]
  53. def format_output(self, rendered_widgets):
  54. html = u' '.join(rendered_widgets)
  55. return u'<span style="white-space: nowrap">%s</span>' % html
  56. class BankcardMonthField(forms.MultiValueField):
  57. """
  58. A modified version of the snippet: http://djangosnippets.org/snippets/907/
  59. """
  60. default_error_messages = {
  61. 'invalid_month': _('Enter a valid month.'),
  62. 'invalid_year': _('Enter a valid year.'),
  63. }
  64. num_years = 5
  65. def __init__(self, *args, **kwargs):
  66. # Allow the number of years to be specified
  67. if 'num_years' in kwargs:
  68. self.num_years = kwargs.pop('num_years')
  69. errors = self.default_error_messages.copy()
  70. if 'error_messages' in kwargs:
  71. errors.update(kwargs['error_messages'])
  72. fields = (
  73. forms.ChoiceField(
  74. choices=self.month_choices(),
  75. error_messages={'invalid': errors['invalid_month']}),
  76. forms.ChoiceField(
  77. choices=self.year_choices(),
  78. error_messages={'invalid': errors['invalid_year']}),
  79. )
  80. if 'widget' not in kwargs:
  81. kwargs['widget'] = BankcardMonthWidget(
  82. widgets=[fields[0].widget, fields[1].widget])
  83. super(BankcardMonthField, self).__init__(fields, *args, **kwargs)
  84. def month_choices(self):
  85. return []
  86. def year_choices(self):
  87. return []
  88. class BankcardExpiryMonthField(BankcardMonthField):
  89. num_years = 10
  90. def __init__(self, *args, **kwargs):
  91. today = date.today()
  92. _kwargs = {
  93. 'required': True,
  94. 'label': _("Valid to"),
  95. 'initial': ["%.2d" % today.month, today.year]
  96. }
  97. _kwargs.update(kwargs)
  98. super(BankcardExpiryMonthField, self).__init__(*args, **_kwargs)
  99. def month_choices(self):
  100. return [("%.2d" % x, "%.2d" % x) for x in range(1, 13)]
  101. def year_choices(self):
  102. return [(x, x) for x in range(
  103. date.today().year,
  104. date.today().year + self.num_years)]
  105. def clean(self, value):
  106. expiry_date = super(BankcardExpiryMonthField, self).clean(value)
  107. if date.today() > expiry_date:
  108. raise forms.ValidationError(
  109. _("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 __init__(self, *args, **kwargs):
  127. _kwargs = {
  128. 'required': False,
  129. 'label': _("Valid from"),
  130. }
  131. _kwargs.update(kwargs)
  132. super(BankcardStartingMonthField, self).__init__(*args, **_kwargs)
  133. def month_choices(self):
  134. months = [("%.2d" % x, "%.2d" % x) for x in range(1, 13)]
  135. months.insert(0, ("", "--"))
  136. return months
  137. def year_choices(self):
  138. today = date.today()
  139. years = [(x, x) for x in range(
  140. today.year - self.num_years,
  141. today.year + 1)]
  142. years.insert(0, ("", "--"))
  143. return years
  144. def clean(self, value):
  145. starting_date = super(BankcardMonthField, self).clean(value)
  146. if starting_date and date.today() < starting_date:
  147. raise forms.ValidationError(
  148. _("The starting date you entered is in the future."))
  149. return starting_date
  150. def compress(self, data_list):
  151. if data_list:
  152. if data_list[1] in forms.fields.EMPTY_VALUES:
  153. error = self.error_messages['invalid_year']
  154. raise forms.ValidationError(error)
  155. if data_list[0] in forms.fields.EMPTY_VALUES:
  156. error = self.error_messages['invalid_month']
  157. raise forms.ValidationError(error)
  158. year = int(data_list[1])
  159. month = int(data_list[0])
  160. return date(year, month, 1)
  161. return None
  162. class BankcardCCVField(forms.RegexField):
  163. def __init__(self, *args, **kwargs):
  164. _kwargs = {
  165. 'required': True,
  166. 'label': _("CCV number"),
  167. 'widget': forms.TextInput(attrs={'size': '5'}),
  168. 'error_message': _("Please enter a 3 or 4 digit number"),
  169. 'help_text': _("This is the 3 or 4 digit security number "
  170. "on the back of your bankcard")
  171. }
  172. _kwargs.update(kwargs)
  173. super(BankcardCCVField, self).__init__(
  174. r'^\d{3,4}$', *args, **_kwargs)
  175. def clean(self, value):
  176. if value is not None:
  177. value = value.strip()
  178. return super(BankcardCCVField, self).clean(value)
  179. class BankcardForm(forms.ModelForm):
  180. # By default, this number field will accept any number. The only validation
  181. # is whether it passes the luhn check. If you wish to only accept certain
  182. # types of card, you can pass a types kwarg to BankcardNumberField, e.g.
  183. #
  184. # BankcardNumberField(types=[bankcards.VISA, bankcards.VISA_ELECTRON,])
  185. number = BankcardNumberField()
  186. ccv = BankcardCCVField()
  187. start_month = BankcardStartingMonthField()
  188. expiry_month = BankcardExpiryMonthField()
  189. class Meta:
  190. model = Bankcard
  191. fields = ('number', 'start_month', 'expiry_month', 'ccv')
  192. def clean(self):
  193. data = self.cleaned_data
  194. number, ccv = data.get('number'), data.get('ccv')
  195. if number and ccv:
  196. if bankcards.is_amex(number) and len(ccv) != 4:
  197. raise forms.ValidationError(_(
  198. "American Express cards use a 4 digit security code"))
  199. return data
  200. def save(self, *args, **kwargs):
  201. # It doesn't really make sense to save directly from the form as saving
  202. # will obfuscate some of the card details which you normally need to
  203. # pass to a payment gateway. Better to use the bankcard property below
  204. # to get the cleaned up data, then once you've used the sensitive
  205. # details, you can save.
  206. raise RuntimeError("Don't save bankcards directly from form")
  207. @property
  208. def bankcard(self):
  209. """
  210. Return an instance of the Bankcard model (unsaved)
  211. """
  212. return Bankcard(number=self.cleaned_data['number'],
  213. expiry_date=self.cleaned_data['expiry_month'],
  214. start_date=self.cleaned_data['start_month'],
  215. ccv=self.cleaned_data['ccv'])
  216. class BillingAddressForm(PhoneNumberMixin, AbstractAddressForm):
  217. def __init__(self, *args, **kwargs):
  218. super(BillingAddressForm, self).__init__(*args, **kwargs)
  219. self.set_country_queryset()
  220. def set_country_queryset(self):
  221. self.fields['country'].queryset = Country._default_manager.all()
  222. class Meta:
  223. model = BillingAddress
  224. exclude = ('search_text',)