From ca5f81247943acf8800423ec2c4f20448c6ef81c Mon Sep 17 00:00:00 2001 From: Jawad Khan Date: Mon, 5 Aug 2024 03:35:14 +0500 Subject: [PATCH] fix: Added user cancelled payment error response in execute view --- ecommerce/extensions/iap/api/v1/constants.py | 3 +++ .../extensions/iap/api/v1/tests/test_views.py | 27 ++++++++++++++++++- ecommerce/extensions/iap/api/v1/views.py | 7 ++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/ecommerce/extensions/iap/api/v1/constants.py b/ecommerce/extensions/iap/api/v1/constants.py index 8c4e65f4fbe..76e7144bfe9 100644 --- a/ecommerce/extensions/iap/api/v1/constants.py +++ b/ecommerce/extensions/iap/api/v1/constants.py @@ -7,6 +7,7 @@ ERROR_ALREADY_PURCHASED = "You have already purchased these products" ERROR_BASKET_NOT_FOUND = "Basket [{}] not found." ERROR_BASKET_ID_NOT_PROVIDED = "Basket id is not provided" +ERROR_USER_CANCELLED_PAYMENT = "User cancelled this payment." ERROR_DURING_IOS_REFUND_EXECUTION = "Could not execute IOS refund." ERROR_DURING_ORDER_CREATION = "An error occurred during order creation." ERROR_DURING_PAYMENT_HANDLING = "An error occurred during payment handling." @@ -32,6 +33,8 @@ LOGGER_CHECKOUT_ERROR = "Checkout failed with the error [%s] and status code [%s]." LOGGER_EXECUTE_ALREADY_PURCHASED = "Execute payment failed for user [%s] and basket [%s]. " \ "Products already purchased." +LOGGER_EXECUTE_CANCELLED_PAYMENT_ERROR = "Execute payment failed for user [%s] and basket [%s]. " \ + "Payment error [%s]." LOGGER_EXECUTE_GATEWAY_ERROR = "Execute payment validation failed for user [%s] and basket [%s]. Error: [%s]" LOGGER_EXECUTE_ORDER_CREATION_FAILED = "Execute payment failed for user [%s] and basket [%s]. " \ "Order Creation failed with error [%s]." diff --git a/ecommerce/extensions/iap/api/v1/tests/test_views.py b/ecommerce/extensions/iap/api/v1/tests/test_views.py index d48d8a56a54..7db577f6f20 100644 --- a/ecommerce/extensions/iap/api/v1/tests/test_views.py +++ b/ecommerce/extensions/iap/api/v1/tests/test_views.py @@ -12,7 +12,7 @@ from django.urls import reverse from oauth2client.service_account import ServiceAccountCredentials from oscar.apps.order.exceptions import UnableToPlaceOrder -from oscar.apps.payment.exceptions import GatewayError, PaymentError +from oscar.apps.payment.exceptions import GatewayError, PaymentError, UserCancelled from oscar.core.loading import get_class, get_model from oscar.test.factories import BasketFactory from rest_framework import status @@ -38,6 +38,7 @@ ERROR_ORDER_NOT_FOUND_FOR_REFUND, ERROR_REFUND_NOT_COMPLETED, ERROR_TRANSACTION_NOT_FOUND_FOR_REFUND, + ERROR_USER_CANCELLED_PAYMENT, IGNORE_NON_REFUND_NOTIFICATION_FROM_APPLE, LOGGER_BASKET_ALREADY_PURCHASED, LOGGER_BASKET_CREATED, @@ -339,6 +340,30 @@ def test_payment_error(self): ), ) + def test_user_cancelled_payment_error(self): + """ + Verify that user cancelled payment error is returned for Usercancelled exception + """ + with mock.patch.object(MobileCoursePurchaseExecutionView, 'handle_payment', + side_effect=UserCancelled('Test Error')) as fake_handle_payment: + with LogCapture(self.logger_name) as logger: + self._assert_response({'error': ERROR_USER_CANCELLED_PAYMENT}) + self.assertTrue(fake_handle_payment.called) + + logger.check( + ( + self.logger_name, + 'INFO', + LOGGER_EXECUTE_STARTED % (self.user.username, self.basket.id, self.processor_name) + ), + ( + self.logger_name, + 'ERROR', + LOGGER_EXECUTE_PAYMENT_ERROR % (self.user.username, self.basket.id, + str(fake_handle_payment.side_effect)) + ), + ) + def test_gateway_error(self): """ Verify that an error is thrown when an approved payment fails to execute diff --git a/ecommerce/extensions/iap/api/v1/views.py b/ecommerce/extensions/iap/api/v1/views.py index e9fd354eabe..a896421dd22 100644 --- a/ecommerce/extensions/iap/api/v1/views.py +++ b/ecommerce/extensions/iap/api/v1/views.py @@ -17,7 +17,7 @@ from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey from oscar.apps.basket.views import * # pylint: disable=wildcard-import, unused-wildcard-import -from oscar.apps.payment.exceptions import GatewayError, PaymentError +from oscar.apps.payment.exceptions import GatewayError, PaymentError, UserCancelled from oscar.core.loading import get_class, get_model from rest_framework import status from rest_framework.permissions import IsAdminUser, IsAuthenticated @@ -51,6 +51,7 @@ ERROR_ORDER_NOT_FOUND_FOR_REFUND, ERROR_REFUND_NOT_COMPLETED, ERROR_TRANSACTION_NOT_FOUND_FOR_REFUND, + ERROR_USER_CANCELLED_PAYMENT, FOUND_MULTIPLE_PRODUCTS_ERROR, GOOGLE_PUBLISHER_API_SCOPE, IGNORE_NON_REFUND_NOTIFICATION_FROM_APPLE, @@ -60,6 +61,7 @@ LOGGER_BASKET_NOT_FOUND, LOGGER_CHECKOUT_ERROR, LOGGER_EXECUTE_ALREADY_PURCHASED, + LOGGER_EXECUTE_CANCELLED_PAYMENT_ERROR, LOGGER_EXECUTE_GATEWAY_ERROR, LOGGER_EXECUTE_ORDER_CREATION_FAILED, LOGGER_EXECUTE_PAYMENT_ERROR, @@ -302,6 +304,9 @@ def post(self, request): except RedundantPaymentNotificationError: logger.exception(LOGGER_EXECUTE_REDUNDANT_PAYMENT, request.user.username, basket_id) return JsonResponse({'error': COURSE_ALREADY_PAID_ON_DEVICE}, status=409) + except UserCancelled as exception: + logger.exception(LOGGER_EXECUTE_CANCELLED_PAYMENT_ERROR, request.user.username, basket_id, str(exception)) + return JsonResponse({'error': ERROR_USER_CANCELLED_PAYMENT}, status=400) except PaymentError as exception: logger.exception(LOGGER_EXECUTE_PAYMENT_ERROR, request.user.username, basket_id, str(exception)) return JsonResponse({'error': ERROR_DURING_PAYMENT_HANDLING}, status=400)