Commit 937ca38f by Awais Qureshi

Merge pull request #8867 from edx/aamir-khan/ECOM-1816-credit-info-recept-page

ECOM-1816: added the provider detail on the receipt page.
parents ba06ab19 d5dda647
......@@ -66,5 +66,6 @@ def checkout_receipt(request):
'error_text': error_text,
'for_help_text': for_help_text,
'payment_support_email': payment_support_email,
'username': request.user.username
}
return render_to_response('commerce/checkout_receipt.html', context)
......@@ -13,7 +13,7 @@ var edx = edx || {};
initialize: function () {
this.useEcommerceApi = !!($.url('?basket_id'));
_.bindAll(this, 'renderReceipt', 'renderError');
_.bindAll(this, 'renderReceipt', 'renderError', 'getProviderData', 'renderProvider');
/* Mix non-conflicting functions from underscore.string (all but include, contains, and reverse) into
* the Underscore namespace.
......@@ -28,17 +28,31 @@ var edx = edx || {};
context = {
platformName: this.$el.data('platform-name'),
verified: this.$el.data('verified').toLowerCase() === 'true'
};
},
providerId;
// Add the receipt info to the template context
this.course_key = this.getOrderCourseKey(data)
this.username = this.$el.data('username');
_.extend(context, {
receipt: this.receiptContext(data),
courseKey: this.getOrderCourseKey(data)
courseKey: this.course_key
});
this.$el.html(_.template(templateHtml, context));
this.trackLinks();
providerId = this.getCreditProviderId(data);
if (providerId) {
this.getProviderData(providerId).then(this.renderProvider, this.renderError)
}
},
renderProvider: function (context) {
var templateHtml = $("#provider-tpl").html(),
providerDiv = this.$el.find("#receipt-provider");
context.course_key = this.course_key;
context.username = this.username;
providerDiv.html(_.template(templateHtml, context)).removeClass('hidden');
},
renderError: function () {
......@@ -80,7 +94,7 @@ var edx = edx || {};
/**
* Retrieve receipt data from Oscar (via LMS).
* @param {int} basketId The basket that was purchased.
* @return {object} JQuery Promise.
* @return {object} JQuery Promise.
*/
getReceiptData: function (basketId) {
var urlFormat = this.useEcommerceApi ? '/api/commerce/v0/baskets/%s/order/' : '/shoppingcart/receipt/%s/';
......@@ -91,13 +105,27 @@ var edx = edx || {};
dataType: 'json'
}).retry({times: 5, timeout: 2000, statusCodes: [404]});
},
/**
* Retrieve credit provider data from LMS.
* @param {string} provider_id The provider_id of the credit provider.
* @return {object} JQuery Promise.
*/
getProviderData: function (providerId) {
var providerUrl = '/api/credit/v1/providers/%s/';
return $.ajax({
url: _.sprintf(providerUrl, providerId),
type: 'GET',
dataType: 'json'
}).retry({times: 5, timeout: 2000, statusCodes: [404]});
},
/**
* 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.
* @return {object} Receipt template context.
*/
receiptContext: function (order) {
var self = this,
......@@ -172,13 +200,13 @@ var edx = edx || {};
length = order.lines.length;
for (var i = 0; i < length; i++) {
var line = order.lines[i],
attribute_values = _.filter(line.product.attribute_values, function (attribute) {
attributeValues = _.find(line.product.attribute_values, function (attribute) {
return attribute.name === 'course_key'
});
// This method assumes that all items in the order are related to a single course.
if (attribute_values.length > 0) {
return attribute_values[0]['value'];
if (attributeValues != undefined) {
return attributeValues['value'];
}
}
} else {
......@@ -196,7 +224,30 @@ var edx = edx || {};
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({
......@@ -204,3 +255,54 @@ var edx = edx || {};
});
})(jQuery, _, _.str, Backbone);
function completeOrder (event) {
var courseKey = $(event).data("course-key"),
username = $(event).data("username"),
providerId = $(event).data("provider"),
postData = {
'course_key': courseKey,
'username': username
},
errorContainer = $("#error-container");
analytics.track(
"edx.bi.credit.clicked_complete_credit",
{
category: "credit",
label: courseKey
}
);
$.ajax({
url: '/api/credit/v1/provider/' + providerId + '/request/',
type: 'POST',
headers: {
'X-CSRFToken': $.cookie('csrftoken')
},
data: JSON.stringify(postData) ,
context: this,
success: function(requestData){
var form = $('#complete-order-form');
$('input', form).remove();
form.attr( 'action', requestData.url );
form.attr( 'method', 'POST' );
_.each( requestData.parameters, function( value, key ) {
$('<input>').attr({
type: 'hidden',
name: key,
value: value
}).appendTo(form);
});
form.submit();
},
error: function(xhr){
errorContainer.removeClass("is-hidden");
errorContainer.removeClass("hidden");
}
});
}
......@@ -244,6 +244,42 @@
}
}
}
.report-receipt-provider {
@extend %ui-window;
border-top:3px solid $credit-color-base !important;
p {
padding: 0 $baseline $baseline/2 $baseline;
overflow: auto;
}
.bold_param {
padding: $baseline*.75 $baseline 0 $baseline;
font-weight: 600;
}
.bold_param span {
@include float(right);
}
div {
padding: 10px 20px;
margin: 0 0 15px;
overflow: auto;
}
div span {
@include float(right);
padding: 0;
margin: 0;
}
.complete-course {
@extend %btn-pl-primary-base;
@include float(right);
&.archived {
@extend %btn-pl-default-base;
}
}
.custom_instructions div {
@include float(left);
}
}
// ====================
......
......@@ -12,6 +12,9 @@ from django.utils.translation import ugettext as _
<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">
......@@ -29,17 +32,18 @@ from django.utils.translation import ugettext as _
<div id="error" class="wrapper-msg wrapper-msg-activate">
<div class=" msg msg-activate">
<i class="msg-icon icon fa fa-exclamation-triangle" aria-hidden="true"></i>
<div class="msg-content">
<h3 class="title">
<span class="sr">${error_summary}</span>
${error_summary}
</h3>
%if error_text:
%if error_text:
<div class="copy">
<p>${error_text}</p>
<br/>
</div>
%endif
%endif
<div class="msg">
<p>${for_help_text}</p>
</div>
......@@ -50,10 +54,12 @@ from django.utils.translation import ugettext as _
<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}'>
<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}'>
<h1>${_("Loading Order Data...")}</h1>
<span>${ _("Please wait while we retrieve your order details.") }</span>
</div>
<form id="complete-order-form"></form>
</section>
</div>
</%block>
<p class="bold_param">
<%= interpolate(gettext("You still need to visit %s to complete the credit process."), [display_name]) %>
<span><%= interpolate("<img src='%s' alt='%s'></image>", ["", display_name]) %></span>
</p>
<p>
<%= interpolate(gettext("In order to learn credit, %s requires learner to:"), [display_name]) %>
</p>
<div class="custom_instructions">
<div>
<%= fulfillment_instructions %>
<%= interpolate('<button data-provider="%s" data-course-key="%s" data-username="%s" class="complete-course" onClick=completeOrder(this)>%s</button>', [provider_id, course_key, username, gettext( "Complete Order")]) %>
</div>
</div>
......@@ -73,6 +73,9 @@
</p>
</div>
<% } %>
<div class="report report-receipt report-receipt-provider hidden" id="receipt-provider"></div>
</div>
</div>
<% } else { %>
......
......@@ -8,6 +8,7 @@ import pytz
import uuid
from django.db import transaction
from lms.djangoapps.django_comment_client.utils import JsonResponse
from openedx.core.djangoapps.credit.exceptions import (
UserIsNotEligible,
......@@ -39,7 +40,6 @@ def get_credit_providers(providers_list=None):
Returns:
list of credit providers represented as dictionaries
Response Values:
>>> get_credit_providers(['hogwarts'])
[
......@@ -60,10 +60,52 @@ def get_credit_providers(providers_list=None):
...
]
"""
return CreditProvider.get_credit_providers(providers_list=providers_list)
def get_credit_provider_info(request, provider_id): # pylint: disable=unused-argument
"""Retrieve the 'CreditProvider' model data against provided
credit provider.
Args:
provider_id (str): The identifier for the credit provider
Returns: 'CreditProvider' data dictionary
Example Usage:
>>> get_credit_provider_info("hogwarts")
{
"provider_id": "hogwarts",
"display_name": "Hogwarts School of Witchcraft and Wizardry",
"provider_url": "https://credit.example.com/",
"provider_status_url": "https://credit.example.com/status/",
"provider_description: "A new model for the Witchcraft and Wizardry School System.",
"enable_integration": False,
"fulfillment_instructions": "
<p>In order to fulfill credit, Hogwarts School of Witchcraft and Wizardry requires learners to:</p>
<ul>
<li>Sample instruction abc</li>
<li>Sample instruction xyz</li>
</ul>",
}
"""
credit_provider = CreditProvider.get_credit_provider(provider_id=provider_id)
credit_provider_data = {}
if credit_provider:
credit_provider_data = {
"provider_id": credit_provider.provider_id,
"display_name": credit_provider.display_name,
"provider_url": credit_provider.provider_url,
"provider_status_url": credit_provider.provider_status_url,
"provider_description": credit_provider.provider_description,
"enable_integration": credit_provider.enable_integration,
"fulfillment_instructions": credit_provider.fulfillment_instructions
}
return JsonResponse(credit_provider_data)
@transaction.commit_on_success
def create_credit_request(course_key, provider_id, username):
"""
......
......@@ -3,11 +3,16 @@ Tests for the API functions in the credit app.
"""
import datetime
import ddt
import json
from mock import patch
import pytz
import unittest
from django.conf import settings
from django.test import TestCase
from django.test.utils import override_settings
from django.db import connection, transaction
from django.core.urlresolvers import reverse, NoReverseMatch
from opaque_keys.edx.keys import CourseKey
......@@ -33,6 +38,8 @@ from student.tests.factories import UserFactory
TEST_CREDIT_PROVIDER_SECRET_KEY = "931433d583c84ca7ba41784bad3232e6"
from util.testing import UrlResetMixin
@override_settings(CREDIT_PROVIDER_SECRET_KEYS={
"hogwarts": TEST_CREDIT_PROVIDER_SECRET_KEY,
......@@ -691,3 +698,117 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase):
"""Check the user's credit status. """
statuses = api.get_credit_requests_for_user(self.USER_INFO["username"])
self.assertEqual(statuses[0]["status"], expected_status)
class CreditApiFeatureFlagTest(UrlResetMixin, TestCase):
"""
Base class to test the credit api urls.
"""
def setUp(self, **kwargs):
enable_credit_api = kwargs.get('enable_credit_api', False)
with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREDIT_API': enable_credit_api}):
super(CreditApiFeatureFlagTest, self).setUp('lms.urls')
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class CreditApiFeatureFlagDisabledTests(CreditApiFeatureFlagTest):
"""
Test Python API for credit provider api with feature flag
'ENABLE_CREDIT_API' disabled.
"""
PROVIDER_ID = "hogwarts"
def setUp(self):
super(CreditApiFeatureFlagDisabledTests, self).setUp(enable_credit_api=False)
def test_get_credit_provider_details(self):
"""
Test that 'get_provider_info' api url not found.
"""
with self.assertRaises(NoReverseMatch):
reverse('credit:get_provider_info', args=[self.PROVIDER_ID])
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class CreditApiFeatureFlagEnabledTests(CreditApiFeatureFlagTest, CreditApiTestBase):
"""
Test Python API for credit provider api with feature flag
'ENABLE_CREDIT_API' enabled.
"""
USER_INFO = {
"username": "bob",
"email": "bob@example.com",
"full_name": "Bob",
"mailing_address": "123 Fake Street, Cambridge MA",
"country": "US",
}
FINAL_GRADE = 0.95
def setUp(self):
super(CreditApiFeatureFlagEnabledTests, self).setUp(enable_credit_api=True)
self.user = UserFactory(
username=self.USER_INFO['username'],
email=self.USER_INFO['email'],
)
self.user.profile.name = self.USER_INFO['full_name']
self.user.profile.mailing_address = self.USER_INFO['mailing_address']
self.user.profile.country = self.USER_INFO['country']
self.user.profile.save()
# By default, configure the database so that there is a single
# credit requirement that the user has satisfied (minimum grade)
self._configure_credit()
def test_get_credit_provider_details(self):
"""Test that credit api method 'test_get_credit_provider_details'
returns dictionary data related to provided credit provider.
"""
expected_result = {
"provider_id": self.PROVIDER_ID,
"display_name": self.PROVIDER_NAME,
"provider_url": self.PROVIDER_URL,
"provider_status_url": self.PROVIDER_STATUS_URL,
"provider_description": self.PROVIDER_DESCRIPTION,
"enable_integration": self.ENABLE_INTEGRATION,
"fulfillment_instructions": self.FULFILLMENT_INSTRUCTIONS,
}
path = reverse('credit:get_provider_info', kwargs={'provider_id': self.PROVIDER_ID})
result = self.client.get(path)
result = json.loads(result.content)
self.assertEqual(result, expected_result)
# now test that user gets empty dict for non existent credit provider
path = reverse('credit:get_provider_info', kwargs={'provider_id': 'fake_provider_id'})
result = self.client.get(path)
result = json.loads(result.content)
self.assertEqual(result, {})
def _configure_credit(self):
"""
Configure a credit course and its requirements.
By default, add a single requirement (minimum grade)
that the user has satisfied.
"""
credit_course = self.add_credit_course()
requirement = CreditRequirement.objects.create(
course=credit_course,
namespace="grade",
name="grade",
active=True
)
status = CreditRequirementStatus.objects.create(
username=self.USER_INFO["username"],
requirement=requirement,
)
status.status = "satisfied"
status.reason = {"final_grade": self.FINAL_GRADE}
status.save()
CreditEligibility.objects.create(
username=self.USER_INFO['username'],
course=CreditCourse.objects.get(course_key=self.course_key)
)
......@@ -3,6 +3,7 @@ URLs for the credit app.
"""
from django.conf.urls import patterns, url
from .api.provider import get_credit_provider_info
from .views import create_credit_request, credit_provider_callback, get_providers_detail, get_eligibility_for_user
PROVIDER_ID_PATTERN = r'(?P<provider_id>[^/]+)'
......@@ -11,6 +12,11 @@ urlpatterns = patterns(
'',
url(
r"^v1/providers/(?P<provider_id>[^/]+)/$",
get_credit_provider_info,
name="get_provider_info"
),
url(
r"^v1/providers/$",
get_providers_detail,
name="providers_detail"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment