commerce app: remove code related to shoppingcart (#23974)
Part of DEPR-43 - https://openedx.atlassian.net/browse/DEPR-43 Co-authored-by: Jeremy Bowman <jbowman@edx.org>
This commit is contained in:
committed by
GitHub
parent
6cf2c56a07
commit
5b3f5ee85a
@@ -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"<title>(\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")
|
||||
|
||||
@@ -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'),
|
||||
]
|
||||
@@ -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})
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user