Commit 03e7d21d by Marko Jevtic

[SOL-1954, SOL-1955, SOL-1956] New Receipt Page - Frontend

parent 57caf7c1
......@@ -11,23 +11,23 @@
],
"dependencies": {
"backbone": "~1.2.1",
"backbone-relational": "~0.9.0",
"backbone-route-filter": "~0.1.2",
"backbone-super": "~1.0.4",
"backbone-validation": "~0.11.5",
"backbone.stickit": "~0.9.2",
"bootstrap-sass": "~3.3.4",
"jquery": "~1.11.3",
"js-cookie": "~2.1.2",
"requirejs": "~2.1.15",
"underscore": "~1.8.2",
"bootstrapaccessibilityplugin": "~1.0.4",
"datatables": "1.10.10",
"fontawesome": "~4.3.0",
"edx-ux-pattern-library": "https://github.com/edx/ux-pattern-library.git#82fa1c823bc322ba8b0742f0c546a89b0c69e952",
"text": "~2.0.14",
"fontawesome": "~4.3.0",
"jquery": "~1.11.3",
"js-cookie": "~2.1.2",
"moment": "~2.10.3",
"pikaday": "https://github.com/owenmead/Pikaday.git#1.4.0",
"underscore.string": "~3.1.1",
"backbone-super": "~1.0.4",
"backbone-route-filter": "~0.1.2",
"backbone-relational": "~0.9.0",
"backbone-validation": "~0.11.5",
"backbone.stickit": "~0.9.2"
"requirejs": "~2.1.15",
"text": "~2.0.14",
"underscore": "~1.8.2",
"underscore.string": "~3.1.1"
}
}
......@@ -35,6 +35,10 @@
{
name: 'js/apps/basket_app',
exclude: ['js/common']
},
{
name: 'js/pages/receipt_page',
exclude: ['js/common']
}
]
})
......@@ -485,6 +485,7 @@ class User(AbstractUser):
cache.set(cache_key, verification, cache_timeout)
return verification
except HttpNotFoundError:
log.debug('No verification data found for [%s]', self.username)
return False
except (ConnectionError, SlumberBaseException, Timeout):
msg = 'Failed to retrieve verification status details for [{username}]'.format(username=self.username)
......
from django import template
from django.conf import settings
from django.utils.safestring import mark_safe
from opaque_keys.edx.keys import CourseKey
register = template.Library()
......@@ -38,6 +39,20 @@ def do_captureas(parser, token):
return CaptureasNode(nodelist, args)
@register.filter(name='course_organization')
def course_organization(course_key):
"""
Retrieve course organization from course key.
Arguments:
course_key (str): Course key.
Returns:
str: Course organization.
"""
return CourseKey.from_string(course_key).org
class CaptureasNode(template.Node):
def __init__(self, nodelist, varname):
self.nodelist = nodelist
......
......@@ -37,3 +37,11 @@ class CoreExtrasTests(TestCase):
def test_captureas_unicode(self):
self.assertTextCaptured(u'★❤')
def test_course_organization(self):
course_id = 'course-v1:edX+Course+100'
template = Template(
"{% load core_extras %}"
"{{ course_id|course_organization }}"
)
self.assertEqual(template.render(Context({'course_id': course_id})), 'edX')
......@@ -7,12 +7,14 @@ class CheckoutApplication(app.CheckoutApplication):
free_checkout = get_class('checkout.views', 'FreeCheckoutView')
cancel_checkout = get_class('checkout.views', 'CancelCheckoutView')
checkout_error = get_class('checkout.views', 'CheckoutErrorView')
receipt_response = get_class('checkout.views', 'ReceiptResponseView')
def get_urls(self):
urls = [
url(r'^free-checkout/$', self.free_checkout.as_view(), name='free-checkout'),
url(r'^cancel-checkout/$', self.cancel_checkout.as_view(), name='cancel-checkout'),
url(r'^error/', self.checkout_error.as_view(), name='error'),
url(r'^receipt/', self.receipt_response.as_view(), name='receipt'),
url(r'^$', self.index_view.as_view(), name='index'),
......@@ -36,12 +38,10 @@ class CheckoutApplication(app.CheckoutApplication):
url(r'payment-details/$',
self.payment_details_view.as_view(), name='payment-details'),
# Preview and thankyou
# Preview
url(r'preview/$',
self.payment_details_view.as_view(preview=True),
name='preview'),
url(r'thank-you/$', self.thankyou_view.as_view(),
name='thank-you'),
]
return self.post_process_urls(urls)
......
......@@ -22,7 +22,7 @@ class SignalTests(CourseCatalogTestMixin, TestCase):
super(SignalTests, self).setUp()
self.user = self.create_user()
self.request.user = self.user
self.site.siteconfiguration.enable_otto_receipt_page = True
self.toggle_ecommerce_receipt_page(True)
toggle_switch('ENABLE_NOTIFICATIONS', True)
def prepare_order(self, seat_type, credit_provider_id=None):
......@@ -90,7 +90,7 @@ class SignalTests(CourseCatalogTestMixin, TestCase):
credit_provider_name=credit_provider_name,
platform_name=self.site.name,
receipt_url=self.site.siteconfiguration.build_ecommerce_url(
'{}{}'.format(settings.RECEIPT_PAGE_PATH, order.number)
'{}?order_number={}'.format(settings.RECEIPT_PAGE_PATH, order.number)
)
)
)
......
import urllib
from decimal import Decimal
from django.core.urlresolvers import reverse
import ddt
import httpretty
from django.conf import settings
from django.core.urlresolvers import reverse
from oscar.core.loading import get_model
from oscar.test import newfactories as factories
from ecommerce.core.url_utils import get_lms_url
from ecommerce.coupons.tests.mixins import CourseCatalogMockMixin
from ecommerce.extensions.checkout.exceptions import BasketNotFreeError
from ecommerce.extensions.checkout.utils import get_receipt_page_url
from ecommerce.extensions.checkout.views import ReceiptResponseView
from ecommerce.extensions.refund.tests.mixins import RefundTestMixin
from ecommerce.tests.mixins import LmsApiMockMixin
from ecommerce.tests.testcases import TestCase
Order = get_model('order', 'Order')
......@@ -20,6 +27,7 @@ class FreeCheckoutViewTests(TestCase):
super(FreeCheckoutViewTests, self).setUp()
self.user = self.create_user()
self.client.login(username=self.user.username, password=self.password)
self.toggle_ecommerce_receipt_page(True)
def prepare_basket(self, price):
""" Helper function that creates a basket and adds a product with set price to it. """
......@@ -46,7 +54,24 @@ class FreeCheckoutViewTests(TestCase):
""" Verify redirect to the receipt page. """
self.prepare_basket(0)
self.assertEqual(Order.objects.count(), 0)
receipt_page = get_lms_url('/commerce/checkout/receipt')
response = self.client.get(self.path)
self.assertEqual(Order.objects.count(), 1)
order = Order.objects.first()
expected_url = get_receipt_page_url(
order_number=order.number,
site_configuration=order.site.siteconfiguration
)
self.assertRedirects(response, expected_url, fetch_redirect_response=False)
@httpretty.activate
def test_redirect_to_lms_receipt(self):
""" Verify that disabling the otto_receipt_page switch redirects to the LMS receipt page. """
self.toggle_ecommerce_receipt_page(False)
self.prepare_basket(0)
self.assertEqual(Order.objects.count(), 0)
receipt_page = self.site.siteconfiguration.build_lms_url('/commerce/checkout/receipt')
response = self.client.get(self.path)
self.assertEqual(Order.objects.count(), 1)
......@@ -121,3 +146,163 @@ class CheckoutErrorViewTests(TestCase):
self.assertEqual(
response.context['payment_support_email'], self.request.site.siteconfiguration.payment_support_email
)
@ddt.ddt
class ReceiptResponseViewTests(CourseCatalogMockMixin, LmsApiMockMixin, RefundTestMixin, TestCase):
"""
Tests for the receipt view.
"""
path = reverse('checkout:receipt')
def setUp(self):
super(ReceiptResponseViewTests, self).setUp()
self.user = self.create_user()
self.client.login(username=self.user.username, password=self.password)
def _get_receipt_response(self, order_number):
"""
Helper function for getting the receipt page response for an order.
Arguments:
order_number (str): Number of Order for which the Receipt Page should be opened.
Returns:
response (Response): Response object that's returned by a ReceiptResponseView
"""
url = '{path}?order_number={order_number}'.format(path=self.path, order_number=order_number)
return self.client.get(url)
def _visit_receipt_page_with_another_user(self, order, user):
"""
Helper function for logging in with another user and going to the Receipt Page.
Arguments:
order (Order): Order for which the Receipt Page should be opened.
user (User): User that's logging in.
Returns:
response (Response): Response object that's returned by a ReceiptResponseView
"""
self.client.logout()
self.client.login(username=user.username, password=self.password)
return self._get_receipt_response(order.number)
def _create_order_for_receipt(self, user, credit=False):
"""
Helper function for creating an order and mocking verification status API response.
Arguments:
user (User): User that's trying to visit the Receipt page.
credit (bool): Indicates whether or not the product is a Credit Course Seat.
Returns:
order (Order): Order for which the Receipt is requested.
"""
self.mock_verification_status_api(
self.site,
user,
status=200,
is_verified=False
)
return self.create_order(credit=credit)
def test_login_required_get_request(self):
""" The view should redirect to the login page if the user is not logged in. """
self.client.logout()
response = self.client.get(self.path)
testserver_login_url = self.get_full_url(reverse(settings.LOGIN_URL))
expected_url = '{path}?next={next}'.format(path=testserver_login_url, next=urllib.quote(self.path))
self.assertRedirects(response, expected_url, target_status_code=302)
def test_get_receipt_for_nonexisting_order(self):
""" The view should return 404 status if the Order is not found. """
order_number = 'ABC123'
response = self._get_receipt_response(order_number)
self.assertEqual(response.status_code, 404)
def test_get_payment_method_no_source(self):
""" Payment method should be None when an Order has no Payment source. """
order = self.create_order()
payment_method = ReceiptResponseView().get_payment_method(order)
self.assertEqual(payment_method, None)
def test_get_payment_method_source_type(self):
"""
Source Type name should be displayed as the Payment method
when the credit card wasn't used to purchase a product.
"""
order = self.create_order()
source = factories.SourceFactory(order=order)
payment_method = ReceiptResponseView().get_payment_method(order)
self.assertEqual(payment_method, source.source_type.name)
def test_get_payment_method_credit_card_purchase(self):
"""
Credit card type and Source label should be displayed as the Payment method
when a Credit card was used to purchase a product.
"""
order = self.create_order()
source = factories.SourceFactory(order=order, card_type='Dummy Card', label='Test')
payment_method = ReceiptResponseView().get_payment_method(order)
self.assertEqual(payment_method, '{} {}'.format(source.card_type, source.label))
@httpretty.activate
def test_get_receipt_for_existing_order(self):
""" Order owner should be able to see the Receipt Page."""
order = self._create_order_for_receipt(self.user)
response = self._get_receipt_response(order.number)
context_data = {
'payment_method': None,
'fire_tracking_events': False,
'display_credit_messaging': False,
}
self.assertEqual(response.status_code, 200)
self.assertDictContainsSubset(context_data, response.context_data)
@httpretty.activate
def test_get_receipt_for_existing_order_as_staff_user(self):
""" Staff users can preview Receipts for all Orders."""
staff_user = self.create_user(is_staff=True)
order = self._create_order_for_receipt(staff_user)
response = self._visit_receipt_page_with_another_user(order, staff_user)
context_data = {
'payment_method': None,
'fire_tracking_events': False,
'display_credit_messaging': False,
}
self.assertEqual(response.status_code, 200)
self.assertDictContainsSubset(context_data, response.context_data)
@httpretty.activate
def test_get_receipt_for_existing_order_user_not_owner(self):
""" Users that don't own the Order shouldn't be able to see the Receipt. """
other_user = self.create_user()
order = self._create_order_for_receipt(other_user)
response = self._visit_receipt_page_with_another_user(order, other_user)
context_data = {'order_history_url': self.site.siteconfiguration.build_lms_url('account/settings')}
self.assertEqual(response.status_code, 404)
self.assertDictContainsSubset(context_data, response.context_data)
@httpretty.activate
def test_order_data_for_credit_seat(self):
""" Ensure that the context is updated with Order data. """
order = self.create_order(credit=True)
self.mock_verification_status_api(
self.site,
self.user,
status=200,
is_verified=True
)
seat = order.lines.first().product
body = {'display_name': 'Hogwarts'}
response = self._get_receipt_response(order.number)
body['course_key'] = seat.attr.course_key
self.assertEqual(response.status_code, 200)
self.assertTrue(response.context_data['display_credit_messaging'])
import logging
import urllib
from babel.numbers import format_currency
from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.translation import get_language, to_locale
from edx_rest_api_client.client import EdxRestApiClient
from requests.exceptions import ConnectionError, Timeout
......@@ -42,15 +44,15 @@ def get_receipt_page_url(site_configuration, order_number=None):
str: Receipt page URL.
"""
if site_configuration.enable_otto_receipt_page:
return site_configuration.build_ecommerce_url('{base_url}{order_number}'.format(
base_url=settings.RECEIPT_PAGE_PATH,
order_number=order_number if order_number else ''
))
return site_configuration.build_lms_url(
'{base_url}{order_number}'.format(
base_url='/commerce/checkout/receipt',
order_number='?orderNum={}'.format(order_number) if order_number else ''
)
base_url = site_configuration.build_ecommerce_url(reverse('checkout:receipt'))
params = urllib.urlencode({'order_number': order_number}) if order_number else ''
else:
base_url = site_configuration.build_lms_url('/commerce/checkout/receipt')
params = urllib.urlencode({'orderNum': order_number}) if order_number else ''
return '{base_url}{params}'.format(
base_url=base_url,
params='?{params}'.format(params=params) if params else ''
)
......
......@@ -2,10 +2,10 @@
from __future__ import unicode_literals
from decimal import Decimal
import logging
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import RedirectView, TemplateView
......@@ -19,7 +19,7 @@ from ecommerce.extensions.checkout.utils import get_receipt_page_url
Applicator = get_class('offer.utils', 'Applicator')
Basket = get_model('basket', 'Basket')
logger = logging.getLogger(__name__)
Order = get_model('order', 'Order')
class FreeCheckoutView(EdxOrderPlacementMixin, RedirectView):
......@@ -50,7 +50,6 @@ class FreeCheckoutView(EdxOrderPlacementMixin, RedirectView):
)
order = self.place_free_order(basket)
receipt_path = get_receipt_page_url(
order_number=order.number,
site_configuration=order.site.siteconfiguration
......@@ -118,3 +117,84 @@ class CheckoutErrorView(TemplateView):
'payment_support_email': self.request.site.siteconfiguration.payment_support_email,
})
return context
class ReceiptResponseView(ThankYouView):
""" Handles behavior needed to display an order receipt. """
template_name = 'edx/checkout/receipt.html'
@method_decorator(csrf_exempt)
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
""" Customers should only be able to view their receipts when logged in. """
return super(ReceiptResponseView, self).dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
try:
return super(ReceiptResponseView, self).get(request, *args, **kwargs)
except Http404:
self.template_name = 'edx/checkout/receipt_not_found.html'
context = {
'order_history_url': request.site.siteconfiguration.build_lms_url('account/settings'),
}
return self.render_to_response(context=context, status=404)
def get_context_data(self, **kwargs):
context = super(ReceiptResponseView, self).get_context_data(**kwargs)
order = context[self.context_object_name]
context.update({
'payment_method': self.get_payment_method(order),
'fire_tracking_events': self.request.session.pop('fire_tracking_events', False),
'display_credit_messaging': self.order_contains_credit_seat(order),
})
context.update(self.get_order_verification_context(order))
return context
def get_object(self):
kwargs = {
'number': self.request.GET['order_number'],
'site': self.request.site,
}
user = self.request.user
if not user.is_staff:
kwargs['user'] = user
return get_object_or_404(Order, **kwargs)
def get_payment_method(self, order):
source = order.sources.first()
if source:
if source.card_type:
return '{type} {number}'.format(
type=source.get_card_type_display(),
number=source.label
)
return source.source_type.name
return None
def order_contains_credit_seat(self, order):
for line in order.lines.all():
if getattr(line.product.attr, 'credit_provider', None):
return True
return False
def get_order_verification_context(self, order):
context = {}
verified_course_id = None
# NOTE: Only display verification and credit completion details to the user who actually placed the order.
if self.request.user == order.user:
for line in order.lines.all():
product = line.product
if not verified_course_id and getattr(product.attr, 'id_verification_required', False):
verified_course_id = product.attr.course_key
if verified_course_id:
context.update({
'verified_course_id': verified_course_id,
'user_verified': self.request.user.is_verified(self.request.site),
})
return context
......@@ -443,7 +443,7 @@ class EnrollmentCodeFulfillmentModuleTests(CourseCatalogTestMixin, TestCase):
def setUp(self):
super(EnrollmentCodeFulfillmentModuleTests, self).setUp()
toggle_switch(ENROLLMENT_CODE_SWITCH, True)
self.site.siteconfiguration.enable_otto_receipt_page = True
self.toggle_ecommerce_receipt_page(True)
course = CourseFactory()
course.create_or_update_seat('verified', True, 50, self.partner, create_enrollment_code=True)
enrollment_code = Product.objects.get(product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME)
......
from django import template
from ecommerce.extensions.offer.utils import format_benefit_value
register = template.Library()
@register.filter(name='benefit_discount')
def benefit_discount(benefit):
"""
Format benefit value for display based on the benefit type.
Example:
'100%' if benefit.value == 100.00 and benefit.type == 'Percentage'
'$100.00' if benefit.value == 100.00 and benefit.type == 'Absolute'
Arguments:
benefit (Benefit): Voucher's Benefit.
Returns:
str: String value containing formatted benefit value and type.
"""
return format_benefit_value(benefit)
from django.template import Context, Template
from oscar.core.loading import get_model
from oscar.test.factories import BenefitFactory
from ecommerce.tests.testcases import TestCase
Benefit = get_model('offer', 'Benefit')
class OfferTests(TestCase):
def test_benefit_discount(self):
benefit = BenefitFactory(type=Benefit.PERCENTAGE, value=35.00)
template = Template(
"{% load offer_tags %}"
"{{ benefit|benefit_discount }}"
)
self.assertEqual(template.render(Context({'benefit': benefit})), '35%')
......@@ -31,7 +31,7 @@ def get_discount_percentage(discount_value, product_price):
Returns:
float: Discount percentage
"""
return discount_value / product_price * 100
return discount_value / product_price * 100 if product_price > 0 else 0.0
def get_discount_value(discount_percentage, product_price):
......
......@@ -2,22 +2,23 @@
from __future__ import unicode_literals
import six
from django.utils.translation import ugettext_lazy as _
CARD_TYPES = {
'american_express': {
'display_name': 'American Express',
'display_name': _('American Express'),
'cybersource_code': '003'
},
'discover': {
'display_name': 'Discover',
'display_name': _('Discover'),
'cybersource_code': '004'
},
'mastercard': {
'display_name': 'MasterCard',
'display_name': _('MasterCard'),
'cybersource_code': '002'
},
'visa': {
'display_name': 'Visa',
'display_name': _('Visa'),
'cybersource_code': '001'
},
}
......
......@@ -31,7 +31,7 @@ class CybersourceTests(CybersourceMixin, PaymentProcessorTestCaseMixin, TestCase
def setUp(self):
super(CybersourceTests, self).setUp()
self.site.siteconfiguration.enable_otto_receipt_page = True
self.toggle_ecommerce_receipt_page(True)
self.basket.site = self.site
@freeze_time('2016-01-01')
......
......@@ -171,7 +171,7 @@ class PaypalTests(PaypalMixin, PaymentProcessorTestCaseMixin, TestCase):
"""
Ensures that when the otto_receipt_page waffle switch is enabled, the processor uses the new receipt page.
"""
self.site.siteconfiguration.enable_otto_receipt_page = True
self.toggle_ecommerce_receipt_page(True)
assert self._get_receipt_url() == self.site.siteconfiguration.build_ecommerce_url(settings.RECEIPT_PAGE_PATH)
def test_switch_disabled_lms_url(self):
......
......@@ -42,7 +42,7 @@ class CybersourceNotifyViewTests(CybersourceMixin, PaymentEventsMixin, TestCase)
def setUp(self):
super(CybersourceNotifyViewTests, self).setUp()
self.site.siteconfiguration.enable_otto_receipt_page = True
self.toggle_ecommerce_receipt_page(True)
self.user = factories.UserFactory()
self.billing_address = self.make_billing_address()
......
......@@ -6,12 +6,11 @@ import six
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
from django.http import HttpResponse, JsonResponse
from django.http import JsonResponse, HttpResponse
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import FormView
from django.views.generic import View
from django.views.generic import FormView, View
from oscar.apps.partner import strategy
from oscar.apps.payment.exceptions import PaymentError, UserCancelled, TransactionDeclined
from oscar.core.loading import get_class, get_model
......
......@@ -27,12 +27,21 @@ class RefundTestMixin(CourseCatalogTestMixin):
self.course, __ = Course.objects.get_or_create(id=u'edX/DemoX/Demo_Course', name=u'edX Demó Course')
self.honor_product = self.course.create_or_update_seat('honor', False, 0, self.partner)
self.verified_product = self.course.create_or_update_seat('verified', True, 10, self.partner)
self.credit_product = self.course.create_or_update_seat(
'credit',
True,
100,
self.partner,
credit_provider='HGW'
)
def create_order(self, user=None, multiple_lines=False, free=False, status=ORDER.COMPLETE):
def create_order(self, user=None, credit=False, multiple_lines=False, free=False, status=ORDER.COMPLETE):
user = user or self.user
basket = BasketFactory(owner=user)
if multiple_lines:
if credit:
basket.add_product(self.credit_product)
elif multiple_lines:
basket.add_product(self.verified_product)
basket.add_product(self.honor_product)
elif free:
......
/**
* Basket page scripts.
**/
define([
'jquery'
],
function ($
) {
'use strict';
var onReady = function() {
var el = $('#receipt-container'),
order_id = el.data('order-id'),
fire_tracking_events = el.data('fire-tracking-events'),
total_amount = el.data('total-amount'),
currency = el.data('currency');
if(order_id && fire_tracking_events){
trackPurchase(order_id, total_amount, currency);
}
},
trackPurchase = function(order_id, total_amount, currency) {
window.analytics.track('Completed Purchase', {
orderId: order_id,
total: total_amount,
currency: currency
});
};
$(document).ready(onReady);
return {
onReady: onReady,
};
}
);
define([
'pages/receipt_page',
'utils/analytics_utils'
],
function (ReceiptPage,
AnalyticsUtils
) {
'use strict';
describe('Receipt Page', function () {
beforeEach(function () {
$('<script type="text/javascript">var initModelData = {};</script>').appendTo('body');
$(
'<div id="receipt-container" data-fire-tracking-events="true" data-order-id="ORDER_ID"></div>'
).appendTo('body');
AnalyticsUtils.analyticsSetUp();
/* jshint ignore:start */
// jscs:disable
window.analytics = window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";}
// jscs:enable
/* jshint ignore:end */
});
describe('onReady', function () {
it('should trigger track purchase', function () {
spyOn(window.analytics, 'track');
ReceiptPage.onReady();
expect(window.analytics.track).toHaveBeenCalled();
});
});
});
}
);
......@@ -42,22 +42,3 @@ a {
background: #fcfcfc;
box-shadow: 0 1px 2px 1px rgba(167, 164, 164, 0.25);
}
.depth--3 {
background: #34383a;
text-align: center;
.copy {
color: #e7e6e6;
@include container-fixed;
@media (min-width: $screen-sm-min) {
width: $container-sm;
}
@media (min-width: $screen-md-min) {
width: $container-md;
}
@media (min-width: $screen-lg-min) {
width: $container-lg;
}
}
}
......@@ -35,5 +35,6 @@
@import 'views/coupon_admin';
@import 'views/coupon_offer';
@import 'views/error';
@import 'views/receipt';
@import "default";
.receipt {
background-color: #ffffff;
font-family: $font-family-sans-serif;
line-height: 1em;
.action-primary {
background: palette(primary, base) none repeat scroll 0 0;
border: 0 none;
box-shadow: 0 2px 1px 0 palette(primary, dark);
color: #fff;
text-decoration: none;
&:hover, &:focus {
background: palette(primary, accent) none repeat scroll 0 0;
color: #fff;
text-decoration: none;
}
}
.content-container {
padding: 15px 30px 40px;
}
.copy {
> div {
margin-bottom: 15px;
line-height: 22px;
}
.billing-address {
margin-top: 35px;
margin-bottom: 35px;
}
}
#error-container {
padding: 15px;
background: #fbf4f7 none repeat scroll 0 0;
border-bottom: 4px solid #b20610;
.error-message {
background: inherit;
.title {
margin-bottom: 5px;
font-weight: 600;
}
}
}
.info-item {
overflow: hidden;
}
.payment-info {
margin-bottom: spacing-vertical(small);
}
.is-ready {
background: #e8f6fc none repeat scroll 0 0;
}
.nav-link {
border-bottom: none;
text-decoration: none !important;
&:active, &:hover, &:focus {
border-bottom: 1px dotted #0079bc;
}
}
.nav-wizard {
padding: 15px 20px;
margin: 0;
overflow: hidden;
border-top: 4px solid rgb(255, 192, 31);
box-shadow: 0 1px 1px 2px rgba(0, 0, 0, 0.2);
.header {
margin: 0;
padding: 10px 0;
color: #434242;
font-size: 18px;
font-weight: bold;
}
.message {
color: #434242;
font-weight: 500;
line-height: 1.4em;
}
#verify_now {
margin-bottom: 10px;
}
#verify_choices {
margin-bottom: 10px;
padding-top: 10px;
text-align: center;
}
#verify_now_button {
display: block;
width: 100%;
margin: 0 auto;
padding: 13px 35px;
text-align: center;
&:hover {
text-decoration: none;
}
}
}
.confirm-message {
font-size: 18px;
}
h2 {
margin-bottom: 10px;
color: rgb(0, 121, 188);
}
p {
margin: 0;
}
.thank-you {
font-size: 38px;
}
.price {
padding-right: 0;
font-size: 16px;
font-weight: bold;
text-align: right;
}
#dashboard-link {
margin: 20px;
text-align: center;
.dashboard-link {
font-size: 18px;
}
}
.dashboard-link {
line-height: 20px;
font-size: 13px;
}
.order-summary {
dt {
font-weight: 500;
}
dd {
margin-bottom: 20px;
color: rgb(29, 29, 29);
font-size: 18px;
}
}
.order-headers {
color: rgb(0, 121, 188);
font-size: 22px;
}
.order-line-data {
color: rgb(29, 29, 29);
font-size: 20px;
> td {
padding-bottom: 1.5em;
padding-top: 1.25em;
line-height: 1.5em !important;
}
}
p, address {
color: #6f7074;
}
/* Styling for the provider area; uses edX Pattern Library utils */
.report-receipt-provider {
@include clearfix;
margin: 0 0 20px 0;
padding: 15px 20px;
border: 1px solid rgb(221,221,221);
border-radius: 3px;
box-shadow: 0 1px 2px 1px rgba(0,0,0,0.1);
background: rgb(255,255,255);
.provider-wrapper {
@include float(left);
padding: 10px;
.provider-info {
margin-bottom: 20px;
font-weight: 600;
}
}
.provider-buttons-logos {
padding: 10px;
@include text-align(center);
.provider-logo img {
max-width: 160px;
margin-bottom: 10px;
}
}
}
.summary {
td:first-child {
padding: 8px 0 0 0;
}
}
.summary-description {
font-weight: bold;
}
.table {
dl {
margin: 0;
}
.header, .order-line-data {
dt, dd {
padding: 10px 10px 5px 10px;
&:not(:last-child) {
float: left;
border-right: 2px solid rgb(235, 235, 235);
}
&:last-child {
display: inline-block;
width: 15%;
}
}
dt:last-child {
text-align: center;
}
dd {
height: 100px;
}
}
.header {
background-color: rgb(249, 249, 249);
dt {
border-bottom: 2px solid rgb(235, 235, 235);
font-size: 16px;
&:first-child {
width: 10%;
}
&:nth-child(2) {
width: 75%;
}
}
}
.order-line-data {
&:not(:last-child) {
border-bottom: 2px solid rgb(235, 235, 235);
}
&:last-child {
border-bottom: 4px solid;
}
dd {
font-size: 16px;
&:nth-child(2) {
width: 10%;
}
&:nth-child(4) {
width: 75%;
}
&.line-price {
padding-right: 5%;
}
}
}
.order-total {
margin-left: 60%;
padding: 20px 0;
&:not(:last-child) {
border-bottom: 2px solid rgb(235, 235, 235);
}
.description {
width: 62.5%;
font-weight: bold;
p {
padding-top: 5px;
font-size: 15px;
font-weight: normal;
}
}
.description, .price {
display: inline-block;
}
.price {
float: right;
margin-right: 12%;
color: black;
}
}
}
.wrapper-content-main {
margin-bottom: 20px;
}
}
/* Small devices (tablets, 992px and lower) */
@media (max-width: $screen-sm-max) {
.receipt {
.sr {
position: static;
width: auto;
height: auto;
font-size: 16px;
}
.thank-you {
text-align: center;
}
.order-summary {
border-top: 2px solid rgb(235, 235, 235);
border-bottom: 2px solid rgb(235, 235, 235);
padding: 10px 0;
background-color: rgb(249, 249, 249);
dt, dd {
padding-left: 10px;
font-size: 16px;
}
dt {
float: left;
color: black;
font-weight: bold;
}
dd {
display: flex;
}
}
.table {
border-top: 4px solid rgb(235, 235, 235);
.header {
display: none;
dt {
border-bottom: none;
}
}
.order-line-data {
padding-top: 10px;
&:not(:last-child) {
border-bottom: none;
}
&:last-child {
border-bottom: 2px solid;
}
dt, dd {
&:not(:last-child) {
float: none;
border-right: none;
}
}
dd {
display: inline-block;
height: auto;
&:nth-child(2), &:nth-child(4) {
width: auto;
}
&.course-description {
padding: 0 10px;
}
&.line-price {
font-weight: normal;
}
}
dt {
display: inline;
float: left;
}
.quantity, .line-price {
display: inline;
}
.course-description {
display: block;
width: auto;
}
}
.order-total {
margin-left: 0;
padding: 20px 10px;
.price {
margin-right: 0;
}
}
}
}
}
{% extends 'edx/base.html' %}
{% load core_extras %}
{% load currency_filters %}
{% load i18n %}
{% load offer_tags %}
{% load staticfiles %}
{% block title %}
{% blocktrans with order_number=order.number %}
Receipt for {{ order_number }}
{% endblocktrans %}
{% endblock title %}
{% block javascript %}
<script src="{% static 'js/pages/receipt_page.js' %}"></script>
{% endblock javascript %}
{% block navbar %}
{% include 'edx/partials/_student_navbar.html' %}
{% endblock navbar %}
{% block content %}
<div id="receipt-container"
class="receipt container content-container"
data-currency="{{ order.currency }}"
data-fire-tracking-events="{{ fire_tracking_events|yesno:"true,false" }}"
data-order-id="{{ order.number }}"
data-total-amount="{{ order.total_incl_tax }}">
<h2 class="thank-you">{% trans "Thank you for your order!" %}</h2>
<div class="list-info">
<div class="info-item payment-info row">
<div class="copy col-md-8">
<div class="confirm-message">
{% captureas link_start %}
<a href="mailto:{{ order.user.email }}">
{% endcaptureas %}
{% blocktrans with email=order.user.email link_end="</a>" %}
Your order is complete. If you need a receipt, you can print this page.
You will also receive a confirmation message with this information at
{{ link_start }}{{ email }}{{ link_end }}.
{% endblocktrans %}
</div>
{% if order.billing_address %}
<address class="billing-address">
{% for field in order.billing_address.active_address_fields %}
{{ field }}<br/>
{% endfor %}
</address>
{% endif %}
</div>
<div class="order-headers order-summary col-md-4">
<dl>
<dt>{% trans "Order Number:" %}</dt>
<dd>{{ order.number }}</dd>
{% if payment_method %}
<dt>{% trans "Payment Method:" %}</dt>
<dd>{{ payment_method }}</dd>
{% endif %}
<dt>{% trans "Order Date:" %}</dt>
<dd>{{ order.date_placed|date:"E d, Y" }}</dd>
</dl>
</div>
</div>
<h2>{% trans "Order Information" %}</h2>
<div class="table">
<dl class="order-lines">
<div class="header">
<dt aria-hidden="true">{% trans "Quantity" %}</dt>
<dt aria-hidden="true">{% trans "Description" %}</dt>
<dt aria-hidden="true">{% trans "Item Price" %}</dt>
</div>
{% for line in order.lines.all %}
<div class="order-line-data">
<dt class="quantity sr">{% trans "Quantity:" %}</dt>
<dd class="quantity">{{ line.quantity }}</dd>
<dt class="course-description sr">{% trans "Description:" %}</dt>
<dd class="course-description">
<span>{{ line.description }}</span>
<p>{{ line.product.course.id|course_organization }}</p>
</dd>
<dt class="line-price sr">{% trans "Item Price:" %}</dt>
<dd class="line-price price">{{ line.line_price_before_discounts_incl_tax|currency:order.currency }}</dd>
</div>
{% endfor %}
</dl>
<div class="order-total">
<div class="description">{% trans "Subtotal:" %}</div>
<div class="price">{{ order.total_before_discounts_incl_tax|currency:order.currency }}</div>
</div>
{% if order.total_discount_incl_tax %}
{% for voucher in order.basket.vouchers.all %}
<div class="order-total">
<div class="description">
<span>{% trans "Coupon applied:" %}</span>
<p>
{% blocktrans with voucher_code=voucher.code voucher_discount_amount=voucher.benefit|benefit_discount %}
{{ voucher_code }} {{ voucher_discount_amount }} off
{% endblocktrans %}
</p>
</div>
<div class="price">
-{{ order.total_discount_incl_tax|currency:order.currency }}
</div>
</div>
{% endfor %}
{% endif %}
<div class="order-total">
<div class="description">{% trans "Total:" %}</div>
<div class="price">{{ order.total_incl_tax|currency:order.currency }}</div>
</div>
</div>
{% if display_credit_messaging %}
{% captureas link_start %}
<a href="{{ lms_dashboard_url }}">
{% endcaptureas %}
<div class="nav-wizard row">
<p class="header">{% trans "Get Your Course Credit" %}</p>
<p class="message">
{% blocktrans with link_end="</a>" %}
To receive academic credit for this course, you must apply for credit at the organization that offers the credit.
You can find a link to the organization’s website on your {{ link_start }}dashboard{{ link_end }}, next to the course name.
{% endblocktrans %}
</p>
</div>
{% endif %}
</div>
{% if verified_course_id and not user_verified %}
<div class="nav-wizard row">
{% include 'oscar/checkout/_verification_data.html' with course_id=verified_course_id %}
</div>
{% else %}
<div id="dashboard-link">
<a class="dashboard-link nav-link" href="{{ lms_dashboard_url }}">
{% trans "Go to Dashboard" %}
</a>
</div>
{% endif %}
</div>
</div>
{% endblock content %}
{% extends 'edx/base.html' %}
{% load core_extras %}
{% load i18n %}
{% block title %}
{% trans "Order Not Found" %}
{% endblock title %}
{% block navbar %}
{% include 'edx/partials/_student_navbar.html' %}
{% endblock navbar %}
{% block content %}
<div class="receipt">
<div id="error-container">
<div class="error-message container">
<h3 class="title">
<span class="sr">{% blocktrans %} {{ error_summary }} {% endblocktrans %}</span>
{{ error_summary }}
</h3>
<div class="copy">
<p>{% trans "The specified order could not be located. Please ensure that the URL is correct, and try again." %}</p>
<br/>
</div>
<div class="msg">
<p>
{% captureas link_start %}
<a href="{{ order_history_url }}">
{% endcaptureas %}
{% blocktrans with link_end="</a>" %}
You may also view your previous orders on the {{ link_start }}Account Settings{{ link_end }}
page.
{% endblocktrans %}
</p>
</div>
</div>
</div>
</div>
{% endblock content %}
{% load i18n %}
<div class="col-md-9">
<p class="header">{% trans "Verify Your Identity" %}</p>
<p class="message">
{% blocktrans %}
To receive a verified certificate, you have to verify your identity using your <strong>webcam</strong> and
an <strong>official government-issued photo identification</strong> before the verification deadline for this
course.
{% endblocktrans %}
</p>
</div>
<div id="verify_choices" class="right col-md-3">
<div id="verify_now">
<a id="verify_now_button"
class="next action-primary"
data-track-category="verification"
data-track-event="edx.bi.user.verification.immediate"
data-track-type="click"
href="{{ verify_url }}{{ course_id }}/">
{% trans "Verify Now" %}
</a>
</div>
<a id="verify_later_button"
class="dashboard-link"
data-track-category="verification"
data-track-event="edx.bi.user.verification.immediate"
data-track-type="click"
href="{{ lms_dashboard_url }}">
{% trans "Go to my dashboard and verify later" %}
</a>
</div>
......@@ -22,6 +22,5 @@
{{ payment_support_email }}{{ end_link }}.
{% endblocktrans %}
{% endwith %}</p>
</div>
{% endblock content %}
......@@ -284,6 +284,12 @@ class SiteMixin(object):
return token
def toggle_ecommerce_receipt_page(self, enable_otto_receipt_page):
""" Enables Ecommerce Receipt Page. """
site_configuration = self.site.siteconfiguration
site_configuration.enable_otto_receipt_page = enable_otto_receipt_page
site_configuration.save()
class TestServerUrlMixin(object):
def get_full_url(self, path, site=None):
......
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