From 4cff92b0ad3a01c27c00137604fbf7699df67198 Mon Sep 17 00:00:00 2001 From: "hasnain.naveed" Date: Thu, 9 Apr 2020 18:12:24 +0500 Subject: [PATCH 1/2] ENT-2735 | Added the payment message on track selection for learners in subsidy offer. --- common/djangoapps/course_modes/helpers.py | 20 +++++++++++ .../course_modes/tests/test_views.py | 35 +++++++++++++++++++ common/djangoapps/course_modes/views.py | 10 ++++-- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/common/djangoapps/course_modes/helpers.py b/common/djangoapps/course_modes/helpers.py index 42e33f6d77..6b41234bf9 100644 --- a/common/djangoapps/course_modes/helpers.py +++ b/common/djangoapps/course_modes/helpers.py @@ -4,8 +4,12 @@ import six from django.utils.translation import ugettext_lazy as _ +from requests.exceptions import ConnectionError, Timeout # pylint: disable=redefined-builtin +from slumber.exceptions import SlumberBaseException + from course_modes.models import CourseMode from student.helpers import VERIFY_STATUS_APPROVED, VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED +from openedx.core.djangoapps.commerce.utils import ecommerce_api_client DISPLAY_VERIFIED = "verified" DISPLAY_HONOR = "honor" @@ -83,3 +87,19 @@ def _enrollment_mode_display(enrollment_mode, verification_status, course_id): display_mode = enrollment_mode return display_mode + + +def get_course_final_price(user, sku, min_price): + """ + Return the course's discounted price for a user if user is entitled other None. + """ + price_details = {} + try: + price_details = ecommerce_api_client(user).baskets.calculate.get( + sku=[sku], + username=user.username, + ) + except (SlumberBaseException, ConnectionError, Timeout) as exc: # pylint: disable=unused-variable + pass + price = price_details.get('total_incl_tax', min_price) + return price if price != min_price else None diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py index f3257a353b..8cb92484c1 100644 --- a/common/djangoapps/course_modes/tests/test_views.py +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -198,6 +198,41 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest else: self.assertNotContains(response, "Credit") + @httpretty.activate + @patch('course_modes.views.is_enterprise_learner') + @patch('course_modes.views.get_course_final_price') + @ddt.data( + (1.0, True), + (50.0, False), + (0.0, True), + (None, False), + ) + @ddt.unpack + def test_display_after_discounted_price( + self, + discounted_price, + is_enterprise_learner, + mock_get_course_final_price, + mock_is_enterprise_learner + ): + # Create the course modes + CourseModeFactory.create(mode_slug='audit', course_id=self.course.id) + verified_mode = CourseModeFactory.create(mode_slug='verified', course_id=self.course.id, sku='dummy') + CourseEnrollmentFactory( + is_active=True, + course_id=self.course.id, + user=self.user + ) + + mock_is_enterprise_learner.return_value = is_enterprise_learner + mock_get_course_final_price.return_value = discounted_price + url = reverse('course_modes_choose', args=[six.text_type(self.course.id)]) + response = self.client.get(url) + + price = discounted_price if is_enterprise_learner else verified_mode.min_price + # response will have after discounted price. + self.assertContains(response, price) + @httpretty.activate @ddt.data(True, False) def test_congrats_on_enrollment_message(self, create_enrollment): diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index cf039e342d..2e76af77a1 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -26,6 +26,7 @@ from opaque_keys.edx.keys import CourseKey from six import text_type from course_modes.models import CourseMode +from common.djangoapps.course_modes.helpers import get_course_final_price from edxmako.shortcuts import render_to_response from lms.djangoapps.commerce.utils import EcommerceService from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context @@ -34,7 +35,7 @@ from openedx.core.djangoapps.embargo import api as embargo_api from openedx.core.djangoapps.enrollments.permissions import ENROLL_IN_COURSE from openedx.features.content_type_gating.models import ContentTypeGatingConfig from openedx.features.course_duration_limits.models import CourseDurationLimitConfig -from openedx.features.discounts.applicability import discount_percentage +from openedx.features.enterprise_support.utils import is_enterprise_learner from student.models import CourseEnrollment from util.db import outer_atomic from xmodule.modulestore.django import modulestore @@ -202,13 +203,18 @@ class ChooseModeView(View): for x in verified_mode.suggested_prices.split(",") if x.strip() ] + price_after_discount = None price_before_discount = verified_mode.min_price + if is_enterprise_learner(request.user) and verified_mode.sku: + price_after_discount = get_course_final_price(request.user, verified_mode.sku, price_before_discount) context["currency"] = verified_mode.currency.upper() context["currency_symbol"] = get_currency_symbol(verified_mode.currency.upper()) - context["min_price"] = price_before_discount + context["min_price"] = price_after_discount if price_after_discount is not None else price_before_discount context["verified_name"] = verified_mode.name context["verified_description"] = verified_mode.description + if price_after_discount is not None: + context["price_before_discount"] = price_before_discount if verified_mode.sku: context["use_ecommerce_payment_flow"] = ecommerce_service.is_enabled(request.user) From 0d85a4ef142e149891a2ac71f071e2cab419c88c Mon Sep 17 00:00:00 2001 From: "hasnain.naveed" Date: Mon, 13 Apr 2020 13:47:56 +0500 Subject: [PATCH 2/2] Feedback. --- common/djangoapps/course_modes/helpers.py | 7 +++---- .../course_modes/tests/test_views.py | 18 ++++++++---------- common/djangoapps/course_modes/views.py | 14 ++++++++------ 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/common/djangoapps/course_modes/helpers.py b/common/djangoapps/course_modes/helpers.py index 6b41234bf9..942b4a22b4 100644 --- a/common/djangoapps/course_modes/helpers.py +++ b/common/djangoapps/course_modes/helpers.py @@ -89,9 +89,9 @@ def _enrollment_mode_display(enrollment_mode, verification_status, course_id): return display_mode -def get_course_final_price(user, sku, min_price): +def get_course_final_price(user, sku, course_price): """ - Return the course's discounted price for a user if user is entitled other None. + Return the course's discounted price for a user if user is eligible for any otherwise return course original price. """ price_details = {} try: @@ -101,5 +101,4 @@ def get_course_final_price(user, sku, min_price): ) except (SlumberBaseException, ConnectionError, Timeout) as exc: # pylint: disable=unused-variable pass - price = price_details.get('total_incl_tax', min_price) - return price if price != min_price else None + return price_details.get('total_incl_tax', course_price) diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py index 8cb92484c1..8a4eac0506 100644 --- a/common/djangoapps/course_modes/tests/test_views.py +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -199,7 +199,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest self.assertNotContains(response, "Credit") @httpretty.activate - @patch('course_modes.views.is_enterprise_learner') + @patch('course_modes.views.enterprise_customer_for_request') @patch('course_modes.views.get_course_final_price') @ddt.data( (1.0, True), @@ -211,12 +211,10 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest def test_display_after_discounted_price( self, discounted_price, - is_enterprise_learner, + is_enterprise_enabled, mock_get_course_final_price, - mock_is_enterprise_learner + mock_enterprise_customer_for_request ): - # Create the course modes - CourseModeFactory.create(mode_slug='audit', course_id=self.course.id) verified_mode = CourseModeFactory.create(mode_slug='verified', course_id=self.course.id, sku='dummy') CourseEnrollmentFactory( is_active=True, @@ -224,14 +222,14 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest user=self.user ) - mock_is_enterprise_learner.return_value = is_enterprise_learner + mock_enterprise_customer_for_request.return_value = is_enterprise_enabled mock_get_course_final_price.return_value = discounted_price - url = reverse('course_modes_choose', args=[six.text_type(self.course.id)]) + url = reverse('course_modes_choose', args=[self.course.id]) response = self.client.get(url) - price = discounted_price if is_enterprise_learner else verified_mode.min_price - # response will have after discounted price. - self.assertContains(response, price) + if is_enterprise_enabled: + self.assertContains(response, discounted_price) + self.assertContains(response, verified_mode.min_price) @httpretty.activate @ddt.data(True, False) diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 2e76af77a1..2015fdeca4 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -35,7 +35,7 @@ from openedx.core.djangoapps.embargo import api as embargo_api from openedx.core.djangoapps.enrollments.permissions import ENROLL_IN_COURSE from openedx.features.content_type_gating.models import ContentTypeGatingConfig from openedx.features.course_duration_limits.models import CourseDurationLimitConfig -from openedx.features.enterprise_support.utils import is_enterprise_learner +from openedx.features.enterprise_support.api import enterprise_customer_for_request from student.models import CourseEnrollment from util.db import outer_atomic from xmodule.modulestore.django import modulestore @@ -203,17 +203,19 @@ class ChooseModeView(View): for x in verified_mode.suggested_prices.split(",") if x.strip() ] - price_after_discount = None price_before_discount = verified_mode.min_price - if is_enterprise_learner(request.user) and verified_mode.sku: - price_after_discount = get_course_final_price(request.user, verified_mode.sku, price_before_discount) + course_price = price_before_discount + enterprise_customer = enterprise_customer_for_request(request) + if enterprise_customer and verified_mode.sku: + course_price = get_course_final_price(request.user, verified_mode.sku, price_before_discount) context["currency"] = verified_mode.currency.upper() context["currency_symbol"] = get_currency_symbol(verified_mode.currency.upper()) - context["min_price"] = price_after_discount if price_after_discount is not None else price_before_discount + context["min_price"] = course_price context["verified_name"] = verified_mode.name context["verified_description"] = verified_mode.description - if price_after_discount is not None: + # if course_price is equal to price_before_discount then user doesn't entitle to any discount. + if course_price != price_before_discount: context["price_before_discount"] = price_before_discount if verified_mode.sku: