From 5b3f5ee85a0f39c3c208e35e476700e689f9fe3f Mon Sep 17 00:00:00 2001 From: Nimisha Asthagiri Date: Thu, 28 May 2020 09:46:12 -0400 Subject: [PATCH] commerce app: remove code related to shoppingcart (#23974) Part of DEPR-43 - https://openedx.atlassian.net/browse/DEPR-43 Co-authored-by: Jeremy Bowman --- lms/djangoapps/commerce/tests/test_views.py | 132 ------ lms/djangoapps/commerce/urls.py | 16 - lms/djangoapps/commerce/views.py | 126 ------ lms/static/js/commerce/views/receipt_view.js | 382 ------------------ .../fixtures/commerce/checkout_receipt.html | 28 -- .../js/spec/commerce/receipt_view_spec.js | 338 ---------------- lms/static/lms/js/spec/main.js | 6 - lms/templates/commerce/checkout_cancel.html | 15 - lms/templates/commerce/checkout_error.html | 17 - lms/templates/commerce/checkout_receipt.html | 62 --- lms/templates/commerce/provider.underscore | 32 -- lms/templates/commerce/receipt.underscore | 109 ----- lms/urls.py | 1 - 13 files changed, 1264 deletions(-) delete mode 100644 lms/djangoapps/commerce/urls.py delete mode 100644 lms/djangoapps/commerce/views.py delete mode 100644 lms/static/js/commerce/views/receipt_view.js delete mode 100644 lms/static/js/fixtures/commerce/checkout_receipt.html delete mode 100644 lms/static/js/spec/commerce/receipt_view_spec.js delete mode 100644 lms/templates/commerce/checkout_cancel.html delete mode 100644 lms/templates/commerce/checkout_error.html delete mode 100644 lms/templates/commerce/checkout_receipt.html delete mode 100644 lms/templates/commerce/provider.underscore delete mode 100644 lms/templates/commerce/receipt.underscore diff --git a/lms/djangoapps/commerce/tests/test_views.py b/lms/djangoapps/commerce/tests/test_views.py index 47fd4240d7..22637c8080 100644 --- a/lms/djangoapps/commerce/tests/test_views.py +++ b/lms/djangoapps/commerce/tests/test_views.py @@ -1,18 +1,6 @@ """ Tests for commerce views. """ - -import json - -import ddt -import mock -from django.urls import reverse - -from course_modes.models import CourseMode -from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme -from student.models import CourseEnrollment from student.tests.factories import UserFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory class UserMixin(object): @@ -25,123 +13,3 @@ class UserMixin(object): def _login(self): """ Log into LMS. """ self.client.login(username=self.user.username, password='test') - - -@ddt.ddt -class ReceiptViewTests(UserMixin, ModuleStoreTestCase): - """ Tests for the receipt view. """ - - def setUp(self): - """ - Add a user and a course - """ - super(ReceiptViewTests, self).setUp() - self.user = UserFactory() - self.client.login(username=self.user.username, password='test') - self.course = CourseFactory.create( - org='edX', - course='900', - run='test_run' - ) - - def test_login_required(self): - """ The view should redirect to the login page if the user is not logged in. """ - self.client.logout() - response = self.client.post(reverse('commerce:checkout_receipt')) - self.assertEqual(response.status_code, 302) - - def post_to_receipt_page(self, post_data): - """ DRY helper """ - response = self.client.post(reverse('commerce:checkout_receipt'), params={'basket_id': 1}, data=post_data) - self.assertEqual(response.status_code, 200) - return response - - def test_user_verification_status_success(self): - """ - Test user verification status. If the user enrollment for the course belongs to verified modes - e.g. Verified, Professional then verification is required. - """ - # Enroll as verified in the course with the current user. - CourseEnrollment.enroll(self.user, self.course.id, mode=CourseMode.VERIFIED) - response = self.client.get( - reverse('commerce:user_verification_status'), data={'course_id': str(self.course.id)} - ) - json_data = json.loads(response.content.decode('utf-8')) - self.assertEqual(json_data['is_verification_required'], True) - - # Enroll as honor in the course with the current user. - CourseEnrollment.enroll(self.user, self.course.id, mode=CourseMode.HONOR) - response = self.client.get( - reverse('commerce:user_verification_status'), data={'course_id': str(self.course.id)} - ) - json_data = json.loads(response.content.decode('utf-8')) - self.assertEqual(json_data['is_verification_required'], False) - - def test_user_verification_status_failure(self): - """ - Test user verification status failure. View should required HttpResponseBadRequest 400 if course id is missing. - """ - response = self.client.get(reverse('commerce:user_verification_status')) - self.assertEqual(response.status_code, 400) - - @ddt.data('decision', 'reason_code', 'signed_field_names', None) - def test_is_cybersource(self, post_key): - """ - Ensure the view uses three specific POST keys to detect a request initiated by Cybersource. - """ - self._login() - post_data = {'decision': 'REJECT', 'reason_code': '200', 'signed_field_names': 'dummy'} - if post_key is not None: - # a key will be missing; we will not expect the receipt page to handle a cybersource decision - del post_data[post_key] - expected_pattern = r"(\s+)Receipt" - else: - expected_pattern = r"<title>(\s+)Payment Failed" - response = self.post_to_receipt_page(post_data) - self.assertRegex(response.content.decode('utf-8'), expected_pattern) - - @ddt.data('ACCEPT', 'REJECT', 'ERROR') - def test_cybersource_decision(self, decision): - """ - Ensure the view renders a page appropriately depending on the Cybersource decision. - """ - self._login() - post_data = {'decision': decision, 'reason_code': '200', 'signed_field_names': 'dummy'} - expected_pattern = r"<title>(\s+)Receipt" if decision == 'ACCEPT' else r"<title>(\s+)Payment Failed" - response = self.post_to_receipt_page(post_data) - self.assertRegex(response.content.decode('utf-8'), expected_pattern) - - @ddt.data(True, False) - @mock.patch('lms.djangoapps.commerce.views.is_user_payment_error') - def test_cybersource_message(self, is_user_message_expected, mock_is_user_payment_error): - """ - Ensure that the page displays the right message for the reason_code (it - may be a user error message or a system error message). - """ - mock_is_user_payment_error.return_value = is_user_message_expected - self._login() - response = self.post_to_receipt_page({'decision': 'REJECT', 'reason_code': '99', 'signed_field_names': 'dummy'}) - self.assertTrue(mock_is_user_payment_error.called) - self.assertTrue(mock_is_user_payment_error.call_args[0][0], '99') - - user_message = "There was a problem with this transaction" - system_message = "A system error occurred while processing your payment" - self.assertRegex( - response.content.decode('utf-8'), - user_message if is_user_message_expected else system_message - ) - self.assertNotRegex( - response.content.decode('utf-8'), - user_message if not is_user_message_expected else system_message - ) - - @with_comprehensive_theme("edx.org") - def test_hide_nav_header(self): - self._login() - post_data = {'decision': 'ACCEPT', 'reason_code': '200', 'signed_field_names': 'dummy'} - response = self.post_to_receipt_page(post_data) - - # Verify that the header navigation links are hidden for the edx.org version - self.assertNotContains(response, "How it Works") - self.assertNotContains(response, "Find courses") - self.assertNotContains(response, "Schools & Partners") diff --git a/lms/djangoapps/commerce/urls.py b/lms/djangoapps/commerce/urls.py deleted file mode 100644 index b7b31fb6c8..0000000000 --- a/lms/djangoapps/commerce/urls.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Defines the URL routes for this app. -""" - - -from django.conf.urls import url - -from . import views - -app_name = 'commerce' -urlpatterns = [ - url(r'^checkout/cancel/$', views.checkout_cancel, name='checkout_cancel'), - url(r'^checkout/error/$', views.checkout_error, name='checkout_error'), - url(r'^checkout/receipt/$', views.checkout_receipt, name='checkout_receipt'), - url(r'^checkout/verification_status/$', views.user_verification_status, name='user_verification_status'), -] diff --git a/lms/djangoapps/commerce/views.py b/lms/djangoapps/commerce/views.py deleted file mode 100644 index 1a10ceae6e..0000000000 --- a/lms/djangoapps/commerce/views.py +++ /dev/null @@ -1,126 +0,0 @@ -""" Commerce views. """ - - -import logging - -from django.conf import settings -from django.contrib.auth.decorators import login_required -from django.core.cache import cache -from django.http import HttpResponseBadRequest -from django.utils.translation import ugettext as _ -from django.views.decorators.csrf import csrf_exempt -from django.views.decorators.http import require_http_methods -from opaque_keys.edx.locator import CourseLocator - -from course_modes.models import CourseMode -from edxmako.shortcuts import render_to_response -from lms.djangoapps.verify_student.services import IDVerificationService -from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers -from openedx.core.djangoapps.theming.helpers import is_request_in_themed_site -from openedx.core.djangolib.markup import HTML -from shoppingcart.processors.CyberSource2 import is_user_payment_error -from student.models import CourseEnrollment -from util.json_request import JsonResponse - -from .models import CommerceConfiguration - -log = logging.getLogger(__name__) - - -@csrf_exempt -def checkout_cancel(_request): - """ Checkout/payment cancellation view. """ - context = { - 'payment_support_email': configuration_helpers.get_value( - 'payment_support_email', settings.PAYMENT_SUPPORT_EMAIL, - ) - } - return render_to_response("commerce/checkout_cancel.html", context) - - -@csrf_exempt -def checkout_error(_request): - """ Checkout/payment error view. """ - context = { - 'payment_support_email': configuration_helpers.get_value( - 'payment_support_email', settings.PAYMENT_SUPPORT_EMAIL, - ) - } - return render_to_response("commerce/checkout_error.html", context) - - -@csrf_exempt -@login_required -def checkout_receipt(request): - """ Receipt view. """ - - page_title = _('Receipt') - is_payment_complete = True - payment_support_email = configuration_helpers.get_value('payment_support_email', settings.PAYMENT_SUPPORT_EMAIL) - payment_support_link = HTML(u'<a href=\"mailto:{email}\">{email}</a>').format(email=payment_support_email) - - is_cybersource = all(k in request.POST for k in ('signed_field_names', 'decision', 'reason_code')) - if is_cybersource and request.POST['decision'] != 'ACCEPT': - # Cybersource may redirect users to this view if it couldn't recover - # from an error while capturing payment info. - is_payment_complete = False - page_title = _('Payment Failed') - reason_code = request.POST['reason_code'] - # if the problem was with the info submitted by the user, we present more detailed messages. - if is_user_payment_error(reason_code): - error_summary = _("There was a problem with this transaction. You have not been charged.") - error_text = _( - "Make sure your information is correct, or try again with a different card or another form of payment." - ) - else: - error_summary = _("A system error occurred while processing your payment. You have not been charged.") - error_text = _("Please wait a few minutes and then try again.") - for_help_text = _(u"For help, contact {payment_support_link}.").format(payment_support_link=payment_support_link) - else: - # if anything goes wrong rendering the receipt, it indicates a problem fetching order data. - error_summary = _("An error occurred while creating your receipt.") - error_text = None # nothing particularly helpful to say if this happens. - for_help_text = _( - u"If your course does not appear on your dashboard, contact {payment_support_link}." - ).format(payment_support_link=payment_support_link) - - commerce_configuration = CommerceConfiguration.current() - # user order cache should be cleared when a new order is placed - # so user can see new order in their order history. - if is_payment_complete and commerce_configuration.enabled and commerce_configuration.is_cache_enabled: - cache_key = commerce_configuration.CACHE_KEY + '.' + str(request.user.id) - cache.delete(cache_key) - - context = { - 'page_title': page_title, - 'is_payment_complete': is_payment_complete, - 'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME), - 'verified': IDVerificationService.user_has_valid_or_pending(request.user), - 'error_summary': error_summary, - 'error_text': error_text, - 'for_help_text': for_help_text, - 'payment_support_email': payment_support_email, - 'username': request.user.username, - 'nav_hidden': True, - 'is_request_in_themed_site': is_request_in_themed_site() - } - return render_to_response('commerce/checkout_receipt.html', context) - - -@require_http_methods(["GET"]) -@login_required -def user_verification_status(request): - """ - Check for user verification status. - :return 'True' if the user enrollment for the course belongs to verified modes e.g. Verified, Professional. - """ - course_id = request.GET.get('course_id', None) - - if course_id is None: - return HttpResponseBadRequest() - - course_key = CourseLocator.from_string(course_id) - enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) - is_verification_required = enrollment_mode in CourseMode.VERIFIED_MODES - - return JsonResponse({'is_verification_required': is_verification_required}) diff --git a/lms/static/js/commerce/views/receipt_view.js b/lms/static/js/commerce/views/receipt_view.js deleted file mode 100644 index 9e377d7e40..0000000000 --- a/lms/static/js/commerce/views/receipt_view.js +++ /dev/null @@ -1,382 +0,0 @@ -/** - * View for the receipt page. - */ - -/* globals _, Backbone */ -var edx = edx || {}; - -(function($, _, Backbone) { - 'use strict'; - - edx.commerce = edx.commerce || {}; - - edx.commerce.ReceiptView = Backbone.View.extend({ - useEcommerceApi: true, - ecommerceBasketId: null, - ecommerceOrderNumber: null, - - initialize: function() { - this.ecommerceBasketId = $.url('?basket_id'); - this.ecommerceOrderNumber = $.url('?orderNum'); - this.useEcommerceApi = this.ecommerceBasketId || this.ecommerceOrderNumber; - _.bindAll(this, 'renderReceipt', 'renderError', 'getProviderData', 'renderProvider', 'getCourseData'); - - this.render(); - }, - - /** - * @param product - * @returns {bool} True if product requires user to be verified by Software Secure. - */ - requiresVerification: function(product) { - function getAttribute(attribute, defaultValue) { - var attr = _.findWhere(product.attribute_values, {name: attribute}); - if (!attr) { - return defaultValue; - } - return attr.value; - } - - return getAttribute('id_verification_required', true); - }, - - renderReceipt: function(data) { - var templateHtml = $('#receipt-tpl').html(), - self = this, - context = { - platformName: this.$el.data('platform-name'), - verified: this.$el.data('verified').toLowerCase() === 'true', - is_request_in_themed_site: this.$el.data('is-request-in-themed-site').toLowerCase() === 'true' - }, - providerId, - courseRequiresVerification; - - // True if any of the courses bought requires verification - courseRequiresVerification = _.any(data.lines, function(line) { - return this.requiresVerification(line.product); - }, this); - - // Add the receipt info to the template context - this.courseKey = this.getOrderCourseKey(data); - this.username = this.$el.data('username'); - $.ajax({ - type: 'GET', - url: '/commerce/checkout/verification_status/', - data: {course_id: this.courseKey} - }).success(function(response) { - _.extend(context, { - receipt: self.receiptContext(data), - courseKey: self.courseKey, - is_verification_required: response.is_verification_required && courseRequiresVerification - }); - - self.$el.html(_.template(templateHtml)(context)); - - self.trackLinks(); - - self.trackPurchase(data); - - self.renderCourseNamePlaceholder(self.courseKey); - - self.renderUserFullNamePlaceholder(self.username); - - providerId = self.getCreditProviderId(data); - if (providerId) { - self.getProviderData(providerId).then(self.renderProvider, self.renderError); - } - }).error(function() { - self.renderError(); - }); - }, - renderCourseNamePlaceholder: function(courseId) { - // Display the course Id or name (if available) in the placeholder - var $courseNamePlaceholder = $('.course_name_placeholder'); - $courseNamePlaceholder.text(courseId); - - this.getCourseData(courseId).then(function(responseData) { - $courseNamePlaceholder.text(responseData.name); - }); - }, - renderUserFullNamePlaceholder: function(username) { - var userModel = Backbone.Model.extend({ - urlRoot: '/api/user/v1/accounts/', - url: function() { - return this.urlRoot + this.id; - } - }); - this.user = new userModel({id: username}); - this.user.fetch({success: function(userData) { - $('.full_name_placeholder').text(userData.get('name')); - }}); - }, - renderProvider: function(context) { - var templateHtml = $('#provider-tpl').html(), - providerDiv = this.$el.find('#receipt-provider'); - context.course_key = this.courseKey; - context.username = this.username; - context.platformName = this.$el.data('platform-name'); - providerDiv.html(_.template(templateHtml)(context)).removeClass('hidden'); - }, - - renderError: function() { - // Display an error - $('#error-container').removeClass('hidden'); - }, - - trackPurchase: function(order) { - window.analytics.track('Completed Purchase', { - orderId: order.number, - total: order.total_excl_tax, - currency: order.currency - }); - }, - - render: function() { - var self = this, - orderId = this.ecommerceOrderNumber || this.ecommerceBasketId || $.url('?payment-order-num'); - - if (orderId && this.$el.data('is-payment-complete') === 'True') { - // Get the order details - self.$el.removeClass('hidden'); - self.getReceiptData(orderId).then(self.renderReceipt, self.renderError); - } else { - self.renderError(); - } - }, - - trackLinks: function() { - var $verifyNowButton = $('#verify_now_button'), - $verifyLaterButton = $('#verify_later_button'); - - // Track a virtual pageview, for easy funnel reconstruction. - window.analytics.page('payment', 'receipt'); - - // Track the user's decision to verify immediately - window.analytics.trackLink($verifyNowButton, 'edx.bi.user.verification.immediate', { - category: 'verification' - }); - - // Track the user's decision to defer their verification - window.analytics.trackLink($verifyLaterButton, 'edx.bi.user.verification.deferred', { - category: 'verification' - }); - }, - - /** - * Retrieve receipt data from Oscar (via LMS). - * @param {string} orderId Identifier of the order that was purchased. - * @return {object} JQuery Promise. - */ - getReceiptData: function(orderId) { - var urlFormat = '/shoppingcart/receipt/{orderId}/'; - - if (this.ecommerceOrderNumber) { - urlFormat = '/api/commerce/v1/orders/{orderId}/'; - } else if (this.ecommerceBasketId) { - urlFormat = '/api/commerce/v0/baskets/{orderId}/order/'; - } - - return $.ajax({ - url: edx.StringUtils.interpolate(urlFormat, {orderId: orderId}), - type: 'GET', - dataType: 'json' - }).retry({times: 5, timeout: 2000, statusCodes: [404]}); - }, - /** - * Retrieve credit provider data from LMS. - * @param {string} providerId The providerId of the credit provider. - * @return {object} JQuery Promise. - */ - getProviderData: function(providerId) { - var providerUrl = '/api/credit/v1/providers/{providerId}/'; - - return $.ajax({ - url: edx.StringUtils.interpolate(providerUrl, {providerId: providerId}), - type: 'GET', - dataType: 'json', - contentType: 'application/json', - headers: { - 'X-CSRFToken': $.cookie('csrftoken') - } - }).retry({times: 5, timeout: 2000, statusCodes: [404]}); - }, - /** - * Retrieve course data from LMS. - * @param {string} courseId The courseId of the course. - * @return {object} JQuery Promise. - */ - getCourseData: function(courseId) { - var courseDetailUrl = '/api/courses/v1/courses/{courseId}/'; - return $.ajax({ - url: edx.StringUtils.interpolate(courseDetailUrl, {courseId: courseId}), - type: 'GET', - dataType: 'json' - }); - }, - - /** - * Construct the template context from data received - * from the E-Commerce API. - * - * @param {object} order Receipt data received from the server - * @return {object} Receipt template context. - */ - receiptContext: function(order) { - var self = this, - receiptContext; - - if (this.useEcommerceApi) { - receiptContext = { - orderNum: order.number, - currency: order.currency, - purchasedDatetime: order.date_placed, - totalCost: self.formatMoney(order.total_excl_tax), - isRefunded: false, - items: [], - billedTo: null - }; - - if (order.billing_address) { - receiptContext.billedTo = { - firstName: order.billing_address.first_name, - lastName: order.billing_address.last_name, - city: order.billing_address.city, - state: order.billing_address.state, - postalCode: order.billing_address.postcode, - country: order.billing_address.country - }; - } - - receiptContext.items = _.map( - order.lines, - function(line) { - return { - lineDescription: line.description, - cost: self.formatMoney(line.line_price_excl_tax) - }; - } - ); - } else { - receiptContext = { - orderNum: order.orderNum, - currency: order.currency, - purchasedDatetime: order.purchase_datetime, - totalCost: self.formatMoney(order.total_cost), - isRefunded: order.status === 'refunded', - billedTo: { - firstName: order.billed_to.first_name, - lastName: order.billed_to.last_name, - city: order.billed_to.city, - state: order.billed_to.state, - postalCode: order.billed_to.postal_code, - country: order.billed_to.country - }, - items: [] - }; - - receiptContext.items = _.map( - order.items, - function(item) { - return { - lineDescription: item.line_desc, - cost: self.formatMoney(item.line_cost) - }; - } - ); - } - - return receiptContext; - }, - - getOrderCourseKey: function(order) { - var length, items; - if (this.useEcommerceApi) { - length = order.lines.length; - for (var i = 0; i < length; i++) { - var line = order.lines[i], - attributeValues = _.find(line.product.attribute_values, function(attribute) { - // If the attribute has a 'code' property, compare its value, otherwise compare 'name' - var value_to_match = 'course_key'; - if (attribute.code) { - return attribute.code === value_to_match; - } else { - return attribute.name === value_to_match; - } - }); - - // This method assumes that all items in the order are related to a single course. - if (attributeValues != undefined) { - return attributeValues.value; - } - } - } else { - items = _.filter(order.items, function(item) { - return item.course_key; - }); - - if (items.length > 0) { - return items[0].course_key; - } - } - - return null; - }, - - formatMoney: function(moneyStr) { - return Number(moneyStr).toFixed(2); - }, - - /** - * Check whether the payment is for the credit course or not. - * - * @param {object} order Receipt data received from the server - * @return {string} String of the provider_id or null. - */ - getCreditProviderId: function(order) { - var attributeValues, - line = order.lines[0]; - if (this.useEcommerceApi) { - attributeValues = _.find(line.product.attribute_values, function(attribute) { - return attribute.name === 'credit_provider'; - }); - - // This method assumes that all items in the order are related to a single course. - if (attributeValues != undefined) { - return attributeValues.value; - } - } - - return null; - } - }); - - new edx.commerce.ReceiptView({ - el: $('#receipt-container') - }); -}(jQuery, _, Backbone)); - -function completeOrder(event) { - 'use strict'; - var courseKey = $(event).data('course-key'), - username = $(event).data('username'), - providerId = $(event).data('provider'), - $errorContainer = $('#error-container'); - - try { - event.preventDefault(); - } catch (err) { - // Ignore the error as not all event inputs have the preventDefault method. - } - - analytics.track( - 'edx.bi.credit.clicked_complete_credit', - { - category: 'credit', - label: courseKey - } - ); - - edx.commerce.credit.createCreditRequest(providerId, courseKey, username).fail(function() { - $errorContainer.removeClass('hidden'); - }); -} diff --git a/lms/static/js/fixtures/commerce/checkout_receipt.html b/lms/static/js/fixtures/commerce/checkout_receipt.html deleted file mode 100644 index 9c0809a701..0000000000 --- a/lms/static/js/fixtures/commerce/checkout_receipt.html +++ /dev/null @@ -1,28 +0,0 @@ -<div id="error-container" class="hidden"> - <div id="error" class="wrapper-msg wrapper-msg-activate"> - <div class=" msg msg-activate"> - <span class="msg-icon icon fa fa-exclamation-triangle" aria-hidden="true"></span> - - <div class="msg-content"> - <h3 class="title"> - Error - </h3> - <div class="copy"> - <p>dummy error text</p> - </div> - </div> - </div> - </div> -</div> - -<div class="container"> - <section class="wrapper carousel"> - <div id="receipt-container" class="pay-and-verify hidden" data-is-payment-complete='True' - data-platform-name='edx-platform' data-verified='True' data-username='user-1' - data-is-request-in-themed-site='True'> - <h2>Loading Order Data...</h2> - <span>Please wait while we retrieve your order details.</span> - </div> - </section> -</div> - diff --git a/lms/static/js/spec/commerce/receipt_view_spec.js b/lms/static/js/spec/commerce/receipt_view_spec.js deleted file mode 100644 index ea00ed44fd..0000000000 --- a/lms/static/js/spec/commerce/receipt_view_spec.js +++ /dev/null @@ -1,338 +0,0 @@ -define([ - 'jquery', - 'jquery.ajax-retry', - 'js/commerce/views/receipt_view', - 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers' -], - function($, AjaxRetry, ReceiptView, AjaxHelpers) { - 'use strict'; - describe('edx.commerce.ReceiptView', function() { - var data, courseResponseData, providerResponseData, mockRequests, mockRender, createReceiptView, - createProduct, createLine, createOrderResponse, doCheckVerification, doCheckVerificationNagRendered, - userResponseData; - - createReceiptView = function() { - return new ReceiptView({el: $('#receipt-container')}); - }; - - mockRequests = function(requests, method, apiUrl, responseData) { - AjaxHelpers.expectRequest(requests, method, apiUrl); - AjaxHelpers.respondWithJson(requests, responseData); - }; - - mockRender = function(useEcommerceOrderNumber, isVerified, requestInThemedSite) { - var requests, view, orderUrlFormat, - actualRequestInThemedSite = requestInThemedSite; - if (typeof actualRequestInThemedSite === 'undefined') { - actualRequestInThemedSite = 'False'; - } - requests = AjaxHelpers.requests(this); - $('#receipt-container').data({ - verified: isVerified, - 'is-request-in-themed-site': actualRequestInThemedSite - }); - view = createReceiptView(); - view.useEcommerceApi = true; - if (useEcommerceOrderNumber) { - view.ecommerceOrderNumber = 'EDX-123456'; - orderUrlFormat = '/api/commerce/v1/orders/EDX-123456/'; - } else { - view.ecommerceBasketId = 'EDX-123456'; - orderUrlFormat = '/api/commerce/v0/baskets/EDX-123456/order/'; - } - view.render(); - mockRequests(requests, 'GET', orderUrlFormat, data); - - mockRequests( - requests, 'GET', '/commerce/checkout/verification_status/?course_id=' + - encodeURIComponent('course-v1:edx+dummy+2015_T3'), {is_verification_required: true} - ); - - mockRequests( - requests, 'GET', '/api/courses/v1/courses/course-v1:edx+dummy+2015_T3/', courseResponseData - ); - - mockRequests( - requests, 'GET', '/api/user/v1/accounts/user-1', userResponseData - ); - - mockRequests(requests, 'GET', '/api/credit/v1/providers/edx/', providerResponseData); - return view; - }; - - createProduct = function(attributes) { - var actualAttributes = attributes; - if (typeof actualAttributes === 'undefined') { - actualAttributes = [ - { - name: 'certificate_type', - value: 'verified' - }, - { - name: 'course_key', - code: 'course_key', - value: 'course-v1:edx+dummy+2015_T3' - }, - { - name: 'credit_provider', - value: 'edx' - } - ]; - } - - return { - attribute_values: actualAttributes, - stockrecords: [ - { - price_currency: 'USD', - product: 123, - partner_sku: '1234ABC', - partner: 1, - price_excl_tax: '10.00', - id: 123 - } - ], - product_class: 'Seat', - title: 'Dummy title', - url: 'https://ecom.edx.org/api/v2/products/123/', - price: '10.00', - expires: null, - is_available_to_buy: true, - id: 123, - structure: 'child' - }; - }; - - createLine = function(product) { - var actualProduct = product; - if (typeof actualProduct === 'undefined') { - actualProduct = createProduct(); - } - return { - status: 'Open', - unit_price_excl_tax: '10.00', - product: actualProduct, - line_price_excl_tax: '10.00', - description: 'dummy description', - title: 'dummy title', - quantity: 1 - }; - }; - - createOrderResponse = function(lines) { - var actualLines = lines; - if (typeof actualLines === 'undefined') { - actualLines = [createLine()]; - } - return { - status: 'Open', - billed_to: { - city: 'dummy city', - first_name: 'john', - last_name: 'doe', - country: 'AL', - line2: 'line2', - line1: 'line1', - state: '', - postcode: '12345' - }, - lines: actualLines, - number: 'EDX-123456', - date_placed: '2016-01-01T01:01:01Z', - currency: 'USD', - total_excl_tax: '10.00' - }; - }; - - doCheckVerification = function(attributes, expected) { - var view = createReceiptView(), - product = createProduct(attributes); - expect(view.requiresVerification(product)).toBe(expected); - }; - - doCheckVerificationNagRendered = function(attributes, userVerified, expected, requestInThemedSite) { - var view; - data = createOrderResponse([createLine(createProduct(attributes))]); - view = mockRender(true, userVerified, requestInThemedSite); - if (expected) { - expect(view.$('.nav-wizard.is-ready').text()).toContain('Want to confirm your identity later'); - } else { - expect(view.$('.nav-wizard.is-ready').text()).toContain('Go to Dashboard'); - } - }; - - beforeEach(function() { - var receiptFixture, providerFixture; - // Stub analytics tracking - window.analytics = jasmine.createSpyObj('analytics', ['page', 'track', 'trackLink']); - - loadFixtures('js/fixtures/commerce/checkout_receipt.html'); - - receiptFixture = readFixtures('templates/commerce/receipt.underscore'); - providerFixture = readFixtures('templates/commerce/provider.underscore'); - appendSetFixtures( - '<script id="receipt-tpl" type="text/template" >' + receiptFixture + '</script>' + - '<script id="provider-tpl" type="text/template" >' + providerFixture + '</script>' - ); - - data = createOrderResponse(); - - providerResponseData = { - id: 'edx', - display_name: 'edX', - url: 'http://www.edx.org', - status_url: 'http://www.edx.org/status', - description: 'Nothing', - enable_integration: false, - fulfillment_instructions: '', - thumbnail_url: 'http://edx.org/thumbnail.png' - }; - - courseResponseData = { - id: 'course-v1:edx+dummy+2015_T3', - name: 'receipt test', - category: 'course', - org: 'edx', - run: '2015_T2', - course: 'CS420', - uri: 'http://test.com/api/courses/v1/courses/course-v1:edx+dummy+2015_T3/', - image_url: '/test.jpg', - start: '2030-01-01T00:00:00Z', - end: null - }; - userResponseData = { - username: 'user-1', - name: 'full name' - }; - }); - - it('sends analytic event when verified receipt is rendered', function() { - mockRender(true, 'True'); - expect(window.analytics.track).toHaveBeenCalledWith( - 'Completed Purchase', - { - orderId: 'EDX-123456', - total: '10.00', - currency: 'USD' - } - ); - }); - - it('sends analytic event when non verified receipt is rendered', function() { - mockRender(true, 'False'); - expect(window.analytics.track).toHaveBeenCalledWith( - 'Completed Purchase', - { - orderId: 'EDX-123456', - total: '10.00', - currency: 'USD' - } - ); - }); - - it('renders a receipt correctly with Ecommerce Order Number', function() { - var view; - - view = mockRender(true, 'True'); - expect(view.$('.course_name_placeholder').text()).toContain('receipt test'); - }); - - it('renders a receipt correctly with Ecommerce Basket Id', function() { - var view; - - view = mockRender(false, 'True'); - expect(view.$('.course_name_placeholder').text()).toContain('receipt test'); - }); - - it('requiresVerification returns true if product requires verification', function() { - var expected = true, - attributes = [ - {name: 'certificate_type', value: 'professional'}, - {name: 'course_key', value: 'course-v1:OC+OC+2'}, - {name: 'id_verification_required', value: true} - ]; - doCheckVerification(attributes, expected, 'False'); - }); - - it('requiresVerification returns true if product requires verification, different order', function() { - var expected = true, - attributes = [ - {name: 'certificate_type', value: 'professional'}, - {name: 'id_verification_required', value: true}, - {name: 'course_key', value: 'course-v1:OC+OC+2'} - ]; - doCheckVerification(attributes, expected, 'False'); - }); - - it('requiresVerification defaults to true', function() { - var expected = true, - attributes = [ - {name: 'certificate_type', value: 'professional'}, - {name: 'course_key', value: 'course-v1:OC+OC+2'} - ]; - doCheckVerification(attributes, expected, 'False'); - }); - - it('requiresVerification returns false for courses not requiring verification', function() { - var expected = false, - attributes = [ - {name: 'certificate_type', value: 'professional'}, - {name: 'id_verification_required', value: false}, - {name: 'course_key', value: 'course-v1:OC+OC+2'} - ]; - doCheckVerification(attributes, expected, 'False'); - }); - - it('receipt view verification nag for not verified users in a verified course', function() { - var attributes = [ - {name: 'certificate_type', value: 'professional'}, - {name: 'id_verification_required', value: true}, - {name: 'course_key', value: 'course-v1:edx+dummy+2015_T3'}, - {name: 'credit_provider', value: 'edx'} - ]; - doCheckVerificationNagRendered(attributes, 'False', true, 'False'); - }); - - it("receipt view doesn't show verification nag for a verified user in a verified course", function() { - var attributes = [ - {name: 'certificate_type', value: 'professional'}, - {name: 'id_verification_required', value: true}, - {name: 'course_key', value: 'course-v1:edx+dummy+2015_T3'}, - {name: 'credit_provider', value: 'edx'} - ]; - doCheckVerificationNagRendered(attributes, 'True', false, 'False'); - }); - - it("receipt view doesn't show verification for a unverified user in a not verified course", function() { - var attributes = [ - {name: 'certificate_type', value: 'professional'}, - {name: 'id_verification_required', value: false}, - {name: 'course_key', value: 'course-v1:edx+dummy+2015_T3'}, - {name: 'credit_provider', value: 'edx'} - ]; - doCheckVerificationNagRendered(attributes, 'False', false, 'False'); - }); - - it("receipt view doesn't show verification nag for a verified user in a not verified course", function() { - var attributes = [ - {name: 'certificate_type', value: 'professional'}, - {name: 'id_verification_required', value: false}, - {name: 'course_key', value: 'course-v1:edx+dummy+2015_T3'}, - {name: 'credit_provider', value: 'edx'} - ]; - doCheckVerificationNagRendered(attributes, 'True', false, 'False'); - }); - - it("receipt view doesn't show verification nag for a not verified user in a verified" + - ' course on themed site', function() { - var attributes = [ - {name: 'certificate_type', value: 'professional'}, - {name: 'id_verification_required', value: true}, - {name: 'course_key', value: 'course-v1:edx+dummy+2015_T3'}, - {name: 'credit_provider', value: 'edx'} - ]; - doCheckVerificationNagRendered(attributes, 'False', false, 'True'); - }); - }); - } -); diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js index 8de036f33a..7c0cdb2c12 100644 --- a/lms/static/lms/js/spec/main.js +++ b/lms/static/lms/js/spec/main.js @@ -79,7 +79,6 @@ // Manually specify LMS files that are not converted to RequireJS 'history': 'js/vendor/history', - 'js/commerce/views/receipt_view': 'js/commerce/views/receipt_view', 'js/staff_debug_actions': 'js/staff_debug_actions', 'js/vendor/jquery.qubit': 'js/vendor/jquery.qubit', 'js/utils/navigation': 'js/utils/navigation', @@ -347,10 +346,6 @@ exports: 'js/ccx/schedule', deps: ['jquery', 'underscore', 'backbone', 'gettext', 'moment'] }, - 'js/commerce/views/receipt_view': { - exports: 'edx.commerce.ReceiptView', - deps: ['jquery', 'jquery.url', 'backbone', 'underscore', 'string_utils'] - }, // Backbone classes loaded explicitly until they are converted to use RequireJS 'js/instructor_dashboard/ecommerce': { @@ -714,7 +709,6 @@ 'js/learner_dashboard/spec/unenroll_view_spec.js', 'js/spec/api_admin/catalog_preview_spec.js', 'js/spec/ccx/schedule_spec.js', - 'js/spec/commerce/receipt_view_spec.js', 'js/spec/components/card/card_spec.js', 'js/spec/components/header/header_spec.js', 'js/spec/course_sharing/course_sharing_events_spec.js', diff --git a/lms/templates/commerce/checkout_cancel.html b/lms/templates/commerce/checkout_cancel.html deleted file mode 100644 index 08566ddb3c..0000000000 --- a/lms/templates/commerce/checkout_cancel.html +++ /dev/null @@ -1,15 +0,0 @@ -<%page expression_filter="h"/> -<%! from django.utils.translation import ugettext as _ -from openedx.core.djangolib.markup import HTML, Text -%> - -<%inherit file="../main.html" /> - -<%block name="pagetitle">${_("Checkout Cancelled")}</%block> - - -<section class="container"> - <h1>${_("Checkout Cancelled")}</h1> - ${ Text(_(u"Your transaction has been cancelled. If you feel an error has occurred, contact {email}.")).format( - email=HTML("<a href=\"mailto:{email}\">{email}</a>").format(email=payment_support_email)) } -</section> diff --git a/lms/templates/commerce/checkout_error.html b/lms/templates/commerce/checkout_error.html deleted file mode 100644 index ecc8dc596a..0000000000 --- a/lms/templates/commerce/checkout_error.html +++ /dev/null @@ -1,17 +0,0 @@ -<%page expression_filter="h"/> -<%! from django.utils.translation import ugettext as _ -from openedx.core.djangolib.markup import HTML, Text -%> - -<%inherit file="../main.html" /> - -<%block name="pagetitle">${_("Checkout Error")}</%block> - - -<section class="container"> - <h1>${_("Checkout Error")}</h1> - ${ Text(_(u"An error has occurred with your payment. {b_start}You have not been charged.{b_end} Please try to submit your payment again. If this problem persists, contact {email}.")).format( - b_start=HTML('<b>'), - b_end=HTML('</b>'), - email=HTML("<a href=\"mailto:{email}\">{email}</a>").format(email=payment_support_email)) } -</section> diff --git a/lms/templates/commerce/checkout_receipt.html b/lms/templates/commerce/checkout_receipt.html deleted file mode 100644 index 2b89d30100..0000000000 --- a/lms/templates/commerce/checkout_receipt.html +++ /dev/null @@ -1,62 +0,0 @@ -<%page expression_filter="h"/> -<%! -from django.utils.translation import ugettext as _ -%> -<%namespace name='static' file='../static_content.html'/> - -<%inherit file="../main.html" /> -<%block name="bodyclass">register verification-process step-requirements</%block> - -<%block name="pagetitle">${page_title}</%block> - -<%block name="header_extras"> -<script type="text/template" id="receipt-tpl"> - <%static:include path="commerce/receipt.underscore" /> -</script> -<script type="text/template" id="provider-tpl"> - <%static:include path="commerce/provider.underscore" /> -</script> - -</%block> -<%block name="js_extra"> -<script src="${static.url('js/vendor/jquery.ajax-retry.js')}"></script> -<script src="${static.url('js/src/tooltip_manager.js')}"></script> -<script src="${static.url('js/commerce/credit.js')}"></script> -<script src="${static.url('js/commerce/views/receipt_view.js')}"></script> -</%block> - -<%block name="content"> -<div id="error-container" class="hidden"> - <div id="error" class="wrapper-msg wrapper-msg-activate"> - <div class=" msg msg-activate"> - <span class="msg-icon icon fa fa-exclamation-triangle" aria-hidden="true"></span> - <div class="msg-content"> - <h3 class="title"> - <span class="sr">${error_summary}</span> - ${error_summary} - </h3> - %if error_text: - <div class="copy"> - <p>${error_text}</p> - <br/> - </div> - %endif - <div class="msg"> - <p>${for_help_text}</p> - </div> - </div> - </div> - </div> -</div> - -<div class="container"> - <section class="wrapper carousel"> - <div id="receipt-container" class="pay-and-verify hidden" data-is-payment-complete='${is_payment_complete}' - data-platform-name='${platform_name}' data-verified='${verified}' data-username='${username}' - data-is-request-in-themed-site='${is_request_in_themed_site}'> - <h1>${_("Loading Order Data...")}</h1> - <span>${ _("Please wait while we retrieve your order details.") }</span> - </div> - </section> -</div> -</%block> diff --git a/lms/templates/commerce/provider.underscore b/lms/templates/commerce/provider.underscore deleted file mode 100644 index d86955eb39..0000000000 --- a/lms/templates/commerce/provider.underscore +++ /dev/null @@ -1,32 +0,0 @@ -<div class="provider-wrapper"> - <div class="provider-info"> - <%- edx.StringUtils.interpolate( - gettext("You still need to visit the {display_name} website to complete the credit process."), - { display_name: display_name }) - %> - </div> - <div class="provider-more-info"> - <%- edx.StringUtils.interpolate( - gettext("To finalize course credit, {display_name} requires {platform_name} learners to submit a credit request."), - { display_name: display_name, platform_name: platformName }) - %> - </div> - <div class="provider-instructions"> - <%- fulfillment_instructions %> - </div> -</div> - -<div class="provider-buttons-logos"> - <div class="provider-logo"> - <%= edx.HtmlUtils.interpolateHtml( - edx.HtmlUtils.HTML('<img src="{thumbnailUrl}" alt="{displayName}"></image>'), - {thumbnailUrl:thumbnail_url ,displayName:display_name}) - %> - </div> - <div class="complete-order"> - <%= edx.HtmlUtils.interpolateHtml( - edx.HtmlUtils.HTML('<button data-provider="{id}" data-course-key="{courseKey}" data-username="{userName}" class="complete-course" onClick=completeOrder(this)>{credit}</button>'), - {id:id, courseKey:course_key, userName:username, credit:gettext( "Get Credit")}) - %> - </div> -</div> diff --git a/lms/templates/commerce/receipt.underscore b/lms/templates/commerce/receipt.underscore deleted file mode 100644 index 9a434efe60..0000000000 --- a/lms/templates/commerce/receipt.underscore +++ /dev/null @@ -1,109 +0,0 @@ -<div class="wrapper-content-main payment-confirmation-step"> - <article class="content-main"> - <h3 class="title"> - <%= edx.HtmlUtils.interpolateHtml( - gettext( "Thank you {full_name}! We have received your payment for {course_name}."), - { - course_name: edx.HtmlUtils.HTML("<span class='course_name_placeholder'></span>"), - full_name: edx.HtmlUtils.HTML("<span class='full_name_placeholder'></span>") - }) - %> - </h3> - - <% if ( receipt ) { %> - <div class="list-info"> - <div class="info-item payment-info"> - <div class="copy"> - <p><%- gettext( "Please print this page for your records; it serves as your receipt. You will also receive an email with the same information." ) %></p> - </div> - - <div class="wrapper-report"> - <table class="report report-receipt"> - <thead> - <tr> - <th scope="col" ><%- gettext( "Order No." ) %></th> - <th scope="col" ><%- gettext( "Description" ) %></th> - <th scope="col" ><%- gettext( "Date" ) %></th> - <th scope="col" ><%- gettext( "Amount" ) %></th> - </tr> - </thead> - - <tbody> - <% for ( var i = 0; i < receipt.items.length; i++ ) { %> - <% if ( receipt.isRefunded ) { %> - <td><del><%- receipt.orderNum %></del></td> - <td><del><%- receipt.items[i].lineDescription %></del></td> - <td><del><%- receipt.purchasedDatetime %></del></td> - <td><del><%- receipt.items[i].cost %> (<%- receipt.currency.toUpperCase() %>)</del></td> - <% } else { %> - <tr> - <td><%- receipt.orderNum %></td> - <td><%- receipt.items[i].lineDescription %></td> - <td><%- receipt.purchasedDatetime %></td> - <td><%- receipt.items[i].cost %> (<%- receipt.currency.toUpperCase() %>)</td> - </tr> - <% } %> - <% } %> - </tbody> - - <tfoot> - <tr> - <th scope="row" class="total-label"><%- gettext( "Total" ) %></th> - <td colspan="2"></td> - <td class="total-value"> - <span class="value-amount"><%- receipt.totalCost %></span> - <span class="value-currency">(<%- receipt.currency.toUpperCase() %>)</span> - </td> - </tr> - </tfoot> - </table> - - <% if ( receipt.isRefunded ) { %> - <div class="msg msg-refunds"> - <h4 class="title sr"><%- gettext( "Please Note" ) %>: </h4> - <div class="copy"> - <p><%- gettext( "Crossed out items have been refunded." ) %></p> - </div> - </div> - <% } %> - </div> - - <% if ( receipt.billedTo ) { %> - <div class="copy"> - <p><%- gettext( "Billed to" ) %>: - <span class="name-first"><%- receipt.billedTo.firstName %></span> - <span class="name-last"><%- receipt.billedTo.lastName %></span> - (<span class="address-city"><%- receipt.billedTo.city %></span>, - <span class="address-state"><%- receipt.billedTo.state %></span> - <span class="address-postalcode"><%- receipt.billedTo.postalCode %></span> - <span class="address-country"><%- receipt.billedTo.country.toUpperCase() %></span>) - </p> - </div> - <% } %> - - <div class="report report-receipt report-receipt-provider hidden" id="receipt-provider"></div> - - </div> - </div> - <% } else { %> - <p class="no-content"><%- gettext( "No receipt available" ) %></p> - <% } %> - - <nav class="nav-wizard is-ready"> - <% if ( !is_verification_required || verified || is_request_in_themed_site) { %> - <a class="next action-primary right" href="/dashboard"><%- gettext( "Go to Dashboard" ) %></a> - <% } else { %> - <a id="verify_later_button" class="next action-secondary verify-later nav-link" href="/dashboard" data-tooltip="<%- edx.StringUtils.interpolate( gettext( "If you don't verify your identity now, you can still explore your course from your dashboard. You will receive periodic reminders from {platformName} to verify your identity." ), { platformName: platformName } ) %>"> - <%- gettext( "Want to confirm your identity later?" ) %> - </a> - - <a id="verify_now_button" - class="next action-primary right" - href="<%- edx.StringUtils.interpolate( '/verify_student/verify-now/{courseKey}/', { courseKey: courseKey } ) %>" - > - <%- gettext( "Verify Now" ) %> - </a> - <% } %> - </nav> - </article> -</div> diff --git a/lms/urls.py b/lms/urls.py index 11eb68908f..b4c018227d 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -817,7 +817,6 @@ if configuration_helpers.get_value('ENABLE_BULK_ENROLLMENT_VIEW', settings.FEATU # Shopping cart urlpatterns += [ url(r'^shoppingcart/', include('shoppingcart.urls')), - url(r'^commerce/', include(('lms.djangoapps.commerce.urls', 'lms.djangoapps.commerce'), namespace='commerce')), ] # Course goals