Commit 1e7d41ed by Vedran Karacic Committed by Vedran Karačić

Client side basket checkout fixes.

* Display form errors from the server on the UI
* Mobile design alignment fix
* Alt attributes for all images
* Catch the ValueError for when a user with empty basket
  opens the basket page. A basket is carried in user
  sessions as a SimpleLazyObject, if it's filled it will
  be commited to the database, but when it's empty, our
  check for vouchers will return a ValueError instead.
parent 5fb27e88
......@@ -387,11 +387,21 @@ class BasketSummaryViewTests(CourseCatalogTestMixin, CourseCatalogMockMixin, Lms
self.assertFalse(line_data['enrollment_code'])
self.assertEqual(response.context['payment_processors'][0].NAME, DummyProcessor.NAME)
def test_no_basket_response(self):
""" Verify there are no form and line data in the context for a non-existing basket. """
def assert_emtpy_basket(self):
""" Assert that the basket is empty on visiting the basket summary page. """
response = self.client.get(self.path)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['formset_lines_data'], [])
self.assertEqual(response.context['total_benefit'], None)
def test_no_basket_response(self):
""" Verify there are no form, line and benefit data in the context for a non-existing basket. """
self.assert_emtpy_basket()
def test_anonymous_basket(self):
""" Verify there are no form, line and benefit data in the context for anonymous user. """
self.client.logout()
self.assert_emtpy_basket()
def test_line_item_discount_data(self):
""" Verify that line item has correct discount data. """
......
......@@ -215,11 +215,14 @@ class BasketSummaryView(BasketView):
# Total benefit displayed in price summary.
# Currently only one voucher per basket is supported.
applied_voucher = basket.vouchers.first()
total_benefit = (
format_benefit_value(applied_voucher.offers.first().benefit)
if applied_voucher else None
)
try:
applied_voucher = basket.vouchers.first()
total_benefit = (
format_benefit_value(applied_voucher.offers.first().benefit)
if applied_voucher else None
)
except ValueError:
total_benefit = None
context.update({
'total_benefit': total_benefit,
......
......@@ -68,12 +68,18 @@ define([
$form.appendTo('body').submit();
},
appendValidationErrorMsg = function(event, field, msg) {
appendCardValidationErrorMsg = function(event, field, msg) {
field.find('~.help-block').append('<span>' + msg + '</span>');
field.focus();
event.preventDefault();
},
appendCardHolderValidationErrorMsg = function(field, msg) {
field.parentsUntil('form-item').find('~.help-block').append(
'<span>' + msg + '</span>'
);
},
cardHolderInfoValidation = function (event) {
var requiredFields = [
'input[name=first_name]',
......@@ -86,9 +92,7 @@ define([
_.each(requiredFields, function(field) {
if ($(field).val() === '') {
event.preventDefault();
$(field).parentsUntil('form-item').find('~.help-block').append(
'<span>This field is required</span>'
);
appendCardHolderValidationErrorMsg($(field), 'This field is required');
}
});
......@@ -112,20 +116,20 @@ define([
cardType = CreditCardUtils.getCreditCardType(cardNumber);
if (!CreditCardUtils.isValidCardNumber(cardNumber)) {
appendValidationErrorMsg(event, cardNumberField, 'Invalid card number');
appendCardValidationErrorMsg(event, cardNumberField, 'Invalid card number');
} else if (typeof cardType === 'undefined') {
appendValidationErrorMsg(event, cardNumberField, 'Unsupported card type');
appendCardValidationErrorMsg(event, cardNumberField, 'Unsupported card type');
} else if (cvnNumber.length !== cardType.cvnLength || !Number.isInteger(Number(cvnNumber))) {
appendValidationErrorMsg(event, cvnNumberField, 'Invalid CVN');
appendCardValidationErrorMsg(event, cvnNumberField, 'Invalid CVN');
}
if (!Number.isInteger(Number(cardExpiryMonth)) ||
Number(cardExpiryMonth) > 12 || Number(cardExpiryMonth) < 1) {
appendValidationErrorMsg(event, cardExpiryMonthField, 'Invalid month');
appendCardValidationErrorMsg(event, cardExpiryMonthField, 'Invalid month');
} else if (!Number.isInteger(Number(cardExpiryYear)) || Number(cardExpiryYear) < currentYear) {
appendValidationErrorMsg(event, cardExpiryYearField, 'Invalid year');
appendCardValidationErrorMsg(event, cardExpiryYearField, 'Invalid year');
} else if (Number(cardExpiryMonth) < currentMonth && Number(cardExpiryYear) === currentYear) {
appendValidationErrorMsg(event, cardExpiryMonthField, 'Card expired');
appendCardValidationErrorMsg(event, cardExpiryMonthField, 'Card expired');
}
},
onReady = function() {
......@@ -155,10 +159,10 @@ define([
$('.card-type-icon').attr(
'src',
iconPath + card.name + '.png'
);
).removeClass('hidden');
$('input[name=card_type]').val(card.type);
} else {
$('.card-type-icon').attr('src', '');
$('.card-type-icon').attr('src', '').addClass('hidden');
$('input[name=card_type]').val('');
}
}
......@@ -217,6 +221,7 @@ define([
};
return {
appendCardHolderValidationErrorMsg: appendCardHolderValidationErrorMsg,
appendToForm: appendToForm,
cardInfoValidation: cardInfoValidation,
checkoutPayment: checkoutPayment,
......
/**
* CyberSource payment processor specific actions.
*/
require(['jquery'], function($) {
require([
'jquery',
'pages/basket_page'
], function(
$,
BasketPage
) {
'use strict';
function initializePaymentForm() {
......@@ -44,15 +50,28 @@ require(['jquery'], function($) {
}
}
},
error: function (jqXHR, textStatus, errorThrown) {
// TODO Handle errors. Ideally the form should be validated in JavaScript
// before it is submitted.
console.log(jqXHR);
console.log(textStatus);
console.log(errorThrown);
error: function (jqXHR, textStatus) {
// Don't allow the form to submit.
event.preventDefault();
event.stopPropagation();
var cardHolderFields = [
'first_name', 'last_name', 'address_line1', 'address_line2',
'state', 'city', 'country', 'postal_code'
];
if (textStatus === 'error') {
var error = JSON.parse(jqXHR.responseText);
if (error.field_errors) {
for (var k in error.field_errors) {
if (cardHolderFields.indexOf(k) !== -1) {
var field = $('input[name=' + k + ']');
BasketPage.appendCardHolderValidationErrorMsg(field, error.field_errors[k]);
field.focus();
}
}
}
}
}
});
});
......
......@@ -254,7 +254,9 @@
}
.help-block {
height: 10px;
margin-top: 0;
margin-bottom: 0;
min-height: 22px;
color: red;
}
......@@ -355,7 +357,7 @@
#expiration-label {
width: 100%;
margin-left: 15px;
padding-left: 15px;
text-align: left;
}
......@@ -527,15 +529,9 @@
}
.payment-form #payment-information {
border-left: none;
}
.payment-methods {
padding-left: 0;
#payment-method {
padding-left: 0;
}
padding-right: 0;
border-left: none;
}
#summary #basket-information .product {
......
......@@ -108,7 +108,7 @@
<legend>{% trans "SELECT PAYMENT METHOD" %}</legend>
<div class="col-sm-12 payment-methods">
<div id="payment-method" class="col-md-4 col-sm-7 col-xs-6">
<a href="#payment-information" id="payment-method-image" aria-label={% trans "Pay with a Credit Card" %} role="button"><img aria-hidden="true" src="/static/images/credit_card_options.png"></a>
<a href="#payment-information" id="payment-method-image" aria-label={% trans "Pay with a Credit Card" %} role="button"><img aria-hidden="true" src="/static/images/credit_card_options.png" alt="Credit cards"></a>
</div>
<div id="payment-processor" class="payment-buttons col-md-3 col-sm-5 col-xs-6" data-basket-id="{{ basket.id }}">
{# Translators: Do NOT translate the name PayPal. #}
......@@ -140,14 +140,14 @@
{% if not free_basket %}
<legend aria-label={% trans "Billing information" %}>{% trans "BILLING INFORMATION" %}</legend>
{% if not paypal_enabled %}
<img aria-hidden="true" class="credit-card-list" src="/static/images/credit_card_options.png">
<img aria-hidden="true" class="credit-card-list" src="/static/images/credit_card_options.png" alt="Credit cards">
{% endif %}
<div class="pci-fields row">
<div id="card-number" class="form-group col-sm-6">
<label for="card-number-input" class="control-label">{% trans "Card Number (required)" %}<span class="sr">, {% trans "Secure" %}</span></label>
<input id="card-number-input" name="card_number" class="form-control pci-field" maxlength="20" required aria-required="true"/>
<i class="fa fa-lock" aria-hidden="true"></i>
<img class="card-type-icon">
<img class="card-type-icon hidden" alt="Credit card icon">
<p class="help-block"></p>
</div>
<input type="hidden" name="card_type" class="pci-field">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment