Add JSON end-point for shoppingcart receipt information
Add enrollment confirmation page Fix progress update Add order info to the payment confirmation page.
This commit is contained in:
@@ -26,6 +26,7 @@ from xmodule.modulestore.tests.django_utils import (
|
||||
ModuleStoreTestCase, mixed_store_config
|
||||
)
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from util.date_utils import get_default_time_display
|
||||
from shoppingcart.views import _can_download_report, _get_date_from_str
|
||||
from shoppingcart.models import (
|
||||
Order, CertificateItem, PaidCourseRegistration, CourseRegCodeItem,
|
||||
@@ -66,6 +67,7 @@ MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, incl
|
||||
|
||||
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
|
||||
@ddt.ddt
|
||||
class ShoppingCartViewsTests(ModuleStoreTestCase):
|
||||
def setUp(self):
|
||||
patcher = patch('student.models.tracker')
|
||||
@@ -801,6 +803,103 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
|
||||
self.assertEqual(context['order'], self.cart)
|
||||
self.assertEqual(context['error_html'], 'ERROR_TEST!!!')
|
||||
|
||||
@ddt.data(0, 1)
|
||||
def test_show_receipt_json(self, num_items):
|
||||
# Create the correct number of items in the order
|
||||
for __ in range(num_items):
|
||||
CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
||||
self.cart.purchase()
|
||||
|
||||
self.login_user()
|
||||
url = reverse('shoppingcart.views.show_receipt', args=[self.cart.id])
|
||||
resp = self.client.get(url, HTTP_ACCEPT="application/json")
|
||||
|
||||
# Should have gotten a successful response
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# Parse the response as JSON and check the contents
|
||||
json_resp = json.loads(resp.content)
|
||||
self.assertEqual(json_resp.get('currency'), self.cart.currency)
|
||||
self.assertEqual(json_resp.get('purchase_datetime'), get_default_time_display(self.cart.purchase_time))
|
||||
self.assertEqual(json_resp.get('total_cost'), self.cart.total_cost)
|
||||
self.assertEqual(json_resp.get('status'), "purchased")
|
||||
self.assertEqual(json_resp.get('billed_to'), {
|
||||
'first_name': self.cart.bill_to_first,
|
||||
'last_name': self.cart.bill_to_last,
|
||||
'street1': self.cart.bill_to_street1,
|
||||
'street2': self.cart.bill_to_street2,
|
||||
'city': self.cart.bill_to_city,
|
||||
'state': self.cart.bill_to_state,
|
||||
'postal_code': self.cart.bill_to_postalcode,
|
||||
'country': self.cart.bill_to_country
|
||||
})
|
||||
|
||||
self.assertEqual(len(json_resp.get('items')), num_items)
|
||||
for item in json_resp.get('items'):
|
||||
self.assertEqual(item, {
|
||||
'unit_cost': 40,
|
||||
'quantity': 1,
|
||||
'line_cost': 40,
|
||||
'line_desc': 'Honor Code Certificate for course Test Course'
|
||||
})
|
||||
|
||||
def test_show_receipt_json_multiple_items(self):
|
||||
# Two different item types
|
||||
PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
||||
CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
||||
self.cart.purchase()
|
||||
|
||||
self.login_user()
|
||||
url = reverse('shoppingcart.views.show_receipt', args=[self.cart.id])
|
||||
resp = self.client.get(url, HTTP_ACCEPT="application/json")
|
||||
|
||||
# Should have gotten a successful response
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# Parse the response as JSON and check the contents
|
||||
json_resp = json.loads(resp.content)
|
||||
self.assertEqual(json_resp.get('total_cost'), self.cart.total_cost)
|
||||
|
||||
items = json_resp.get('items')
|
||||
self.assertEqual(len(items), 2)
|
||||
self.assertEqual(items[0], {
|
||||
'unit_cost': 40,
|
||||
'quantity': 1,
|
||||
'line_cost': 40,
|
||||
'line_desc': 'Registration for Course: Robot Super Course'
|
||||
})
|
||||
self.assertEqual(items[1], {
|
||||
'unit_cost': 40,
|
||||
'quantity': 1,
|
||||
'line_cost': 40,
|
||||
'line_desc': 'Honor Code Certificate for course Test Course'
|
||||
})
|
||||
|
||||
def test_receipt_json_refunded(self):
|
||||
mock_enrollment = Mock()
|
||||
mock_enrollment.refundable.side_effect = lambda: True
|
||||
mock_enrollment.course_id = self.verified_course_key
|
||||
mock_enrollment.user = self.user
|
||||
|
||||
CourseMode.objects.create(
|
||||
course_id=self.verified_course_key,
|
||||
mode_slug="verified",
|
||||
mode_display_name="verified cert",
|
||||
min_price=self.cost
|
||||
)
|
||||
|
||||
cert = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'verified')
|
||||
self.cart.purchase()
|
||||
cert.refund_cert_callback(course_enrollment=mock_enrollment)
|
||||
|
||||
self.login_user()
|
||||
url = reverse('shoppingcart.views.show_receipt', args=[self.cart.id])
|
||||
resp = self.client.get(url, HTTP_ACCEPT="application/json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
json_resp = json.loads(resp.content)
|
||||
self.assertEqual(json_resp.get('status'), 'refunded')
|
||||
|
||||
def test_show_receipt_404s(self):
|
||||
PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
||||
CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
||||
|
||||
@@ -14,6 +14,7 @@ from django.views.decorators.http import require_POST, require_http_methods
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from util.bad_request_rate_limiter import BadRequestRateLimiter
|
||||
from util.date_utils import get_default_time_display
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from microsite_configuration import microsite
|
||||
from edxmako.shortcuts import render_to_response
|
||||
@@ -655,15 +656,76 @@ def show_receipt(request, ordernum):
|
||||
Displays a receipt for a particular order.
|
||||
404 if order is not yet purchased or request.user != order.user
|
||||
"""
|
||||
|
||||
try:
|
||||
order = Order.objects.get(id=ordernum)
|
||||
except Order.DoesNotExist:
|
||||
raise Http404('Order not found!')
|
||||
|
||||
if order.user != request.user or order.status != 'purchased':
|
||||
if order.user != request.user or order.status not in ['purchased', 'refunded']:
|
||||
raise Http404('Order not found!')
|
||||
|
||||
if 'application/json' in request.META.get('HTTP_ACCEPT', ""):
|
||||
return _show_receipt_json(order)
|
||||
else:
|
||||
return _show_receipt_html(request, order)
|
||||
|
||||
|
||||
def _show_receipt_json(order):
|
||||
"""Render the receipt page as JSON.
|
||||
|
||||
The included information is deliberately minimal:
|
||||
as much as possible, the included information should
|
||||
be common to *all* order items, so the client doesn't
|
||||
need to handle different item types differently.
|
||||
|
||||
Arguments:
|
||||
request (HttpRequest): The request for the receipt.
|
||||
order (Order): The order model to display.
|
||||
|
||||
Returns:
|
||||
HttpResponse
|
||||
|
||||
"""
|
||||
order_info = {
|
||||
'orderNum': order.id,
|
||||
'currency': order.currency,
|
||||
'status': order.status,
|
||||
'purchase_datetime': get_default_time_display(order.purchase_time) if order.purchase_time else None,
|
||||
'billed_to': {
|
||||
'first_name': order.bill_to_first,
|
||||
'last_name': order.bill_to_last,
|
||||
'street1': order.bill_to_street1,
|
||||
'street2': order.bill_to_street2,
|
||||
'city': order.bill_to_city,
|
||||
'state': order.bill_to_state,
|
||||
'postal_code': order.bill_to_postalcode,
|
||||
'country': order.bill_to_country,
|
||||
},
|
||||
'total_cost': order.total_cost,
|
||||
'items': [
|
||||
{
|
||||
'quantity': item.qty,
|
||||
'unit_cost': item.unit_cost,
|
||||
'line_cost': item.line_cost,
|
||||
'line_desc': item.line_desc
|
||||
}
|
||||
for item in OrderItem.objects.filter(order=order).select_subclasses()
|
||||
]
|
||||
}
|
||||
return JsonResponse(order_info)
|
||||
|
||||
|
||||
def _show_receipt_html(request, order):
|
||||
"""Render the receipt page as HTML.
|
||||
|
||||
Arguments:
|
||||
request (HttpRequest): The request for the receipt.
|
||||
order (Order): The order model to display.
|
||||
|
||||
Returns:
|
||||
HttpResponse
|
||||
|
||||
"""
|
||||
order_items = OrderItem.objects.filter(order=order).select_subclasses()
|
||||
shoppingcart_items = []
|
||||
course_names_list = []
|
||||
|
||||
@@ -1010,6 +1010,7 @@ courseware_js = (
|
||||
base_vendor_js = [
|
||||
'js/vendor/jquery.min.js',
|
||||
'js/vendor/jquery.cookie.js',
|
||||
'js/vendor/url.min.js',
|
||||
'js/vendor/underscore-min.js',
|
||||
'js/vendor/require.js',
|
||||
'js/RequireJS-namespace-undefine.js',
|
||||
|
||||
@@ -56,6 +56,11 @@ var edx = edx || {};
|
||||
'review-photos-step': {
|
||||
fullName: el.data('full-name'),
|
||||
platformName: el.data('platform-name')
|
||||
},
|
||||
'enrollment-confirmation-step': {
|
||||
courseName: el.data('course-name'),
|
||||
courseStartDate: el.data('course-start-date'),
|
||||
coursewareUrl: el.data('courseware-url')
|
||||
}
|
||||
}
|
||||
}).render();
|
||||
|
||||
@@ -30,18 +30,17 @@ var edx = edx || {};
|
||||
this.errorModel = obj.errorModel || {};
|
||||
this.displaySteps = obj.displaySteps || [];
|
||||
|
||||
// Determine which step we're starting on
|
||||
// Depending on how the user enters the flow,
|
||||
// this could be anywhere in the sequence of steps.
|
||||
this.currentStepIndex = _.indexOf(
|
||||
_.pluck( this.displaySteps, 'name' ),
|
||||
obj.currentStep
|
||||
);
|
||||
|
||||
this.progressView = new edx.verify_student.ProgressView({
|
||||
el: this.el,
|
||||
displaySteps: this.displaySteps,
|
||||
currentStepIndex: this.currentStepIndex
|
||||
|
||||
// Determine which step we're starting on
|
||||
// Depending on how the user enters the flow,
|
||||
// this could be anywhere in the sequence of steps.
|
||||
currentStepIndex: _.indexOf(
|
||||
_.pluck( this.displaySteps, 'name' ),
|
||||
obj.currentStep
|
||||
)
|
||||
});
|
||||
|
||||
this.initializeStepViews( obj.stepInfo );
|
||||
@@ -140,29 +139,21 @@ var edx = edx || {};
|
||||
// underscore template.
|
||||
// When the view is rendered, it will overwrite the existing
|
||||
// step in the DOM.
|
||||
stepName = this.displaySteps[ this.currentStepIndex ].name;
|
||||
stepName = this.displaySteps[ this.progressView.currentStepIndex ].name;
|
||||
stepView = this.subviews[ stepName ];
|
||||
stepView.el = stepEl;
|
||||
stepView.render();
|
||||
},
|
||||
|
||||
nextStep: function() {
|
||||
this.currentStepIndex = Math.min( this.currentStepIndex + 1, this.displaySteps.length - 1 );
|
||||
this.progressView.nextStep();
|
||||
this.render();
|
||||
},
|
||||
|
||||
goToStep: function( stepName ) {
|
||||
var stepIndex = _.indexOf(
|
||||
_.pluck( this.displaySteps, 'name' ),
|
||||
stepName
|
||||
);
|
||||
|
||||
if ( stepIndex >= 0 ) {
|
||||
this.currentStepIndex = stepIndex;
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
this.progressView.goToStep( stepName );
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery, _, Backbone, gettext);
|
||||
|
||||
@@ -3,16 +3,133 @@
|
||||
*/
|
||||
var edx = edx || {};
|
||||
|
||||
(function( $ ) {
|
||||
(function( $, _, gettext ) {
|
||||
'use strict';
|
||||
|
||||
edx.verify_student = edx.verify_student || {};
|
||||
|
||||
// The "Verify Later" button goes directly to the dashboard,
|
||||
// The "Verify Now" button reloads this page with the "skip-first-step"
|
||||
// flag set. This allows the user to navigate back to the confirmation
|
||||
// if he/she wants to.
|
||||
// For this reason, we don't need any custom click handlers here.
|
||||
edx.verify_student.PaymentConfirmationStepView = edx.verify_student.StepView.extend({});
|
||||
edx.verify_student.PaymentConfirmationStepView = edx.verify_student.StepView.extend({
|
||||
|
||||
})( jQuery );
|
||||
/**
|
||||
* Retrieve receipt information from the shopping cart.
|
||||
*
|
||||
* We make this request from JavaScript to encapsulate
|
||||
* the verification Django app from the shopping cart app.
|
||||
*
|
||||
* This method checks the query string param
|
||||
* ?payment-order-num, which can be set by the shopping cart
|
||||
* before redirecting to the payment confirmation page.
|
||||
* This step then reads the param and requests receipt information
|
||||
* from the shopping cart. At no point does the "verify student"
|
||||
* Django app interact directly with the shopping cart.
|
||||
*
|
||||
* @param {object} templateContext The original template context.
|
||||
* @return {object} A JQuery promise that resolves with the modified
|
||||
* template context.
|
||||
*/
|
||||
updateContext: function( templateContext ) {
|
||||
var view = this;
|
||||
return $.Deferred(
|
||||
function( defer ) {
|
||||
var paymentOrderNum = $.url( '?payment-order-num' );
|
||||
if ( paymentOrderNum ) {
|
||||
// If there is a payment order number, try to retrieve
|
||||
// the receipt information from the shopping cart.
|
||||
view.getReceiptData( paymentOrderNum ).done(
|
||||
function( data ) {
|
||||
// Add the receipt info to the template context
|
||||
_.extend( templateContext, { receipt: this.receiptContext( data ) } );
|
||||
defer.resolveWith( view, [ templateContext ]);
|
||||
}
|
||||
).fail(function() {
|
||||
// Display an error
|
||||
// This can occur if the user does not have access to the receipt
|
||||
// or the order number is invalid.
|
||||
defer.rejectWith(
|
||||
this,
|
||||
[
|
||||
gettext( "Error" ),
|
||||
gettext( "Could not retrieve payment information" )
|
||||
]
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// If no payment order is provided, return the original context
|
||||
// The template is responsible for displaying a default state.
|
||||
_.extend( templateContext, { receipt: null } );
|
||||
defer.resolveWith( view, [ templateContext ]);
|
||||
}
|
||||
}
|
||||
).promise();
|
||||
},
|
||||
|
||||
/**
|
||||
* The "Verify Later" button goes directly to the dashboard,
|
||||
* The "Verify Now" button reloads this page with the "skip-first-step"
|
||||
* flag set. This allows the user to navigate back to the confirmation
|
||||
* if he/she wants to.
|
||||
* For this reason, we don't need any custom click handlers here.
|
||||
*/
|
||||
postRender: function() {},
|
||||
|
||||
/**
|
||||
* Retrieve receipt data from the shoppingcart.
|
||||
* @param {int} paymentOrderNum The order number of the payment.
|
||||
* @return {object} JQuery Promise.
|
||||
*/
|
||||
getReceiptData: function( paymentOrderNum ) {
|
||||
return $.ajax({
|
||||
url: _.sprintf( '/shoppingcart/receipt/%s/', paymentOrderNum ),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
context: this
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Construct the template context from data received
|
||||
* from the shopping cart receipt.
|
||||
*
|
||||
* @param {object} data Receipt data received from the server
|
||||
* @return {object} Receipt template context.
|
||||
*/
|
||||
receiptContext: function( data ) {
|
||||
var view = this,
|
||||
receiptContext;
|
||||
|
||||
receiptContext = {
|
||||
orderNum: data.orderNum,
|
||||
currency: data.currency,
|
||||
purchasedDatetime: data.purchase_datetime,
|
||||
totalCost: view.formatMoney( data.total_cost ),
|
||||
isRefunded: data.status === "refunded",
|
||||
billedTo: {
|
||||
firstName: data.billed_to.first_name,
|
||||
lastName: data.billed_to.last_name,
|
||||
city: data.billed_to.city,
|
||||
state: data.billed_to.state,
|
||||
postalCode: data.billed_to.postal_code,
|
||||
country: data.billed_to.country
|
||||
},
|
||||
items: []
|
||||
};
|
||||
|
||||
receiptContext.items = _.map(
|
||||
data.items,
|
||||
function( item ) {
|
||||
return {
|
||||
lineDescription: item.line_desc,
|
||||
cost: view.formatMoney( item.line_cost )
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return receiptContext;
|
||||
},
|
||||
|
||||
formatMoney: function( moneyStr ) {
|
||||
return Number( moneyStr ).toFixed(2);
|
||||
}
|
||||
});
|
||||
|
||||
})( jQuery, _, gettext );
|
||||
|
||||
@@ -18,6 +18,24 @@
|
||||
this.currentStepIndex = obj.currentStepIndex || 0;
|
||||
},
|
||||
|
||||
nextStep: function() {
|
||||
this.currentStepIndex = Math.min(
|
||||
this.currentStepIndex + 1,
|
||||
this.displaySteps.length - 1
|
||||
);
|
||||
},
|
||||
|
||||
goToStep: function( stepName ) {
|
||||
var stepIndex = _.indexOf(
|
||||
_.pluck( this.displaySteps, 'name' ),
|
||||
stepName
|
||||
);
|
||||
|
||||
if ( stepIndex >= 0 ) {
|
||||
this.currentStepIndex = stepIndex;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var renderedHtml, context;
|
||||
|
||||
|
||||
@@ -64,10 +64,10 @@
|
||||
this.postRender();
|
||||
},
|
||||
|
||||
handleError: function() {
|
||||
handleError: function( errorTitle, errorMsg ) {
|
||||
this.errorModel.set({
|
||||
errorTitle: gettext( "Error" ),
|
||||
errorMsg: gettext( "An unexpected error occurred. Please reload the page to try again." ),
|
||||
errorTitle: errorTitle || gettext( "Error" ),
|
||||
errorMsg: errorMsg || gettext( "An unexpected error occurred. Please reload the page to try again." ),
|
||||
shown: true
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1 +1,50 @@
|
||||
<p>Enrollment confirmation!</p>
|
||||
<div class="wrapper-content-main">
|
||||
<article class="content-main">
|
||||
<h3 class="title"><%- gettext( "Congratulations! You are now enrolled in the verified track." ) %></h3>
|
||||
<div class="instruction">
|
||||
<p><%- gettext( "You are now enrolled as a verified student! Your enrollment details are below.") %></p>
|
||||
</div>
|
||||
|
||||
<ul class="list-info">
|
||||
<li class="info-item course-info">
|
||||
<h4 class="title">
|
||||
<%- gettext( "You are enrolled in " ) %> :
|
||||
</h4>
|
||||
<div class="wrapper-report">
|
||||
<table class="report report-course">
|
||||
<caption class="sr"><%- gettext( "A list of courses you have just enrolled in as a verified student" ) %></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" ><%- gettext( "Course" ) %></th>
|
||||
<th scope="col" ><%- gettext( "Status" ) %></th>
|
||||
<th scope="col" ><span class="sr"><%- gettext( "Options" ) %></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><%- courseName %></td>
|
||||
<td>
|
||||
<%- _.sprintf( gettext( "Starts: %(start)s" ), { start: courseStartDate } ) %>
|
||||
</td>
|
||||
<td class="options">
|
||||
<% if ( coursewareUrl ) { %>
|
||||
<a class="action action-course" href="<%- coursewareUrl %>"><%- gettext( "Go to Course" ) %></a>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="course-actions">
|
||||
<td colspan="3">
|
||||
<a class="action action-dashboard" href="/dashboard"><%- gettext("Go to your dashboard") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
@@ -5,47 +5,80 @@
|
||||
<p><%- gettext( "You are now enrolled as a verified student! Your enrollment details are below.") %></p>
|
||||
</div>
|
||||
|
||||
<% if ( receipt ) { %>
|
||||
<ul class="list-info">
|
||||
<li class="info-item course-info">
|
||||
<h4 class="title">
|
||||
<%- gettext( "You are enrolled in " ) %> :
|
||||
</h4>
|
||||
<li class="info-item payment-info">
|
||||
<h4 class="title"><%- gettext( "Payment Details" ) %></h4>
|
||||
|
||||
<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-course">
|
||||
<caption class="sr"><%- gettext( "A list of courses you have just enrolled in as a verified student" ) %></caption>
|
||||
<table class="report report-receipt">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" ><%- gettext( "Course" ) %></th>
|
||||
<th scope="col" ><%- gettext( "Status" ) %></th>
|
||||
<th scope="col" ><span class="sr"><%- gettext( "Options" ) %></span></th>
|
||||
<th scope="col" ><%- gettext( "Order No." ) %></th>
|
||||
<th scope="col" ><%- gettext( "Description" ) %></th>
|
||||
<th scope="col" ><%- gettext( "Date" ) %></th>
|
||||
<th scope="col" ><%- gettext( "Description" ) %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><%- courseName %></td>
|
||||
<td>
|
||||
<%- _.sprintf( gettext( "Starts: %(start)s" ), { start: courseStartDate } ) %>
|
||||
</td>
|
||||
<td class="options">
|
||||
<% if ( coursewareUrl ) { %>
|
||||
<a class="action action-course" href="<%- coursewareUrl %>"><%- gettext( "Go to Course" ) %></a>
|
||||
<% 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>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
|
||||
<tfoot>
|
||||
<tr class="course-actions">
|
||||
<td colspan="3">
|
||||
<a class="action action-dashboard" href="/dashboard"><%- gettext("Go to your dashboard") %></a>
|
||||
<tr>
|
||||
<th scope="row" class="total-label" colspan="1"><%- gettext( "Total" ) %></th>
|
||||
<td claass="total-value" colspan="3">
|
||||
<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( "Items with strikethough have been refunded." ) %></p>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
<% } else { %>
|
||||
<p><%- gettext( "No receipt available." ) %></p>
|
||||
<% } %>
|
||||
|
||||
<% if ( nextStepTitle ) { %>
|
||||
<nav class="nav-wizard is-ready">
|
||||
|
||||
Reference in New Issue
Block a user