Skip to content

Commit

Permalink
Added more handling for bankcards and their forms
Browse files Browse the repository at this point in the history
  • Loading branch information
codeinthehole committed Feb 23, 2011
1 parent 4fe2904 commit d65d7d6
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 22 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ django_oscar.egg-info/
STORIES
docs/build/*
*.*~
examples/.coverage
17 changes: 16 additions & 1 deletion examples/datacash/shop/checkout/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
PaymentMethodView as CorePaymentMethodView,
PaymentDetailsView as CorePaymentDetailsView,
OrderPreviewView as CoreOrderPreviewView,
SubmitView as CoreSubmitView,
prev_steps_must_be_complete)
from oscar.payment.forms import BankcardForm
from oscar.services import import_module

shipping_models = import_module('shipping.models', ['Method'])

shipping_models = import_module('shipping.models', ['Method'])

class ShippingMethodView(CoreShippingMethodView):

Expand Down Expand Up @@ -46,10 +47,24 @@ class PaymentDetailsView(CorePaymentMethodView):
template_file = 'checkout/payment_details.html'

def handle_GET(self):

# Need a billing address form and a bankcard form
self.context['bankcard_form'] = BankcardForm()

return render(self.request, self.template_file, self.context)

def handle_POST(self):
assert False


class SubmitView(CoreSubmitView):

def _handle_payment(self, basket):
u"""Handle any payment processing"""

bankcard_form = BankcardForm(request.POST)
if not bankcard_form.is_valid():
# Put form into the session and redirect back
self.request.session['bankcard_form'] = bankcard_form

assert False
9 changes: 3 additions & 6 deletions examples/datacash/templates/checkout/payment_details.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@
{% block payment_details %}
<h3>Payment details</h3>
<h4>Bankcard</h4>
<form method="post" action="">
<form method="post" action="{% url oscar-checkout-payment-details %}">
{% csrf_token %}
{{ bankcard_form.as_p }}
<input type="submit" value="Place order" />
</form>

{% endblock payment_details %}

{% block place_order %}
<hr/>
<h3>Place your order</h3>
<a href="{% url oscar-checkout-payment-details %}">Place order</a>
{% endblock place_order %}

8 changes: 0 additions & 8 deletions examples/datacash/templates/checkout/preview.html

This file was deleted.

8 changes: 3 additions & 5 deletions oscar/checkout/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,9 @@ def step_complete(self, request):

def get_next_step(self, request):
u"""Returns the next incomplete step of the checkout."""
completed_steps = self._get_completed_steps(request)
if len(completed_steps):
return self.urls_for_steps[len(completed_steps)]
else:
return self.urls_for_steps[0]
url_name = self._get_url_name(request)
current_step_index = self.urls_for_steps.index(url_name)
return self.urls_for_steps[current_step_index+1]

def all_steps_complete(self, request):
u"""
Expand Down
1 change: 1 addition & 0 deletions oscar/checkout/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def _view_wrapper(self, request, *args, **kwargs):
if not checker.are_previous_steps_complete(request):
messages.error(request, "You must complete this step of the checkout first")
url_name = checker.get_next_step(request)
assert False
return HttpResponseRedirect(reverse(url_name))
return view_fn(self, request, *args, **kwargs)
return _view_wrapper
Expand Down
1 change: 1 addition & 0 deletions oscar/payment/abstract_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def __unicode__(self):

class AbstractBankcard(models.Model):
user = models.ForeignKey('auth.User', related_name='bankcards')
card_type = models.CharField(max_length=128)
name = models.CharField(max_length=255)
number = models.CharField(max_length=32)
expiry_date = models.DateField()
Expand Down
109 changes: 108 additions & 1 deletion oscar/payment/forms.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,120 @@
from datetime import date, datetime
from calendar import monthrange
import re

from django import forms

from oscar.services import import_module

payment_models = import_module('payment.models', ['Bankcard'])

VISA, MASTERCARD, AMEX, MAESTRO, DISCOVER = ('Visa', 'Mastercard', 'American Express', 'Maestro', 'Discover')

def bankcard_type(number):
number = str(number)
if len(number) == 13:
if number[0] == "4":
return VISA
elif len(number) == 14:
if number[:2] == "36":
return MASTERCARD
elif len(number) == 15:
if number[:2] in ("34", "37"):
return AMEX
elif len(number) == 16:
if number[:4] == "6011":
return DISCOVER
if number[:2] in ("51", "52", "53", "54", "55"):
return MASTERCARD
if number[0] == "4":
return VISA
return None


class BankcardField(forms.CharField):

def clean(self, value):
"""Check if given CC number is valid and one of the
card types we accept"""
non_decimal = re.compile(r'\D+')
value = non_decimal.sub('', value.strip())

if value and (len(value) < 13 or len(value) > 16):
raise forms.ValidationError("Please enter in a valid credit card number.")
return super(BankcardField, self).clean(value)


class BankcardMonthWidget(forms.MultiWidget):
"""
Widget containing two select boxes for selecting the month and year
"""
def decompress(self, value):
return [value.month, value.year] if value else [None, None]

def format_output(self, rendered_widgets):
html = u' '.join(rendered_widgets)
return u'<span style="white-space: nowrap">%s</span>' % html


class BankcardMonthField(forms.MultiValueField):
"""
A modified version of the snippet: http://djangosnippets.org/snippets/907/
"""
MONTHS = [("%.2d" % x, "%.2d" % x) for x in xrange(1, 13)]
YEARS = [(x, x) for x in xrange(date.today().year, date.today().year + 10)]

default_error_messages = {
'invalid_month': u'Enter a valid month.',
'invalid_year': u'Enter a valid year.',
}

def __init__(self, *args, **kwargs):
errors = self.default_error_messages.copy()
if 'error_messages' in kwargs:
errors.update(kwargs['error_messages'])
fields = (
forms.ChoiceField(choices=self.MONTHS,
error_messages={'invalid': errors['invalid_month']}),
forms.ChoiceField(choices=self.YEARS,
error_messages={'invalid': errors['invalid_year']}),
)
super(BankcardMonthField, self).__init__(fields, *args, **kwargs)
self.widget = BankcardMonthWidget(widgets = [fields[0].widget, fields[1].widget])

def clean(self, value):
expiry_date = super(BankcardMonthField, self).clean(value)
if date.today() > expiry_date:
raise forms.ValidationError("The expiration date you entered is in the past.")
return expiry_date

def compress(self, data_list):
if data_list:
if data_list[1] in forms.fields.EMPTY_VALUES:
error = self.error_messages['invalid_year']
raise forms.ValidationError(error)
if data_list[0] in forms.fields.EMPTY_VALUES:
error = self.error_messages['invalid_month']
raise forms.ValidationError(error)
year = int(data_list[1])
month = int(data_list[0])
# find last day of the month
day = monthrange(year, month)[1]
return date(year, month, day)
return None


class BankcardForm(forms.ModelForm):

number = BankcardField(max_length=20, widget=forms.TextInput(attrs={'autocomplete':'off'}), label="Card number")
name = forms.CharField(max_length=128, label="Name on card")
ccv_number = forms.IntegerField(required = True, label = "CCV Number",
max_value = 9999, widget=forms.TextInput(attrs={'size': '4'}))
expiry_month = BankcardMonthField(required = True, label = "Valid to")

class Meta:
model = payment_models.Bankcard
exclude = ('user', 'partner_reference')

fields = ('number', 'name', 'expiry_month', 'ccv_number')



25 changes: 25 additions & 0 deletions oscar/payment/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.contrib.auth.models import User

from oscar.payment.models import Bankcard
from oscar.payment.forms import bankcard_type, VISA, MASTERCARD, AMEX, DISCOVER, BankcardField


class BankcardTest(TestCase):
Expand All @@ -18,3 +19,27 @@ def test_number_is_anonymised_when_saving(self):
bankcard = Bankcard.objects.create(name="David Winterbottom", number="1000011100000004", user=user, expiry_date=expiry_date)
self.assertEquals("XXXX-XXXX-XXXX-0004", bankcard.number)


class BankcardTypeTest(TestCase):

fixture_data = {
VISA: ('4111111111111111',),
MASTERCARD: ('5500000000000004',),
DISCOVER: ('6011000000000004',),
AMEX: ('340000000000009',),
}

def test_bankcard_type_sniffer(self):
for type, numbers in self.fixture_data.items():
for n in numbers:
self.assertEquals(type, bankcard_type(n), "%s is a %s" % (n, type))


class BankcardFieldType(TestCase):

def setUp(self):
self.f = BankcardField()

def test_spaces_are_stipped(self):
self.assertEquals('4111111111111111', self.f.clean(' 4111 1111 1111 1111'))

2 changes: 1 addition & 1 deletion oscar/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def import_module(module_label, classes=[]):
local_module_name = "%s.%s" % (installed_app_parts[0], module_label)
try:
imported_local_mod = __import__(local_module_name, fromlist=classes)
except ImportError:
except ImportError, e:
# Module doesn't exist, fall back to oscar core
return _import_classes_from_module("oscar.%s" % module_label, classes)

Expand Down

0 comments on commit d65d7d6

Please sign in to comment.