diff --git a/lms/djangoapps/instructor/views/coupons.py b/lms/djangoapps/instructor/views/coupons.py index b98eb9ba15..48f9828863 100644 --- a/lms/djangoapps/instructor/views/coupons.py +++ b/lms/djangoapps/instructor/views/coupons.py @@ -9,6 +9,7 @@ from django.utils.translation import ugettext as _ from util.json_request import JsonResponse from django.http import HttpResponse, HttpResponseNotFound from shoppingcart.models import Coupon, CourseRegistrationCode +from opaque_keys.edx.locations import SlashSeparatedCourseKey import logging @@ -54,32 +55,34 @@ def add_coupon(request, course_id): # pylint: disable=W0613 code = request.POST.get('code') # check if the code is already in the Coupons Table and active - coupon = Coupon.objects.filter(is_active=True, code=code) + try: + course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) + coupon = Coupon.objects.get(is_active=True, code=code, course_id=course_id) + except Coupon.DoesNotExist: + # check if the coupon code is in the CourseRegistrationCode Table + course_registration_code = CourseRegistrationCode.objects.filter(code=code) + if course_registration_code: + return HttpResponseNotFound(_( + "The code ({code}) that you have tried to define is already in use as a registration code").format(code=code) + ) + + description = request.POST.get('description') + course_id = request.POST.get('course_id') + try: + discount = int(request.POST.get('discount')) + except ValueError: + return HttpResponseNotFound(_("Please Enter the Integer Value for Coupon Discount")) + if discount > 100 or discount < 0: + return HttpResponseNotFound(_("Please Enter the Coupon Discount Value Less than or Equal to 100")) + coupon = Coupon( + code=code, description=description, course_id=course_id, + percentage_discount=discount, created_by_id=request.user.id + ) + coupon.save() + return HttpResponse(_("coupon with the coupon code ({code}) added successfully").format(code=code)) if coupon: - return HttpResponseNotFound(_("coupon with the coupon code ({code}) already exist").format(code=code)) - - # check if the coupon code is in the CourseRegistrationCode Table - course_registration_code = CourseRegistrationCode.objects.filter(code=code) - if course_registration_code: - return HttpResponseNotFound(_( - "The code ({code}) that you have tried to define is already in use as a registration code").format(code=code) - ) - - description = request.POST.get('description') - course_id = request.POST.get('course_id') - try: - discount = int(request.POST.get('discount')) - except ValueError: - return HttpResponseNotFound(_("Please Enter the Integer Value for Coupon Discount")) - if discount > 100: - return HttpResponseNotFound(_("Please Enter the Coupon Discount Value Less than or Equal to 100")) - coupon = Coupon( - code=code, description=description, course_id=course_id, - percentage_discount=discount, created_by_id=request.user.id - ) - coupon.save() - return HttpResponse(_("coupon with the coupon code ({code}) added successfully").format(code=code)) + return HttpResponseNotFound(_("coupon with the coupon code ({code}) already exists for this course").format(code=code)) @require_POST diff --git a/lms/djangoapps/instructor_analytics/basic.py b/lms/djangoapps/instructor_analytics/basic.py index d5bc599663..4091db27a8 100644 --- a/lms/djangoapps/instructor_analytics/basic.py +++ b/lms/djangoapps/instructor_analytics/basic.py @@ -81,7 +81,7 @@ def purchase_transactions(course_id, features): ] """ - purchased_courses = PaidCourseRegistration.objects.filter(course_id=course_id, status='purchased') + purchased_courses = PaidCourseRegistration.objects.filter(course_id=course_id, status='purchased').order_by('user') def purchase_transactions_info(purchased_course, features): """ convert purchase transactions to dictionary """ @@ -103,12 +103,17 @@ def purchase_transactions(course_id, features): for feature in order_item_features) order_item_dict.update({"orderitem_id": getattr(purchased_course, 'id')}) - try: - coupon_redemption = CouponRedemption.objects.select_related('coupon').get(order_id=purchased_course.order_id) - except CouponRedemption.DoesNotExist: - coupon_code_dict = {'coupon_code': 'None'} + coupon_redemption = CouponRedemption.objects.select_related('coupon').filter(order_id=purchased_course.order_id) + if coupon_redemption: + # we format the coupon codes in comma separated way if there are more then one coupon against a order id. + coupon_codes = list() + for redemption in coupon_redemption: + coupon_codes.append(redemption.coupon.code) + + coupon_code_dict = {'coupon_code': ", ".join(coupon_codes)} + else: - coupon_code_dict = {'coupon_code': coupon_redemption.coupon.code} + coupon_code_dict = {'coupon_code': 'None'} student_dict.update(dict(order_dict.items() + order_item_dict.items() + coupon_code_dict.items())) student_dict.update({'course_id': course_id.to_deprecated_string()}) diff --git a/lms/djangoapps/shoppingcart/exceptions.py b/lms/djangoapps/shoppingcart/exceptions.py index 1f704e0ed1..1a1f944e71 100644 --- a/lms/djangoapps/shoppingcart/exceptions.py +++ b/lms/djangoapps/shoppingcart/exceptions.py @@ -32,11 +32,7 @@ class CouponDoesNotExistException(InvalidCartItem): pass -class CouponAlreadyExistException(InvalidCartItem): - pass - - -class ItemDoesNotExistAgainstCouponException(InvalidCartItem): +class MultipleCouponsNotAllowedException(InvalidCartItem): pass diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py index 73943328af..fce97868f9 100644 --- a/lms/djangoapps/shoppingcart/models.py +++ b/lms/djangoapps/shoppingcart/models.py @@ -32,8 +32,7 @@ from verify_student.models import SoftwareSecurePhotoVerification from .exceptions import (InvalidCartItem, PurchasedCallbackException, ItemAlreadyInCartException, AlreadyEnrolledInCourseException, CourseDoesNotExistException, - CouponAlreadyExistException, ItemDoesNotExistAgainstCouponException, - RegCodeAlreadyExistException, ItemDoesNotExistAgainstRegCodeException) + MultipleCouponsNotAllowedException, RegCodeAlreadyExistException, ItemDoesNotExistAgainstRegCodeException) from microsite_configuration import microsite @@ -498,31 +497,33 @@ class CouponRedemption(models.Model): return value - discount @classmethod - def add_coupon_redemption(cls, coupon, order): + def add_coupon_redemption(cls, coupon, order, cart_items): """ add coupon info into coupon_redemption model """ - cart_items = order.orderitem_set.all().select_subclasses() + is_redemption_applied = False + coupon_redemptions = cls.objects.filter(order=order, user=order.user) + for coupon_redemption in coupon_redemptions: + if coupon_redemption.coupon.code != coupon.code or coupon_redemption.coupon.id == coupon.id: + log.exception("Coupon redemption already exist for user '{0}' against order id '{1}'" + .format(order.user.username, order.id)) + raise MultipleCouponsNotAllowedException for item in cart_items: if getattr(item, 'course_id'): if item.course_id == coupon.course_id: - coupon_redemption, created = cls.objects.get_or_create(order=order, user=order.user, coupon=coupon) - if not created: - log.exception("Coupon '{0}' already exist for user '{1}' against order id '{2}'" - .format(coupon.code, order.user.username, order.id)) - raise CouponAlreadyExistException - + coupon_redemption = cls(order=order, user=order.user, coupon=coupon) + coupon_redemption.save() discount_price = cls.get_discount_price(coupon.percentage_discount, item.unit_cost) item.list_price = item.unit_cost item.unit_cost = discount_price item.save() log.info("Discount generated for user {0} against order id '{1}' " .format(order.user.username, order.id)) - return coupon_redemption + is_redemption_applied = True + return is_redemption_applied - log.warning("Course item does not exist for coupon '{0}'".format(coupon.code)) - raise ItemDoesNotExistAgainstCouponException + return is_redemption_applied class PaidCourseRegistration(OrderItem): diff --git a/lms/djangoapps/shoppingcart/tests/test_views.py b/lms/djangoapps/shoppingcart/tests/test_views.py index 86b3c9d414..efe6d0fd94 100644 --- a/lms/djangoapps/shoppingcart/tests/test_views.py +++ b/lms/djangoapps/shoppingcart/tests/test_views.py @@ -77,18 +77,18 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): self.cart = Order.get_cart_for_user(self.user) self.addCleanup(patcher.stop) - def get_discount(self): + def get_discount(self, cost): """ This method simple return the discounted amount """ - val = Decimal("{0:.2f}".format(Decimal(self.percentage_discount / 100.00) * self.cost)) - return self.cost - val + val = Decimal("{0:.2f}".format(Decimal(self.percentage_discount / 100.00) * cost)) + return cost - val - def add_coupon(self, course_key, is_active): + def add_coupon(self, course_key, is_active, code): """ add dummy coupon into models """ - coupon = Coupon(code=self.coupon_code, description='testing code', course_id=course_key, + coupon = Coupon(code=code, description='testing code', course_id=course_key, percentage_discount=self.percentage_discount, created_by=self.user, is_active=is_active) coupon.save() @@ -99,12 +99,12 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): course_reg_code = CourseRegistrationCode(code=self.reg_code, course_id=course_key, created_by=self.user) course_reg_code.save() - def add_course_to_user_cart(self): + def add_course_to_user_cart(self, course_key): """ adding course to user cart """ self.login_user() - reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_key) + reg_item = PaidCourseRegistration.add_to_order(self.cart, course_key) return reg_item def login_user(self): @@ -122,8 +122,8 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): self.assertIn('The course {0} is already in your cart.'.format(self.course_key.to_deprecated_string()), resp.content) def test_course_discount_invalid_coupon(self): - self.add_coupon(self.course_key, True) - self.add_course_to_user_cart() + self.add_coupon(self.course_key, True, self.coupon_code) + self.add_course_to_user_cart(self.course_key) non_existing_code = "non_existing_code" resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': non_existing_code}) self.assertEqual(resp.status_code, 404) @@ -131,23 +131,23 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): def test_course_discount_invalid_reg_code(self): self.add_reg_code(self.course_key) - self.add_course_to_user_cart() + self.add_course_to_user_cart(self.course_key) non_existing_code = "non_existing_code" resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': non_existing_code}) self.assertEqual(resp.status_code, 404) self.assertIn("Discount does not exist against code '{0}'.".format(non_existing_code), resp.content) def test_course_discount_inactive_coupon(self): - self.add_coupon(self.course_key, False) - self.add_course_to_user_cart() + self.add_coupon(self.course_key, False, self.coupon_code) + self.add_course_to_user_cart(self.course_key) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code}) self.assertEqual(resp.status_code, 404) self.assertIn("Discount does not exist against code '{0}'.".format(self.coupon_code), resp.content) def test_course_does_not_exist_in_cart_against_valid_coupon(self): course_key = self.course_key.to_deprecated_string() + 'testing' - self.add_coupon(course_key, True) - self.add_course_to_user_cart() + self.add_coupon(course_key, True, self.coupon_code) + self.add_course_to_user_cart(self.course_key) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code}) self.assertEqual(resp.status_code, 404) @@ -156,7 +156,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): def test_course_does_not_exist_in_cart_against_valid_reg_code(self): course_key = self.course_key.to_deprecated_string() + 'testing' self.add_reg_code(course_key) - self.add_course_to_user_cart() + self.add_course_to_user_cart(self.course_key) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code}) self.assertEqual(resp.status_code, 404) @@ -164,26 +164,64 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): def test_course_discount_for_valid_active_coupon_code(self): - self.add_coupon(self.course_key, True) - self.add_course_to_user_cart() + self.add_coupon(self.course_key, True, self.coupon_code) + self.add_course_to_user_cart(self.course_key) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code}) self.assertEqual(resp.status_code, 200) # unit price should be updated for that course item = self.cart.orderitem_set.all().select_subclasses()[0] - self.assertEquals(item.unit_cost, self.get_discount()) + self.assertEquals(item.unit_cost, self.get_discount(self.cost)) # after getting 10 percent discount - self.assertEqual(self.cart.total_cost, self.get_discount()) + self.assertEqual(self.cart.total_cost, self.get_discount(self.cost)) - # now testing coupon code already used scenario, reusing the same coupon code + # now using the same coupon code against the same order. + # Only one coupon redemption should be allowed per order. resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code}) self.assertEqual(resp.status_code, 400) - self.assertIn("Coupon '{0}' already used.".format(self.coupon_code), resp.content) + self.assertIn("Only one coupon redemption is allowed against an order", resp.content) + + def test_course_discount_against_two_distinct_coupon_codes(self): + + self.add_coupon(self.course_key, True, self.coupon_code) + self.add_course_to_user_cart(self.course_key) + + resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code}) + self.assertEqual(resp.status_code, 200) + + # unit price should be updated for that course + item = self.cart.orderitem_set.all().select_subclasses()[0] + self.assertEquals(item.unit_cost, self.get_discount(self.cost)) + + # now using another valid active coupon code. + # Only one coupon redemption should be allowed per order. + self.add_coupon(self.course_key, True, 'abxyz') + resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': 'abxyz'}) + self.assertEqual(resp.status_code, 400) + self.assertIn("Only one coupon redemption is allowed against an order", resp.content) + + def test_same_coupons_code_on_multiple_courses(self): + + # add two same coupon codes on two different courses + self.add_coupon(self.course_key, True, self.coupon_code) + self.add_coupon(self.testing_course.id, True, self.coupon_code) + self.add_course_to_user_cart(self.course_key) + self.add_course_to_user_cart(self.testing_course.id) + + resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code}) + self.assertEqual(resp.status_code, 200) + + # unit price should be updated for that course + item = self.cart.orderitem_set.all().select_subclasses()[0] + self.assertEquals(item.unit_cost, self.get_discount(self.cost)) + + item = self.cart.orderitem_set.all().select_subclasses()[1] + self.assertEquals(item.unit_cost, self.get_discount(self.testing_cost)) def test_soft_delete_coupon(self): # pylint: disable=E1101 - self.add_coupon(self.course_key, True) + self.add_coupon(self.course_key, True, self.coupon_code) coupon = Coupon(code='TestCode', description='testing', course_id=self.course_key, percentage_discount=12, created_by=self.user, is_active=True) coupon.save() @@ -218,7 +256,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): def test_course_free_discount_for_valid_active_reg_code(self): self.add_reg_code(self.course_key) - self.add_course_to_user_cart() + self.add_course_to_user_cart(self.course_key) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code}) self.assertEqual(resp.status_code, 200) @@ -236,7 +274,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): @patch('shoppingcart.views.log.debug') def test_non_existing_coupon_redemption_on_removing_item(self, debug_log): - reg_item = self.add_course_to_user_cart() + reg_item = self.add_course_to_user_cart(self.course_key) resp = self.client.post(reverse('shoppingcart.views.remove_item', args=[]), {'id': reg_item.id}) debug_log.assert_called_with( @@ -248,8 +286,8 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): @patch('shoppingcart.views.log.info') def test_existing_coupon_redemption_on_removing_item(self, info_log): - self.add_coupon(self.course_key, True) - reg_item = self.add_course_to_user_cart() + self.add_coupon(self.course_key, True, self.coupon_code) + reg_item = self.add_course_to_user_cart(self.course_key) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code}) self.assertEqual(resp.status_code, 200) @@ -266,7 +304,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): def test_existing_reg_code_redemption_on_removing_item(self, info_log): self.add_reg_code(self.course_key) - reg_item = self.add_course_to_user_cart() + reg_item = self.add_course_to_user_cart(self.course_key) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code}) self.assertEqual(resp.status_code, 200) @@ -282,8 +320,8 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): @patch('shoppingcart.views.log.info') def test_coupon_discount_for_multiple_courses_in_cart(self, info_log): - reg_item = self.add_course_to_user_cart() - self.add_coupon(self.course_key, True) + reg_item = self.add_course_to_user_cart(self.course_key) + self.add_coupon(self.course_key, True, self.coupon_code) cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor') self.assertEquals(self.cart.orderitem_set.count(), 2) @@ -294,7 +332,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): items = self.cart.orderitem_set.all().select_subclasses() for item in items: if item.id == reg_item.id: - self.assertEquals(item.unit_cost, self.get_discount()) + self.assertEquals(item.unit_cost, self.get_discount(self.cost)) elif item.id == cert_item.id: self.assertEquals(item.list_price, None) @@ -310,7 +348,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): @patch('shoppingcart.views.log.info') def test_reg_code_free_discount_with_multiple_courses_in_cart(self, info_log): - reg_item = self.add_course_to_user_cart() + reg_item = self.add_course_to_user_cart(self.course_key) self.add_reg_code(self.course_key) cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor') self.assertEquals(self.cart.orderitem_set.count(), 2) @@ -338,7 +376,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): @patch('shoppingcart.views.log.info') def test_delete_certificate_item(self, info_log): - reg_item = self.add_course_to_user_cart() + self.add_course_to_user_cart(self.course_key) cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor') self.assertEquals(self.cart.orderitem_set.count(), 2) @@ -354,11 +392,11 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): @patch('shoppingcart.views.log.info') def test_remove_coupon_redemption_on_clear_cart(self, info_log): - reg_item = self.add_course_to_user_cart() + reg_item = self.add_course_to_user_cart(self.course_key) CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor') self.assertEquals(self.cart.orderitem_set.count(), 2) - self.add_coupon(self.course_key, True) + self.add_coupon(self.course_key, True, self.coupon_code) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code}) self.assertEqual(resp.status_code, 200) @@ -372,7 +410,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): @patch('shoppingcart.views.log.info') def test_remove_registration_code_redemption_on_clear_cart(self, info_log): - reg_item = self.add_course_to_user_cart() + reg_item = self.add_course_to_user_cart(self.course_key) CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor') self.assertEquals(self.cart.orderitem_set.count(), 2) @@ -504,9 +542,9 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): self.assertEqual(resp2.status_code, 404) def test_total_amount_of_purchased_course(self): - self.add_course_to_user_cart() + self.add_course_to_user_cart(self.course_key) self.assertEquals(self.cart.orderitem_set.count(), 1) - self.add_coupon(self.course_key, True) + self.add_coupon(self.course_key, True, self.coupon_code) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code}) self.assertEqual(resp.status_code, 200) @@ -526,8 +564,8 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): @patch('shoppingcart.views.render_to_response', render_mock) def test_show_receipt_success_with_valid_coupon_code(self): - self.add_course_to_user_cart() - self.add_coupon(self.course_key, True) + self.add_course_to_user_cart(self.course_key) + self.add_coupon(self.course_key, True, self.coupon_code) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code}) self.assertEqual(resp.status_code, 200) @@ -536,14 +574,14 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id])) self.assertEqual(resp.status_code, 200) self.assertIn('FirstNameTesting123', resp.content) - self.assertIn(str(self.get_discount()), resp.content) + self.assertIn(str(self.get_discount(self.cost)), resp.content) @patch('shoppingcart.views.render_to_response', render_mock) def test_reg_code_and_course_registration_scenario(self): self.add_reg_code(self.course_key) # One courses in user shopping cart - self.add_course_to_user_cart() + self.add_course_to_user_cart(self.course_key) self.assertEquals(self.cart.orderitem_set.count(), 1) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code}) @@ -585,7 +623,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): @patch('shoppingcart.views.render_to_response', render_mock) def test_show_receipt_success_with_valid_reg_code(self): - self.add_course_to_user_cart() + self.add_course_to_user_cart(self.course_key) self.add_reg_code(self.course_key) resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code}) diff --git a/lms/djangoapps/shoppingcart/views.py b/lms/djangoapps/shoppingcart/views.py index e821c8eac3..792cea6666 100644 --- a/lms/djangoapps/shoppingcart/views.py +++ b/lms/djangoapps/shoppingcart/views.py @@ -15,7 +15,8 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey from shoppingcart.reports import RefundReport, ItemizedPurchaseReport, UniversityRevenueShareReport, CertificateStatusReport from student.models import CourseEnrollment from .exceptions import ItemAlreadyInCartException, AlreadyEnrolledInCourseException, CourseDoesNotExistException, ReportTypeDoesNotExistException, \ - CouponAlreadyExistException, ItemDoesNotExistAgainstCouponException, RegCodeAlreadyExistException, ItemDoesNotExistAgainstRegCodeException + RegCodeAlreadyExistException, ItemDoesNotExistAgainstRegCodeException,\ + MultipleCouponsNotAllowedException from .models import Order, PaidCourseRegistration, OrderItem, Coupon, CouponRedemption, CourseRegistrationCode, RegistrationCodeRedemption from .processors import process_postpay_callback, render_purchase_form_html import json @@ -154,9 +155,8 @@ def use_code(request): Valid Code can be either coupon or registration code. """ code = request.POST["code"] - try: - coupon = Coupon.objects.get(code=code, is_active=True) - except Coupon.DoesNotExist: + coupons = Coupon.objects.filter(code=code, is_active=True) + if not coupons: # If not coupon code then we check that code against course registration code try: course_reg = CourseRegistrationCode.objects.get(code=code) @@ -165,7 +165,7 @@ def use_code(request): return use_registration_code(course_reg, request.user) - return use_coupon_code(coupon, request.user) + return use_coupon_code(coupons, request.user) def use_registration_code(course_reg, user): @@ -183,17 +183,23 @@ def use_registration_code(course_reg, user): return HttpResponse(json.dumps({'response': 'success'}), content_type="application/json") -def use_coupon_code(coupon, user): +def use_coupon_code(coupons, user): """ This method utilize course coupon code """ - try: - cart = Order.get_cart_for_user(user) - CouponRedemption.add_coupon_redemption(coupon, cart) - except CouponAlreadyExistException: - return HttpResponseBadRequest(_("Coupon '{0}' already used.".format(coupon.code))) - except ItemDoesNotExistAgainstCouponException: - return HttpResponseNotFound(_("Coupon '{0}' is not valid for any course in the shopping cart.".format(coupon.code))) + cart = Order.get_cart_for_user(user) + cart_items = cart.orderitem_set.all().select_subclasses() + is_redemption_applied = False + for coupon in coupons: + try: + if CouponRedemption.add_coupon_redemption(coupon, cart, cart_items): + is_redemption_applied = True + except MultipleCouponsNotAllowedException: + return HttpResponseBadRequest(_("Only one coupon redemption is allowed against an order")) + + if not is_redemption_applied: + log.warning("Course item does not exist for coupon '{0}'".format(coupons[0].code)) + return HttpResponseNotFound(_("Coupon '{0}' is not valid for any course in the shopping cart.".format(coupons[0].code))) return HttpResponse(json.dumps({'response': 'success'}), content_type="application/json")