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:
Nimisha Asthagiri
2020-05-28 09:46:12 -04:00
committed by GitHub
parent 6cf2c56a07
commit 5b3f5ee85a
13 changed files with 0 additions and 1264 deletions

View File

@@ -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")

View File

@@ -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'),
]

View File

@@ -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})

View File

@@ -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');
});
}

View File

@@ -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>

View File

@@ -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');
});
});
}
);

View File

@@ -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',

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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