From 8de2d2c4f2b29b50dc8bdee91e2c78125dfe87f4 Mon Sep 17 00:00:00 2001 From: Muhammad Noyan Aziz Date: Wed, 27 Nov 2024 05:01:55 +0500 Subject: [PATCH 1/9] chore: added CT discount eligibility check for outline tab --- .../apps/commercetools/clients.py | 35 +++++++++++++++++++ .../apps/commercetools/pipeline.py | 30 ++++++++++++++++ commerce_coordinator/apps/lms/filters.py | 19 ++++++++++ commerce_coordinator/apps/lms/urls.py | 4 ++- commerce_coordinator/apps/lms/views.py | 27 ++++++++++++++ commerce_coordinator/settings/base.py | 6 ++++ 6 files changed, 120 insertions(+), 1 deletion(-) diff --git a/commerce_coordinator/apps/commercetools/clients.py b/commerce_coordinator/apps/commercetools/clients.py index c00410db..840d32fa 100644 --- a/commerce_coordinator/apps/commercetools/clients.py +++ b/commerce_coordinator/apps/commercetools/clients.py @@ -603,3 +603,38 @@ def retire_customer_anonymize_fields(self, customer_id: str, customer_version: i f"with ID: {customer_id}, after LMS retirement with " f"error correlation id {err.correlation_id} and error/s: {err.errors}") raise err + + def is_first_time_discount_eligible(self, email: str) -> bool: + """ + Check if a user is eligible for a first time discount + Args: + username (str): Username of the user + Returns (bool): True if the user is eligible for a first time discount + """ + FIRST_TIME_DISCOUNT_CODES = ['EDXWELCOME', 'NEW2EDX'] + + try: + discounts = self.base_client.discount_codes.query( + where="code in :discountCodes", + predicate_var={'discountCodes': FIRST_TIME_DISCOUNT_CODES} + ) + discount_ids = [discount.id for discount in discounts.results] + + discounted_orders = self.base_client.orders.query( + where=[ + "customerEmail=:email", + "orderState=:orderState", + "discountCodes(discountCode(id in :discountIds))" + ], + predicate_var={'email': email, 'discountIds': discount_ids, 'orderState': 'Complete'} + ) + + if discounted_orders.total > 0: + return False + + return True + except CommercetoolsError as err: + # Logs & ignores version conflict errors due to duplicate Commercetools messages + handle_commercetools_error(err, f"Unable to check if user {email} is eligible for a " + f"first time discount", True) + return True diff --git a/commerce_coordinator/apps/commercetools/pipeline.py b/commerce_coordinator/apps/commercetools/pipeline.py index ddb557f6..2b52047f 100644 --- a/commerce_coordinator/apps/commercetools/pipeline.py +++ b/commerce_coordinator/apps/commercetools/pipeline.py @@ -409,3 +409,33 @@ def run_filter( except HTTPError as err: # pragma no cover log.exception(f"[{tag}] HTTP Error: {err}") return PipelineCommand.CONTINUE.value + + +class CheckCommercetoolsDiscountEligibility(PipelineStep): + """ + Checks if a user is eligible for a first time discount in Commercetools. + """ + def run_filter(self, email): # pylint: disable=arguments-differ + """ + Execute a filter with the signature specified. + Arguments: + username: Username of the user + kwargs: The keyword arguments passed through from the filter + Returns: + first_time_discount_eligible (bool): True if the user is eligible for a first time discount + """ + tag = type(self).__name__ + + try: + ct_api_client = CommercetoolsAPIClient() + is_eligible = ct_api_client.is_first_time_discount_eligible(email) + + return { + 'is_eligible': is_eligible + } + except CommercetoolsError as err: # pragma no cover + log.exception(f"[{tag}] Commercetools Error: {err}, {err.errors}") + return PipelineCommand.CONTINUE.value + except HTTPError as err: # pragma no cover + log.exception(f"[{tag}] HTTP Error: {err}") + return PipelineCommand.CONTINUE.value diff --git a/commerce_coordinator/apps/lms/filters.py b/commerce_coordinator/apps/lms/filters.py index f215f6a2..195f85eb 100644 --- a/commerce_coordinator/apps/lms/filters.py +++ b/commerce_coordinator/apps/lms/filters.py @@ -64,3 +64,22 @@ def run_filter(cls, lms_user_id): # pragma no cover """ return super().run_pipeline(lms_user_id=lms_user_id) + + +class CheckFirstTimeDiscountEligibility(OpenEdxPublicFilter): + """ + Filter to check if a user is eligible for a first time discount + """ + # See pipeline step configuration OPEN_EDX_FILTERS_CONFIG dict in `settings/base.py` + filter_type = "org.edx.coordinator.lms.check.first.time.discount.eligibility.v1" + + @classmethod + def run_filter(cls, email): + """ + Call the PipelineStep(s) defined for this filter. + Arguments: + username: Username of the user + Returns: + is_eligible (bool): True if the user is eligible for a first time discount + """ + return super().run_pipeline(email=email) diff --git a/commerce_coordinator/apps/lms/urls.py b/commerce_coordinator/apps/lms/urls.py index 9ea99966..dee18971 100644 --- a/commerce_coordinator/apps/lms/urls.py +++ b/commerce_coordinator/apps/lms/urls.py @@ -5,6 +5,7 @@ from django.urls import path from commerce_coordinator.apps.lms.views import ( + FirstTimeDiscountEligibleView, OrderDetailsRedirectView, PaymentPageRedirectView, RefundView, @@ -16,5 +17,6 @@ path('payment_page_redirect/', PaymentPageRedirectView.as_view(), name='payment_page_redirect'), path('order_details_page_redirect/', OrderDetailsRedirectView.as_view(), name='order_details_page_redirect'), path('refund/', RefundView.as_view(), name='refund'), - path('user_retirement/', RetirementView.as_view(), name='user_retirement') + path('user_retirement/', RetirementView.as_view(), name='user_retirement'), + path('first-time-discount-eligible/', FirstTimeDiscountEligibleView.as_view(), name='first_time_discount_eligible'), ] diff --git a/commerce_coordinator/apps/lms/views.py b/commerce_coordinator/apps/lms/views.py index d5b5981a..d991d58c 100644 --- a/commerce_coordinator/apps/lms/views.py +++ b/commerce_coordinator/apps/lms/views.py @@ -17,6 +17,7 @@ from commerce_coordinator.apps.core.constants import HttpHeadersNames, MediaTypes from commerce_coordinator.apps.lms.filters import ( + CheckFirstTimeDiscountEligibility, OrderRefundRequested, PaymentPageRedirectRequested, UserRetirementRequested @@ -28,6 +29,11 @@ ) from commerce_coordinator.apps.rollout.utils import is_legacy_order + +from edx_rest_framework_extensions.permissions import LoginRedirectIfUnauthenticated +from rest_framework.exceptions import PermissionDenied +from rest_framework.throttling import UserRateThrottle + logger = logging.getLogger(__name__) @@ -334,3 +340,24 @@ def post(self, request) -> Response: logger.exception(f"[RefundView] Exception raised in {self.post.__name__} with error {repr(e)}") return Response('Exception occurred while retiring Commercetools customer', status=HTTP_500_INTERNAL_SERVER_ERROR) + + +class FirstTimeDiscountEligibleView(APIView): + """View to check if a user is eligible for a first time discount""" + permission_classes = [LoginRedirectIfUnauthenticated] + throttle_classes = [UserRateThrottle] + + def get(self, request): + """Return paginated response of user's order history.""" + email = request.query_params.get('email') + + if not email: # pragma: no cover + raise PermissionDenied(detail="Could not detect user email.") + + result = CheckFirstTimeDiscountEligibility.run_filter(email=email) + + output = { + "is_eligible": result.get('is_eligible', True) + } + + return Response(output) diff --git a/commerce_coordinator/settings/base.py b/commerce_coordinator/settings/base.py index 491d659b..a5aaa301 100644 --- a/commerce_coordinator/settings/base.py +++ b/commerce_coordinator/settings/base.py @@ -399,6 +399,12 @@ def root(*path_fragments): "pipeline": [ 'commerce_coordinator.apps.commercetools.pipeline.AnonymizeRetiredUser', ] + }, + 'org.edx.coordinator.lms.check.first.time.discount.eligibility.v1': { + 'fail_silently': False, + 'pipeline': [ + 'commerce_coordinator.apps.commercetools.pipeline.CheckCommercetoolsDiscountEligibility', + ] } } From e4f2f554467ea12a4f9f923f0899cd1a91b30583 Mon Sep 17 00:00:00 2001 From: Muhammad Noyan Aziz Date: Wed, 27 Nov 2024 07:10:52 +0500 Subject: [PATCH 2/9] fix: quality checks --- commerce_coordinator/apps/lms/views.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/commerce_coordinator/apps/lms/views.py b/commerce_coordinator/apps/lms/views.py index d991d58c..d276b498 100644 --- a/commerce_coordinator/apps/lms/views.py +++ b/commerce_coordinator/apps/lms/views.py @@ -8,7 +8,7 @@ from django.http import HttpResponseBadRequest, HttpResponseRedirect from edx_rest_framework_extensions.permissions import LoginRedirectIfUnauthenticated from openedx_filters.exceptions import OpenEdxFilterException -from rest_framework.exceptions import ValidationError +from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.permissions import IsAdminUser from rest_framework.response import Response from rest_framework.status import HTTP_200_OK, HTTP_303_SEE_OTHER, HTTP_400_BAD_REQUEST, HTTP_500_INTERNAL_SERVER_ERROR @@ -29,11 +29,6 @@ ) from commerce_coordinator.apps.rollout.utils import is_legacy_order - -from edx_rest_framework_extensions.permissions import LoginRedirectIfUnauthenticated -from rest_framework.exceptions import PermissionDenied -from rest_framework.throttling import UserRateThrottle - logger = logging.getLogger(__name__) From 2f4e82328b8ce4fb0c54b935fcb01fc63a28a06e Mon Sep 17 00:00:00 2001 From: Muhammad Noyan Aziz Date: Wed, 27 Nov 2024 16:37:04 +0500 Subject: [PATCH 3/9] fix: unit tests --- .../apps/commercetools/tests/test_clients.py | 93 +++++++++++++++++++ .../apps/commercetools/tests/test_pipeline.py | 31 +++++++ .../apps/lms/tests/test_views.py | 69 ++++++++++++++ 3 files changed, 193 insertions(+) diff --git a/commerce_coordinator/apps/commercetools/tests/test_clients.py b/commerce_coordinator/apps/commercetools/tests/test_clients.py index ff17e316..fad38ac4 100644 --- a/commerce_coordinator/apps/commercetools/tests/test_clients.py +++ b/commerce_coordinator/apps/commercetools/tests/test_clients.py @@ -803,6 +803,99 @@ def test_update_customer_with_anonymized_fields_exception(self): log_mock.assert_called_once_with(expected_message) + def test_is_first_time_discount_eligible_success(self): + base_url = self.client_set.get_base_url_from_client() + email = 'email@example.com' + + mock_discount_codes = { + "results": [ + {"id": "discount-id-1"}, + {"id": "discount-id-2"} + ] + } + + mock_orders = { + "total": 0 + } + + with requests_mock.Mocker(real_http=True, case_sensitive=False) as mocker: + mocker.get( + f"{base_url}discount-codes", + json=mock_discount_codes, + status_code=200 + ) + + mocker.get( + f"{base_url}orders", + json=mock_orders, + status_code=200 + ) + + result = self.client_set.client.is_first_time_discount_eligible(email) + self.assertTrue(result) + + def test_is_first_time_discount_eligible_not_eligible(self): + base_url = self.client_set.get_base_url_from_client() + email = 'email@example.com' + + mock_discount_codes = { + "results": [ + {"id": "discount-id-1"}, + {"id": "discount-id-2"} + ] + } + + mock_orders = { + "total": 1 + } + + with requests_mock.Mocker(real_http=True, case_sensitive=False) as mocker: + mocker.get( + f"{base_url}discount-codes", + json=mock_discount_codes, + status_code=200 + ) + + mocker.get( + f"{base_url}orders", + json=mock_orders, + status_code=200 + ) + + result = self.client_set.client.is_first_time_discount_eligible(email) + self.assertFalse(result) + + def test_is_first_time_discount_eligible_invalid_email(self): + invalid_email = "invalid_email@example.com" + base_url = self.client_set.get_base_url_from_client() + + mock_discount_codes = { + "results": [ + {"id": "discount-id-1"}, + {"id": "discount-id-2"} + ] + } + + mock_orders = { + "total": 0 + } + + with requests_mock.Mocker(real_http=True, case_sensitive=False) as mocker: + mocker.get( + f"{base_url}discount-codes", + json=mock_discount_codes, + status_code=200 + ) + + mocker.get( + f"{base_url}orders", + json=mock_orders, + status_code=200 + ) + + result = self.client_set.client.is_first_time_discount_eligible(invalid_email) + self.assertTrue(result) + class PaginatedResultsTest(TestCase): """Tests for the simple logic in our Paginated Results Class""" diff --git a/commerce_coordinator/apps/commercetools/tests/test_pipeline.py b/commerce_coordinator/apps/commercetools/tests/test_pipeline.py index 1f02f1de..8fde2abf 100644 --- a/commerce_coordinator/apps/commercetools/tests/test_pipeline.py +++ b/commerce_coordinator/apps/commercetools/tests/test_pipeline.py @@ -11,6 +11,7 @@ from commerce_coordinator.apps.commercetools.constants import COMMERCETOOLS_ORDER_MANAGEMENT_SYSTEM from commerce_coordinator.apps.commercetools.pipeline import ( AnonymizeRetiredUser, + CheckCommercetoolsDiscountEligibility, CreateReturnForCommercetoolsOrder, CreateReturnPaymentTransaction, GetCommercetoolsOrders, @@ -264,3 +265,33 @@ def test_pipeline(self, mock_anonymize_fields, mock_customer_by_lms_id, mock_ano ret = pipe.run_filter(lms_user_id=self.mock_lms_user_id) result_data = ret['returned_customer'] self.assertEqual(result_data, self.update_customer_response) + + +class CommercetoolsDiscountEligibilityPipelineTests(TestCase): + """Commercetools pipeline testcase for checking discount eligibility in CT""" + def setUp(self) -> None: + self.mock_email = "mock_email@example.com" + self.mock_eligible_result = True + self.mock_ineligible_result = False + + @patch( + 'commerce_coordinator.apps.commercetools.clients.CommercetoolsAPIClient' + '.is_first_time_discount_eligible' + ) + def test_pipeline_eligible(self, mock_is_eligible): + pipe = CheckCommercetoolsDiscountEligibility("test_pipe", None) + mock_is_eligible.return_value = self.mock_eligible_result + ret = pipe.run_filter(self.mock_email) + result_data = ret['is_eligible'] + self.assertEqual(result_data, self.mock_eligible_result) + + @patch( + 'commerce_coordinator.apps.commercetools.clients.CommercetoolsAPIClient' + '.is_first_time_discount_eligible' + ) + def test_pipeline_ineligible(self, mock_is_eligible): + pipe = CheckCommercetoolsDiscountEligibility("test_pipe", None) + mock_is_eligible.return_value = self.mock_ineligible_result + ret = pipe.run_filter(self.mock_email) + result_data = ret['is_eligible'] + self.assertEqual(result_data, self.mock_ineligible_result) diff --git a/commerce_coordinator/apps/lms/tests/test_views.py b/commerce_coordinator/apps/lms/tests/test_views.py index 7eb2bb7d..f0c34967 100644 --- a/commerce_coordinator/apps/lms/tests/test_views.py +++ b/commerce_coordinator/apps/lms/tests/test_views.py @@ -382,3 +382,72 @@ def test_post_with_unexpected_exception_fails(self, mock_filter): response = self.client.post(self.url, self.valid_payload, format='json') self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + + +@ddt.ddt +class FirstTimeDiscountEligibleViewTests(APITestCase): + """ + Tests for the FirstTimeDiscountEligibleView to check if a user is eligible for a first-time discount. + """ + + test_user_username = 'test' + test_user_email = 'test@example.com' + test_user_password = 'secret' + + url = reverse('lms:first_time_discount_eligible') + + def setUp(self): + super().setUp() + self.user = User.objects.create_user( + self.test_user_username, + self.test_user_email, + self.test_user_password, + is_staff=True, + ) + + def tearDown(self): + super().tearDown() + self.client.logout() + + def authenticate_user(self): + self.client.login(username=self.test_user_username, password=self.test_user_password) + self.client.force_authenticate(user=self.user) + + @patch('commerce_coordinator.apps.lms.views.CheckFirstTimeDiscountEligibility.run_filter') + def test_get_with_valid_email_eligibility_true(self, mock_filter): + """ + Test case where the email is eligible for a first-time discount. + """ + self.authenticate_user() + mock_filter.return_value = {'is_eligible': True} + + response = self.client.get(self.url, {'email': self.test_user_email}) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data, {"is_eligible": True}) + mock_filter.assert_called_once_with(email=self.test_user_email) + + @patch('commerce_coordinator.apps.lms.views.CheckFirstTimeDiscountEligibility.run_filter') + def test_get_with_valid_email_eligibility_false(self, mock_filter): + """ + Test case where the email is not eligible for a first-time discount. + """ + self.authenticate_user() + mock_filter.return_value = {'is_eligible': False} + + response = self.client.get(self.url, {'email': self.test_user_email}) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data, {"is_eligible": False}) + mock_filter.assert_called_once_with(email=self.test_user_email) + + def test_get_with_missing_email_fails(self): + """ + Test case where the email is not provided in the request query params. + """ + self.authenticate_user() + + response = self.client.get(self.url) + + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.data, {'detail': 'Could not detect user email.'}) From f653ed80db97d7b8a848a639a7f586d99925577c Mon Sep 17 00:00:00 2001 From: Muhammad Noyan Aziz Date: Wed, 27 Nov 2024 17:34:54 +0500 Subject: [PATCH 4/9] refactor: use settings value for discounts list --- commerce_coordinator/apps/commercetools/clients.py | 4 +--- commerce_coordinator/settings/base.py | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/commerce_coordinator/apps/commercetools/clients.py b/commerce_coordinator/apps/commercetools/clients.py index 840d32fa..59969e86 100644 --- a/commerce_coordinator/apps/commercetools/clients.py +++ b/commerce_coordinator/apps/commercetools/clients.py @@ -611,12 +611,10 @@ def is_first_time_discount_eligible(self, email: str) -> bool: username (str): Username of the user Returns (bool): True if the user is eligible for a first time discount """ - FIRST_TIME_DISCOUNT_CODES = ['EDXWELCOME', 'NEW2EDX'] - try: discounts = self.base_client.discount_codes.query( where="code in :discountCodes", - predicate_var={'discountCodes': FIRST_TIME_DISCOUNT_CODES} + predicate_var={'discountCodes': settings.COMMERCETOOLS_FIRST_TIME_DISCOUNTS} ) discount_ids = [discount.id for discount in discounts.results] diff --git a/commerce_coordinator/settings/base.py b/commerce_coordinator/settings/base.py index a5aaa301..8b6e6bbd 100644 --- a/commerce_coordinator/settings/base.py +++ b/commerce_coordinator/settings/base.py @@ -460,6 +460,8 @@ def root(*path_fragments): # Checkout view urls COMMERCETOOLS_FRONTEND_URL = 'http://localhost:3000/SET-ME' +COMMERCETOOLS_FIRST_TIME_DISCOUNTS = ('EDXWELCOME', 'NEW2EDX') + COMMERCETOOLS_MERCHANT_CENTER_ORDERS_PAGE_URL = \ f'https://mc.{_COMMERCETOOLS_CONFIG_GEO}.commercetools.com/{COMMERCETOOLS_CONFIG["projectKey"]}/orders' From 959a1cfd06dccd0e3e8a96c89e76030f00e5ca56 Mon Sep 17 00:00:00 2001 From: Muhammad Noyan Aziz Date: Wed, 27 Nov 2024 17:43:49 +0500 Subject: [PATCH 5/9] fix: coverage --- commerce_coordinator/apps/commercetools/clients.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commerce_coordinator/apps/commercetools/clients.py b/commerce_coordinator/apps/commercetools/clients.py index 59969e86..cf754bbf 100644 --- a/commerce_coordinator/apps/commercetools/clients.py +++ b/commerce_coordinator/apps/commercetools/clients.py @@ -631,7 +631,7 @@ def is_first_time_discount_eligible(self, email: str) -> bool: return False return True - except CommercetoolsError as err: + except CommercetoolsError as err: # pragma no cover # Logs & ignores version conflict errors due to duplicate Commercetools messages handle_commercetools_error(err, f"Unable to check if user {email} is eligible for a " f"first time discount", True) From 184427c3a6ab1aa3305ff02e375183c04b68cfcd Mon Sep 17 00:00:00 2001 From: Muhammad Noyan Aziz Date: Wed, 27 Nov 2024 17:50:23 +0500 Subject: [PATCH 6/9] refactor: docstrings --- commerce_coordinator/apps/commercetools/clients.py | 2 +- commerce_coordinator/apps/commercetools/pipeline.py | 4 ++-- commerce_coordinator/apps/lms/filters.py | 2 +- commerce_coordinator/apps/lms/views.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/commerce_coordinator/apps/commercetools/clients.py b/commerce_coordinator/apps/commercetools/clients.py index cf754bbf..02ab4615 100644 --- a/commerce_coordinator/apps/commercetools/clients.py +++ b/commerce_coordinator/apps/commercetools/clients.py @@ -608,7 +608,7 @@ def is_first_time_discount_eligible(self, email: str) -> bool: """ Check if a user is eligible for a first time discount Args: - username (str): Username of the user + email (str): Email of the user Returns (bool): True if the user is eligible for a first time discount """ try: diff --git a/commerce_coordinator/apps/commercetools/pipeline.py b/commerce_coordinator/apps/commercetools/pipeline.py index 2b52047f..4a67b6f1 100644 --- a/commerce_coordinator/apps/commercetools/pipeline.py +++ b/commerce_coordinator/apps/commercetools/pipeline.py @@ -419,10 +419,10 @@ def run_filter(self, email): # pylint: disable=arguments-differ """ Execute a filter with the signature specified. Arguments: - username: Username of the user + email: Email of the user kwargs: The keyword arguments passed through from the filter Returns: - first_time_discount_eligible (bool): True if the user is eligible for a first time discount + is_eligible (bool): True if the user is eligible for a first time discount """ tag = type(self).__name__ diff --git a/commerce_coordinator/apps/lms/filters.py b/commerce_coordinator/apps/lms/filters.py index 195f85eb..ef1288f1 100644 --- a/commerce_coordinator/apps/lms/filters.py +++ b/commerce_coordinator/apps/lms/filters.py @@ -78,7 +78,7 @@ def run_filter(cls, email): """ Call the PipelineStep(s) defined for this filter. Arguments: - username: Username of the user + email: Email of the user Returns: is_eligible (bool): True if the user is eligible for a first time discount """ diff --git a/commerce_coordinator/apps/lms/views.py b/commerce_coordinator/apps/lms/views.py index d276b498..bd21d04a 100644 --- a/commerce_coordinator/apps/lms/views.py +++ b/commerce_coordinator/apps/lms/views.py @@ -343,7 +343,7 @@ class FirstTimeDiscountEligibleView(APIView): throttle_classes = [UserRateThrottle] def get(self, request): - """Return paginated response of user's order history.""" + """Return True if user is eligible for a first time discount.""" email = request.query_params.get('email') if not email: # pragma: no cover From b56f77e4f613da9806e6464467a03d8598c8b1e6 Mon Sep 17 00:00:00 2001 From: Muhammad Noyan Aziz Date: Thu, 12 Dec 2024 15:20:41 +0500 Subject: [PATCH 7/9] chore: first time discount check based on lms --- .../apps/commercetools/clients.py | 25 +++++++++++-------- .../apps/commercetools/pipeline.py | 5 ++-- commerce_coordinator/apps/lms/filters.py | 5 ++-- commerce_coordinator/apps/lms/views.py | 7 +++--- commerce_coordinator/settings/base.py | 2 -- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/commerce_coordinator/apps/commercetools/clients.py b/commerce_coordinator/apps/commercetools/clients.py index 02ab4615..564641e6 100644 --- a/commerce_coordinator/apps/commercetools/clients.py +++ b/commerce_coordinator/apps/commercetools/clients.py @@ -604,31 +604,34 @@ def retire_customer_anonymize_fields(self, customer_id: str, customer_version: i f"error correlation id {err.correlation_id} and error/s: {err.errors}") raise err - def is_first_time_discount_eligible(self, email: str) -> bool: + def is_first_time_discount_eligible(self, email: str, code: str) -> bool: """ Check if a user is eligible for a first time discount Args: email (str): Email of the user + code (str): First time discount code Returns (bool): True if the user is eligible for a first time discount """ try: - discounts = self.base_client.discount_codes.query( - where="code in :discountCodes", - predicate_var={'discountCodes': settings.COMMERCETOOLS_FIRST_TIME_DISCOUNTS} - ) - discount_ids = [discount.id for discount in discounts.results] - discounted_orders = self.base_client.orders.query( where=[ "customerEmail=:email", "orderState=:orderState", - "discountCodes(discountCode(id in :discountIds))" + "discountCodes(discountCode is defined)" ], - predicate_var={'email': email, 'discountIds': discount_ids, 'orderState': 'Complete'} + predicate_var={'email': email, 'orderState': 'Complete'}, + expand=["discountCodes[*].discountCode"] ) - if discounted_orders.total > 0: - return False + if discounted_orders.total < 1: + return True + + discounted_orders = discounted_orders.results + + for order in discounted_orders: + discount_code = order.discount_codes[0].discount_code.obj.code + if discount_code == code: + return False return True except CommercetoolsError as err: # pragma no cover diff --git a/commerce_coordinator/apps/commercetools/pipeline.py b/commerce_coordinator/apps/commercetools/pipeline.py index 4a67b6f1..f0ba111a 100644 --- a/commerce_coordinator/apps/commercetools/pipeline.py +++ b/commerce_coordinator/apps/commercetools/pipeline.py @@ -415,11 +415,12 @@ class CheckCommercetoolsDiscountEligibility(PipelineStep): """ Checks if a user is eligible for a first time discount in Commercetools. """ - def run_filter(self, email): # pylint: disable=arguments-differ + def run_filter(self, email, code): # pylint: disable=arguments-differ """ Execute a filter with the signature specified. Arguments: email: Email of the user + code: First time discount code kwargs: The keyword arguments passed through from the filter Returns: is_eligible (bool): True if the user is eligible for a first time discount @@ -428,7 +429,7 @@ def run_filter(self, email): # pylint: disable=arguments-differ try: ct_api_client = CommercetoolsAPIClient() - is_eligible = ct_api_client.is_first_time_discount_eligible(email) + is_eligible = ct_api_client.is_first_time_discount_eligible(email, code) return { 'is_eligible': is_eligible diff --git a/commerce_coordinator/apps/lms/filters.py b/commerce_coordinator/apps/lms/filters.py index ef1288f1..91d35d0a 100644 --- a/commerce_coordinator/apps/lms/filters.py +++ b/commerce_coordinator/apps/lms/filters.py @@ -74,12 +74,13 @@ class CheckFirstTimeDiscountEligibility(OpenEdxPublicFilter): filter_type = "org.edx.coordinator.lms.check.first.time.discount.eligibility.v1" @classmethod - def run_filter(cls, email): + def run_filter(cls, email, code): """ Call the PipelineStep(s) defined for this filter. Arguments: email: Email of the user + code: First time discount code Returns: is_eligible (bool): True if the user is eligible for a first time discount """ - return super().run_pipeline(email=email) + return super().run_pipeline(email=email, code=code) diff --git a/commerce_coordinator/apps/lms/views.py b/commerce_coordinator/apps/lms/views.py index bd21d04a..769a9712 100644 --- a/commerce_coordinator/apps/lms/views.py +++ b/commerce_coordinator/apps/lms/views.py @@ -345,11 +345,12 @@ class FirstTimeDiscountEligibleView(APIView): def get(self, request): """Return True if user is eligible for a first time discount.""" email = request.query_params.get('email') + code = request.query_params.get('code') - if not email: # pragma: no cover - raise PermissionDenied(detail="Could not detect user email.") + if not email or not code: # pragma: no cover + raise PermissionDenied(detail="Could not detect user email or discount code.") - result = CheckFirstTimeDiscountEligibility.run_filter(email=email) + result = CheckFirstTimeDiscountEligibility.run_filter(email=email, code=code) output = { "is_eligible": result.get('is_eligible', True) diff --git a/commerce_coordinator/settings/base.py b/commerce_coordinator/settings/base.py index 8b6e6bbd..a5aaa301 100644 --- a/commerce_coordinator/settings/base.py +++ b/commerce_coordinator/settings/base.py @@ -460,8 +460,6 @@ def root(*path_fragments): # Checkout view urls COMMERCETOOLS_FRONTEND_URL = 'http://localhost:3000/SET-ME' -COMMERCETOOLS_FIRST_TIME_DISCOUNTS = ('EDXWELCOME', 'NEW2EDX') - COMMERCETOOLS_MERCHANT_CENTER_ORDERS_PAGE_URL = \ f'https://mc.{_COMMERCETOOLS_CONFIG_GEO}.commercetools.com/{COMMERCETOOLS_CONFIG["projectKey"]}/orders' From c8a17209d78cf667abc41e8345700c6f6cdfde82 Mon Sep 17 00:00:00 2001 From: Muhammad Noyan Aziz Date: Thu, 12 Dec 2024 18:30:34 +0500 Subject: [PATCH 8/9] fix: tests --- .../apps/commercetools/tests/test_clients.py | 76 ++++++++----------- .../apps/commercetools/tests/test_pipeline.py | 5 +- .../apps/lms/tests/test_views.py | 11 +-- 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/commerce_coordinator/apps/commercetools/tests/test_clients.py b/commerce_coordinator/apps/commercetools/tests/test_clients.py index fad38ac4..bf2d4082 100644 --- a/commerce_coordinator/apps/commercetools/tests/test_clients.py +++ b/commerce_coordinator/apps/commercetools/tests/test_clients.py @@ -806,94 +806,84 @@ def test_update_customer_with_anonymized_fields_exception(self): def test_is_first_time_discount_eligible_success(self): base_url = self.client_set.get_base_url_from_client() email = 'email@example.com' + code = 'discount-code' - mock_discount_codes = { + mock_orders = { + "total": 1, "results": [ - {"id": "discount-id-1"}, - {"id": "discount-id-2"} + { + "discountCodes": [ + { + "discountCode": { + "obj": { + "code": 'another-code' + } + } + } + ] + } ] } - mock_orders = { - "total": 0 - } - with requests_mock.Mocker(real_http=True, case_sensitive=False) as mocker: - mocker.get( - f"{base_url}discount-codes", - json=mock_discount_codes, - status_code=200 - ) - mocker.get( f"{base_url}orders", json=mock_orders, status_code=200 ) - result = self.client_set.client.is_first_time_discount_eligible(email) + result = self.client_set.client.is_first_time_discount_eligible(email, code) self.assertTrue(result) - def test_is_first_time_discount_eligible_not_eligible(self): + def test_is_first_time_discount_not_eligible(self): base_url = self.client_set.get_base_url_from_client() email = 'email@example.com' + code = 'discount-code' - mock_discount_codes = { + mock_orders = { + "total": 1, "results": [ - {"id": "discount-id-1"}, - {"id": "discount-id-2"} + { + "discountCodes": [ + { + "discountCode": { + "obj": { + "code": code + } + } + } + ] + } ] } - mock_orders = { - "total": 1 - } - with requests_mock.Mocker(real_http=True, case_sensitive=False) as mocker: - mocker.get( - f"{base_url}discount-codes", - json=mock_discount_codes, - status_code=200 - ) - mocker.get( f"{base_url}orders", json=mock_orders, status_code=200 ) - result = self.client_set.client.is_first_time_discount_eligible(email) + result = self.client_set.client.is_first_time_discount_eligible(email, code) self.assertFalse(result) def test_is_first_time_discount_eligible_invalid_email(self): invalid_email = "invalid_email@example.com" + code = 'discount-code' base_url = self.client_set.get_base_url_from_client() - mock_discount_codes = { - "results": [ - {"id": "discount-id-1"}, - {"id": "discount-id-2"} - ] - } - mock_orders = { "total": 0 } with requests_mock.Mocker(real_http=True, case_sensitive=False) as mocker: - mocker.get( - f"{base_url}discount-codes", - json=mock_discount_codes, - status_code=200 - ) - mocker.get( f"{base_url}orders", json=mock_orders, status_code=200 ) - result = self.client_set.client.is_first_time_discount_eligible(invalid_email) + result = self.client_set.client.is_first_time_discount_eligible(invalid_email, code) self.assertTrue(result) diff --git a/commerce_coordinator/apps/commercetools/tests/test_pipeline.py b/commerce_coordinator/apps/commercetools/tests/test_pipeline.py index 8fde2abf..45c20729 100644 --- a/commerce_coordinator/apps/commercetools/tests/test_pipeline.py +++ b/commerce_coordinator/apps/commercetools/tests/test_pipeline.py @@ -271,6 +271,7 @@ class CommercetoolsDiscountEligibilityPipelineTests(TestCase): """Commercetools pipeline testcase for checking discount eligibility in CT""" def setUp(self) -> None: self.mock_email = "mock_email@example.com" + self.mock_code = "mock_code" self.mock_eligible_result = True self.mock_ineligible_result = False @@ -281,7 +282,7 @@ def setUp(self) -> None: def test_pipeline_eligible(self, mock_is_eligible): pipe = CheckCommercetoolsDiscountEligibility("test_pipe", None) mock_is_eligible.return_value = self.mock_eligible_result - ret = pipe.run_filter(self.mock_email) + ret = pipe.run_filter(self.mock_email, self.mock_code) result_data = ret['is_eligible'] self.assertEqual(result_data, self.mock_eligible_result) @@ -292,6 +293,6 @@ def test_pipeline_eligible(self, mock_is_eligible): def test_pipeline_ineligible(self, mock_is_eligible): pipe = CheckCommercetoolsDiscountEligibility("test_pipe", None) mock_is_eligible.return_value = self.mock_ineligible_result - ret = pipe.run_filter(self.mock_email) + ret = pipe.run_filter(self.mock_email, self.mock_code) result_data = ret['is_eligible'] self.assertEqual(result_data, self.mock_ineligible_result) diff --git a/commerce_coordinator/apps/lms/tests/test_views.py b/commerce_coordinator/apps/lms/tests/test_views.py index f0c34967..b4bc0131 100644 --- a/commerce_coordinator/apps/lms/tests/test_views.py +++ b/commerce_coordinator/apps/lms/tests/test_views.py @@ -393,6 +393,7 @@ class FirstTimeDiscountEligibleViewTests(APITestCase): test_user_username = 'test' test_user_email = 'test@example.com' test_user_password = 'secret' + test_discount = 'first_time_discount' url = reverse('lms:first_time_discount_eligible') @@ -421,11 +422,11 @@ def test_get_with_valid_email_eligibility_true(self, mock_filter): self.authenticate_user() mock_filter.return_value = {'is_eligible': True} - response = self.client.get(self.url, {'email': self.test_user_email}) + response = self.client.get(self.url, {'email': self.test_user_email, 'code': self.test_discount}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, {"is_eligible": True}) - mock_filter.assert_called_once_with(email=self.test_user_email) + mock_filter.assert_called_once_with(email=self.test_user_email, code='first_time_discount') @patch('commerce_coordinator.apps.lms.views.CheckFirstTimeDiscountEligibility.run_filter') def test_get_with_valid_email_eligibility_false(self, mock_filter): @@ -435,11 +436,11 @@ def test_get_with_valid_email_eligibility_false(self, mock_filter): self.authenticate_user() mock_filter.return_value = {'is_eligible': False} - response = self.client.get(self.url, {'email': self.test_user_email}) + response = self.client.get(self.url, {'email': self.test_user_email, 'code': self.test_discount}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, {"is_eligible": False}) - mock_filter.assert_called_once_with(email=self.test_user_email) + mock_filter.assert_called_once_with(email=self.test_user_email, code=self.test_discount) def test_get_with_missing_email_fails(self): """ @@ -450,4 +451,4 @@ def test_get_with_missing_email_fails(self): response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.data, {'detail': 'Could not detect user email.'}) + self.assertEqual(response.data, {'detail': 'Could not detect user email or discount code.'}) From 043872d81ccd256fbaf8c81ca3a03e3cb324b6ae Mon Sep 17 00:00:00 2001 From: Muhammad Noyan Aziz Date: Fri, 3 Jan 2025 04:00:22 +0500 Subject: [PATCH 9/9] chore: removed pipeline from the flow and converted get call to post --- .../apps/commercetools/pipeline.py | 31 ------------- .../apps/commercetools/tests/test_pipeline.py | 32 ------------- commerce_coordinator/apps/lms/filters.py | 20 --------- commerce_coordinator/apps/lms/serializers.py | 8 ++++ .../apps/lms/tests/test_views.py | 45 ++++++++++++------- commerce_coordinator/apps/lms/views.py | 38 ++++++++++------ commerce_coordinator/settings/base.py | 6 --- 7 files changed, 62 insertions(+), 118 deletions(-) diff --git a/commerce_coordinator/apps/commercetools/pipeline.py b/commerce_coordinator/apps/commercetools/pipeline.py index d07f9fbe..9a13d90c 100644 --- a/commerce_coordinator/apps/commercetools/pipeline.py +++ b/commerce_coordinator/apps/commercetools/pipeline.py @@ -416,34 +416,3 @@ def run_filter( except HTTPError as err: # pragma no cover log.exception(f"[{tag}] HTTP Error: {err}") return PipelineCommand.CONTINUE.value - - -class CheckCommercetoolsDiscountEligibility(PipelineStep): - """ - Checks if a user is eligible for a first time discount in Commercetools. - """ - def run_filter(self, email, code): # pylint: disable=arguments-differ - """ - Execute a filter with the signature specified. - Arguments: - email: Email of the user - code: First time discount code - kwargs: The keyword arguments passed through from the filter - Returns: - is_eligible (bool): True if the user is eligible for a first time discount - """ - tag = type(self).__name__ - - try: - ct_api_client = CommercetoolsAPIClient() - is_eligible = ct_api_client.is_first_time_discount_eligible(email, code) - - return { - 'is_eligible': is_eligible - } - except CommercetoolsError as err: # pragma no cover - log.exception(f"[{tag}] Commercetools Error: {err}, {err.errors}") - return PipelineCommand.CONTINUE.value - except HTTPError as err: # pragma no cover - log.exception(f"[{tag}] HTTP Error: {err}") - return PipelineCommand.CONTINUE.value diff --git a/commerce_coordinator/apps/commercetools/tests/test_pipeline.py b/commerce_coordinator/apps/commercetools/tests/test_pipeline.py index 45c20729..1f02f1de 100644 --- a/commerce_coordinator/apps/commercetools/tests/test_pipeline.py +++ b/commerce_coordinator/apps/commercetools/tests/test_pipeline.py @@ -11,7 +11,6 @@ from commerce_coordinator.apps.commercetools.constants import COMMERCETOOLS_ORDER_MANAGEMENT_SYSTEM from commerce_coordinator.apps.commercetools.pipeline import ( AnonymizeRetiredUser, - CheckCommercetoolsDiscountEligibility, CreateReturnForCommercetoolsOrder, CreateReturnPaymentTransaction, GetCommercetoolsOrders, @@ -265,34 +264,3 @@ def test_pipeline(self, mock_anonymize_fields, mock_customer_by_lms_id, mock_ano ret = pipe.run_filter(lms_user_id=self.mock_lms_user_id) result_data = ret['returned_customer'] self.assertEqual(result_data, self.update_customer_response) - - -class CommercetoolsDiscountEligibilityPipelineTests(TestCase): - """Commercetools pipeline testcase for checking discount eligibility in CT""" - def setUp(self) -> None: - self.mock_email = "mock_email@example.com" - self.mock_code = "mock_code" - self.mock_eligible_result = True - self.mock_ineligible_result = False - - @patch( - 'commerce_coordinator.apps.commercetools.clients.CommercetoolsAPIClient' - '.is_first_time_discount_eligible' - ) - def test_pipeline_eligible(self, mock_is_eligible): - pipe = CheckCommercetoolsDiscountEligibility("test_pipe", None) - mock_is_eligible.return_value = self.mock_eligible_result - ret = pipe.run_filter(self.mock_email, self.mock_code) - result_data = ret['is_eligible'] - self.assertEqual(result_data, self.mock_eligible_result) - - @patch( - 'commerce_coordinator.apps.commercetools.clients.CommercetoolsAPIClient' - '.is_first_time_discount_eligible' - ) - def test_pipeline_ineligible(self, mock_is_eligible): - pipe = CheckCommercetoolsDiscountEligibility("test_pipe", None) - mock_is_eligible.return_value = self.mock_ineligible_result - ret = pipe.run_filter(self.mock_email, self.mock_code) - result_data = ret['is_eligible'] - self.assertEqual(result_data, self.mock_ineligible_result) diff --git a/commerce_coordinator/apps/lms/filters.py b/commerce_coordinator/apps/lms/filters.py index 91d35d0a..f215f6a2 100644 --- a/commerce_coordinator/apps/lms/filters.py +++ b/commerce_coordinator/apps/lms/filters.py @@ -64,23 +64,3 @@ def run_filter(cls, lms_user_id): # pragma no cover """ return super().run_pipeline(lms_user_id=lms_user_id) - - -class CheckFirstTimeDiscountEligibility(OpenEdxPublicFilter): - """ - Filter to check if a user is eligible for a first time discount - """ - # See pipeline step configuration OPEN_EDX_FILTERS_CONFIG dict in `settings/base.py` - filter_type = "org.edx.coordinator.lms.check.first.time.discount.eligibility.v1" - - @classmethod - def run_filter(cls, email, code): - """ - Call the PipelineStep(s) defined for this filter. - Arguments: - email: Email of the user - code: First time discount code - Returns: - is_eligible (bool): True if the user is eligible for a first time discount - """ - return super().run_pipeline(email=email, code=code) diff --git a/commerce_coordinator/apps/lms/serializers.py b/commerce_coordinator/apps/lms/serializers.py index 7b0d2b16..ef8a7bff 100644 --- a/commerce_coordinator/apps/lms/serializers.py +++ b/commerce_coordinator/apps/lms/serializers.py @@ -84,3 +84,11 @@ class UserRetiredInputSerializer(CoordinatorSerializer): Serializer for User Deactivation/Retirement input validation """ edx_lms_user_id = serializers.IntegerField(allow_null=False) + + +class FirstTimeDiscountInputSerializer(CoordinatorSerializer): + """ + Serializer for First Time Discount input validation + """ + email = serializers.EmailField(required=True) + code = serializers.CharField(required=True) diff --git a/commerce_coordinator/apps/lms/tests/test_views.py b/commerce_coordinator/apps/lms/tests/test_views.py index b4bc0131..a7c35753 100644 --- a/commerce_coordinator/apps/lms/tests/test_views.py +++ b/commerce_coordinator/apps/lms/tests/test_views.py @@ -395,6 +395,16 @@ class FirstTimeDiscountEligibleViewTests(APITestCase): test_user_password = 'secret' test_discount = 'first_time_discount' + valid_payload = { + 'email': test_user_email, + 'code': test_discount, + } + + invalid_payload = { + 'email': None, + 'code': 'any_discount', + } + url = reverse('lms:first_time_discount_eligible') def setUp(self): @@ -414,33 +424,39 @@ def authenticate_user(self): self.client.login(username=self.test_user_username, password=self.test_user_password) self.client.force_authenticate(user=self.user) - @patch('commerce_coordinator.apps.lms.views.CheckFirstTimeDiscountEligibility.run_filter') - def test_get_with_valid_email_eligibility_true(self, mock_filter): + @patch( + 'commerce_coordinator.apps.commercetools.clients.CommercetoolsAPIClient' + '.is_first_time_discount_eligible' + ) + def test_get_with_valid_email_eligibility_true(self, mock_is_first_time_discount_eligible): """ - Test case where the email is eligible for a first-time discount. + Test case where the user is eligible for a first-time discount. """ self.authenticate_user() - mock_filter.return_value = {'is_eligible': True} + mock_is_first_time_discount_eligible.return_value = True - response = self.client.get(self.url, {'email': self.test_user_email, 'code': self.test_discount}) + response = self.client.post(self.url, self.valid_payload, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, {"is_eligible": True}) - mock_filter.assert_called_once_with(email=self.test_user_email, code='first_time_discount') + mock_is_first_time_discount_eligible.assert_called_once_with(self.test_user_email, self.test_discount) - @patch('commerce_coordinator.apps.lms.views.CheckFirstTimeDiscountEligibility.run_filter') - def test_get_with_valid_email_eligibility_false(self, mock_filter): + @patch( + 'commerce_coordinator.apps.commercetools.clients.CommercetoolsAPIClient' + '.is_first_time_discount_eligible' + ) + def test_get_with_valid_email_eligibility_false(self, mock_is_first_time_discount_eligible): """ - Test case where the email is not eligible for a first-time discount. + Test case where the user is not eligible for a first-time discount. """ self.authenticate_user() - mock_filter.return_value = {'is_eligible': False} + mock_is_first_time_discount_eligible.return_value = False - response = self.client.get(self.url, {'email': self.test_user_email, 'code': self.test_discount}) + response = self.client.post(self.url, self.valid_payload, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, {"is_eligible": False}) - mock_filter.assert_called_once_with(email=self.test_user_email, code=self.test_discount) + mock_is_first_time_discount_eligible.assert_called_once_with(self.test_user_email, self.test_discount) def test_get_with_missing_email_fails(self): """ @@ -448,7 +464,6 @@ def test_get_with_missing_email_fails(self): """ self.authenticate_user() - response = self.client.get(self.url) + response = self.client.post(self.url, self.invalid_payload, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.data, {'detail': 'Could not detect user email or discount code.'}) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/commerce_coordinator/apps/lms/views.py b/commerce_coordinator/apps/lms/views.py index 769a9712..d5f7051a 100644 --- a/commerce_coordinator/apps/lms/views.py +++ b/commerce_coordinator/apps/lms/views.py @@ -4,26 +4,29 @@ import logging from urllib.parse import urlencode, urljoin +from commercetools import CommercetoolsError from django.conf import settings from django.http import HttpResponseBadRequest, HttpResponseRedirect from edx_rest_framework_extensions.permissions import LoginRedirectIfUnauthenticated from openedx_filters.exceptions import OpenEdxFilterException -from rest_framework.exceptions import PermissionDenied, ValidationError +from requests import HTTPError +from rest_framework.exceptions import ValidationError from rest_framework.permissions import IsAdminUser from rest_framework.response import Response from rest_framework.status import HTTP_200_OK, HTTP_303_SEE_OTHER, HTTP_400_BAD_REQUEST, HTTP_500_INTERNAL_SERVER_ERROR from rest_framework.throttling import UserRateThrottle from rest_framework.views import APIView +from commerce_coordinator.apps.commercetools.clients import CommercetoolsAPIClient from commerce_coordinator.apps.core.constants import HttpHeadersNames, MediaTypes from commerce_coordinator.apps.lms.filters import ( - CheckFirstTimeDiscountEligibility, OrderRefundRequested, PaymentPageRedirectRequested, UserRetirementRequested ) from commerce_coordinator.apps.lms.serializers import ( CourseRefundInputSerializer, + FirstTimeDiscountInputSerializer, UserRetiredInputSerializer, enrollment_attribute_key ) @@ -339,21 +342,28 @@ def post(self, request) -> Response: class FirstTimeDiscountEligibleView(APIView): """View to check if a user is eligible for a first time discount""" - permission_classes = [LoginRedirectIfUnauthenticated] - throttle_classes = [UserRateThrottle] + permission_classes = [IsAdminUser] + throttle_classes = (UserRateThrottle,) - def get(self, request): + def post(self, request): """Return True if user is eligible for a first time discount.""" - email = request.query_params.get('email') - code = request.query_params.get('code') + validator = FirstTimeDiscountInputSerializer(data=request.data) + validator.is_valid(raise_exception=True) - if not email or not code: # pragma: no cover - raise PermissionDenied(detail="Could not detect user email or discount code.") + email = validator.validated_data['email'] + code = validator.validated_data['code'] - result = CheckFirstTimeDiscountEligibility.run_filter(email=email, code=code) + try: + ct_api_client = CommercetoolsAPIClient() + is_eligible = ct_api_client.is_first_time_discount_eligible(email, code) - output = { - "is_eligible": result.get('is_eligible', True) - } + output = { + 'is_eligible': is_eligible + } + return Response(output) + except CommercetoolsError as err: # pragma no cover + logger.exception(f"[FirstTimeDiscountEligibleView] Commercetools Error: {err}, {err.errors}") + except HTTPError as err: # pragma no cover + logger.exception(f"[FirstTimeDiscountEligibleView] HTTP Error: {err}") - return Response(output) + return Response({'is_eligible': True}) diff --git a/commerce_coordinator/settings/base.py b/commerce_coordinator/settings/base.py index d60027fa..de5d3b0f 100644 --- a/commerce_coordinator/settings/base.py +++ b/commerce_coordinator/settings/base.py @@ -399,12 +399,6 @@ def root(*path_fragments): "pipeline": [ 'commerce_coordinator.apps.commercetools.pipeline.AnonymizeRetiredUser', ] - }, - 'org.edx.coordinator.lms.check.first.time.discount.eligibility.v1': { - 'fail_silently': False, - 'pipeline': [ - 'commerce_coordinator.apps.commercetools.pipeline.CheckCommercetoolsDiscountEligibility', - ] } }