예제 #1
0
    def test_get_course_info_from_catalog_cached(self):
        """
        Verify that get_course_info_from_catalog is cached

        We expect 2 calls to set_all_tiers in the get_course_info_from_catalog
        method due to:
            - the site_configuration api setup
            - the result being cached
        """
        self.mock_access_token_response()
        product = create_or_update_course_entitlement('verified', 100,
                                                      self.partner, 'foo-bar',
                                                      'Foo Bar Entitlement')
        self.mock_course_detail_endpoint(
            product,
            discovery_api_url=self.site_configuration.discovery_api_url)

        with patch.object(
                TieredCache, 'set_all_tiers',
                wraps=TieredCache.set_all_tiers) as mocked_set_all_tiers:
            mocked_set_all_tiers.assert_not_called()

            _ = get_course_info_from_catalog(self.request.site, product)
            self.assertEqual(mocked_set_all_tiers.call_count, 2)

            _ = get_course_info_from_catalog(self.request.site, product)
            self.assertEqual(mocked_set_all_tiers.call_count, 2)
예제 #2
0
    def test_get_course_run_info_from_catalog(self, course_run):
        """ Check to see if course info gets cached """
        self.mock_access_token_response()
        if course_run:
            course = CourseFactory(partner=self.partner)
            product = course.create_or_update_seat('verified', None, 100)
            key = CourseKey.from_string(product.attr.course_key)
            self.mock_course_run_detail_endpoint(
                course,
                discovery_api_url=self.site_configuration.discovery_api_url)
        else:
            product = create_or_update_course_entitlement(
                'verified', 100, self.partner, 'foo-bar',
                'Foo Bar Entitlement')
            key = product.attr.UUID
            self.mock_course_detail_endpoint(
                product,
                discovery_api_url=self.site_configuration.discovery_api_url)

        cache_key = u'courses_api_detail_{}{}'.format(key,
                                                      self.partner.short_code)
        cache_key = hashlib.md5(cache_key.encode('utf-8')).hexdigest()
        course_cached_response = TieredCache.get_cached_response(cache_key)
        self.assertFalse(course_cached_response.is_found)

        response = get_course_info_from_catalog(self.request.site, product)

        if course_run:
            self.assertEqual(response['title'], course.name)
        else:
            self.assertEqual(response['title'], product.title)

        course_cached_response = TieredCache.get_cached_response(cache_key)
        self.assertEqual(course_cached_response.value, response)
예제 #3
0
    def test_get_course_run_info_from_catalog(self, course_run):
        """ Check to see if course info gets cached """
        self.mock_access_token_response()
        if course_run:
            resource = "course_runs"
            course = CourseFactory(partner=self.partner)
            product = course.create_or_update_seat('verified', None, 100)
            key = CourseKey.from_string(product.attr.course_key)
            self.mock_course_run_detail_endpoint(
                course,
                discovery_api_url=self.site_configuration.discovery_api_url)
        else:
            resource = "courses"
            product = create_or_update_course_entitlement(
                'verified', 100, self.partner, 'foo-bar',
                'Foo Bar Entitlement')
            key = product.attr.UUID
            self.mock_course_detail_endpoint(
                discovery_api_url=self.site_configuration.discovery_api_url,
                course=product)

        cache_key = get_cache_key(site_domain=self.site.domain,
                                  resource="{}-{}".format(resource, key))
        course_cached_response = TieredCache.get_cached_response(cache_key)
        self.assertFalse(course_cached_response.is_found)

        response = get_course_info_from_catalog(self.request.site, product)

        if course_run:
            self.assertEqual(response['title'], course.name)
        else:
            self.assertEqual(response['title'], product.title)

        course_cached_response = TieredCache.get_cached_response(cache_key)
        self.assertEqual(course_cached_response.value, response)
예제 #4
0
    def _get_course_data(self, product):
        """
        Return course data.

        Args:
            product (Product): A product that has course_key as attribute (seat or bulk enrollment coupon)
        Returns:
            A dictionary containing product title, course key, image URL, description, and start and end dates.
            Also returns course information found from catalog.
        """
        course_data = {
            'product_title': None,
            'course_key': None,
            'image_url': None,
            'product_description': None,
            'course_start': None,
            'course_end': None,
        }
        course = None

        if product.is_seat_product:
            course_data['course_key'] = CourseKey.from_string(
                product.attr.course_key)

        try:
            course = get_course_info_from_catalog(self.request.site, product)
            if 'src' in course.get('image', {}):
                course_data['image_url'] = course['image']['src']
            elif 'card_image_url' in course:
                course_data['image_url'] = course['card_image_url']
            else:
                try:
                    course_data['image_url'] = course['media']['image']['raw']
                except (KeyError, TypeError):
                    pass

            course_data['product_description'] = course.get(
                'short_description', '')
            course_data['product_title'] = course.get('name') or course.get(
                'title', '')

            # The course start/end dates are not currently used
            # in the default basket templates, but we are adding
            # the dates to the template context so that theme
            # template overrides can make use of them.
            course_data['course_start'] = self._deserialize_date(
                course.get('start'))
            course_data['course_end'] = self._deserialize_date(
                course.get('end'))
        except (ReqConnectionError, SlumberBaseException, Timeout):
            logger.exception(
                'Failed to retrieve data from Discovery Service for course [%s].',
                course_data['course_key'],
            )

        return course_data, course
예제 #5
0
    def get_offers(self, request, voucher):
        """
        Get the course offers associated with the voucher.
        Arguments:
            request (HttpRequest): Request data.
            voucher (Voucher): Oscar Voucher for which the offers are returned.
        Returns:
            dict: Dictionary containing a link to the next page of Course Discovery results and
                  a List of course offers where each offer is represented as a dictionary.
        """
        benefit = voucher.offers.first().benefit
        catalog_query = benefit.range.catalog_query
        catalog_id = benefit.range.course_catalog
        enterprise_catalog = benefit.range.enterprise_customer_catalog
        next_page = None
        offers = []

        if catalog_id:
            catalog = fetch_course_catalog(request.site, catalog_id)
            catalog_query = catalog.get("query") if catalog else catalog_query

        if catalog_query or enterprise_catalog:
            offers, next_page = self.get_offers_from_catalog(
                request, voucher, catalog_query, enterprise_catalog)
        else:
            product_range = voucher.offers.first().benefit.range
            products = product_range.all_products()
            if products:
                product = products[0]
            else:
                raise Product.DoesNotExist
            course_id = product.course_id
            course = get_object_or_404(Course, id=course_id)
            stock_record = get_object_or_404(StockRecord,
                                             product__id=product.id)
            course_info = get_course_info_from_catalog(request.site, product)

            if course_info:
                offers.append(
                    self.get_course_offer_data(
                        benefit=benefit,
                        course=course,
                        course_info=course_info,
                        credit_provider_price=None,
                        multiple_credit_providers=False,
                        is_verified=(course.type == 'verified'),
                        product=product,
                        stock_record=stock_record,
                        voucher=voucher))

        return {'next': next_page, 'results': offers}
 def _already_enrolled_in_course_entitlement(self, seat_product, user, site):
     """
     Check if the user is enrolled in course entitlement for given seat product.
     :return: True if enrolled
     """
     course_uuid = get_course_info_from_catalog(site, seat_product)['course_uuid']
     user_bought_product_ids = OrderLine.objects.filter(
         order__in=user.orders.all()
     ).values_list('product', flat=True)
     return Product.objects.filter(
         pk__in=user_bought_product_ids,
         attributes__code='UUID',
         attribute_values__value_text=course_uuid,
     ).exists()
예제 #7
0
    def test_get_course_info_from_catalog(self):
        """ Check to see if course info gets cached """
        course = CourseFactory()
        self.mock_dynamic_catalog_single_course_runs_api(course)

        cache_key = 'courses_api_detail_{}{}'.format(course.id, self.site.siteconfiguration.partner.short_code)
        cache_key = hashlib.md5(cache_key).hexdigest()
        cached_course = cache.get(cache_key)
        self.assertIsNone(cached_course)

        response = get_course_info_from_catalog(self.request.site, course)

        self.assertEqual(response['title'], course.name)

        cached_course = cache.get(cache_key)
        self.assertEqual(cached_course, response)
예제 #8
0
    def _get_course_data(self, product):
        """
        Return course data.

        Args:
            product (Product): A product that has course_key as attribute (seat or bulk enrollment coupon)
        Returns:
            Dictionary containing course name, course key, course image URL and description.
        """
        if product.is_seat_product:
            course_key = CourseKey.from_string(product.attr.course_key)
        else:
            course_key = None
        course_name = None
        image_url = None
        short_description = None
        course_start = None
        course_end = None

        try:
            course = get_course_info_from_catalog(self.request.site, product)
            try:
                image_url = course['image']['src']
            except (KeyError, TypeError):
                image_url = ''
            short_description = course.get('short_description', '')
            course_name = course.get('title', '')

            # The course start/end dates are not currently used
            # in the default basket templates, but we are adding
            # the dates to the template context so that theme
            # template overrides can make use of them.
            course_start = self._deserialize_date(course.get('start'))
            course_end = self._deserialize_date(course.get('end'))
        except (ConnectionError, SlumberBaseException, Timeout):
            logger.exception(
                'Failed to retrieve data from Discovery Service for course [%s].',
                course_key)

        return {
            'product_title': course_name,
            'course_key': course_key,
            'image_url': image_url,
            'product_description': short_description,
            'course_start': course_start,
            'course_end': course_end,
        }
예제 #9
0
    def test_get_course_info_from_catalog(self):
        """ Check to see if course info gets cached """
        course = CourseFactory()
        self.mock_dynamic_catalog_single_course_runs_api(course)

        cache_key = 'courses_api_detail_{}{}'.format(
            course.id, self.site.siteconfiguration.partner.short_code)
        cache_hash = hashlib.md5(cache_key).hexdigest()
        cached_course = cache.get(cache_hash)
        self.assertIsNone(cached_course)

        response = get_course_info_from_catalog(self.request.site, course)

        self.assertEqual(response['title'], course.name)

        cached_course = cache.get(cache_hash)
        self.assertEqual(cached_course, response)
예제 #10
0
 def get_transaction_parameters(self,
                                basket,
                                request=None,
                                use_client_side_checkout=False,
                                **kwargs):
     """
     approval_url
     """
     trade_id = create_trade_id(basket.id)
     try:
         course_data = get_course_info_from_catalog(
             request.site,
             basket.all_lines()[0].product)
         subject = body = course_data.get('title')
     except Exception, e:
         logger.exception(e)
         subject = body = 'buy course'
예제 #11
0
    def _get_course_data(self, product):
        """
        Return course data.

        Args:
            product (Product): A product that has course_key as attribute (seat or bulk enrollment coupon)
        Returns:
            Dictionary containing course name, course key, course image URL and description.
        """
        course_key = CourseKey.from_string(product.attr.course_key)
        course_name = None
        image_url = None
        short_description = None
        course_start = None
        course_end = None

        try:
            course = get_course_info_from_catalog(self.request.site, course_key)
            try:
                image_url = course['image']['src']
            except (KeyError, TypeError):
                image_url = ''
            short_description = course.get('short_description', '')
            course_name = course.get('title', '')

            # The course start/end dates are not currently used
            # in the default basket templates, but we are adding
            # the dates to the template context so that theme
            # template overrides can make use of them.
            course_start = self._deserialize_date(course.get('start'))
            course_end = self._deserialize_date(course.get('end'))
        except (ConnectionError, SlumberBaseException, Timeout):
            logger.exception('Failed to retrieve data from Catalog Service for course [%s].', course_key)

        return {
            'product_title': course_name,
            'course_key': course_key,
            'image_url': image_url,
            'product_description': short_description,
            'course_start': course_start,
            'course_end': course_end,
        }
예제 #12
0
 def send_receipt_email(self, order, user, site):
     '''
     send receipt email
     '''
     try:
         lines_data = [(line,
                        get_course_info_from_catalog(site, line.product))
                       for line in order.lines.all()]
         content = loader.render_to_string(
             'edx/checkout/receipt_email.html', {
                 'order': order,
                 'lines_data': lines_data
             })
         subject = loader.render_to_string(
             'oscar/customer/emails/commtype_order_placed_subject.txt',
             {'order': order})
         email_msg = EmailMessage(subject.strip(), content,
                                  settings.OSCAR_FROM_EMAIL, [user.email])
         email_msg.content_subtype = "html"
         email_msg.send()
     except Exception, e:
         logger.exception(e)
예제 #13
0
파일: views.py 프로젝트: git-IOS/ecommerce
    def _get_course_data(self, product):
        """
        Return course data.

        Args:
            product (Product): A product that has course_key as attribute (seat or bulk enrollment coupon)
        Returns:
            Dictionary containing course name, course key, course image URL and description.
        """
        if product.is_seat_product:
            course_key = CourseKey.from_string(product.attr.course_key)
        else:
            course_key = None
        course_name = None
        image_url = None
        short_description = None
        course_start = None
        course_end = None
        course = None

        try:
            course = get_course_info_from_catalog(self.request.site, product)
            try:
                image_url = course['image']['src']
            except (KeyError, TypeError):
                image_url = ''
            short_description = course.get('short_description', '')
            course_name = course.get('title', '')

            # The course start/end dates are not currently used
            # in the default basket templates, but we are adding
            # the dates to the template context so that theme
            # template overrides can make use of them.
            course_start = self._deserialize_date(course.get('start'))
            course_end = self._deserialize_date(course.get('end'))
        except (ConnectionError, SlumberBaseException, Timeout):
            logger.exception(
                'Failed to retrieve data from Discovery Service for course [%s].',
                course_key)

        if self.request.basket.num_items == 1 and product.is_enrollment_code_product:
            course_key = CourseKey.from_string(product.attr.course_key)
            if course and course.get('marketing_url', None):
                course_about_url = course['marketing_url']
            else:
                course_about_url = get_lms_course_about_url(
                    course_key=course_key)
            messages.info(
                self.request,
                _('{strong_start}Purchasing just for yourself?{strong_end}{paragraph_start}If you are '
                  'purchasing a single code for someone else, please continue with checkout. However, if you are the '
                  'learner {link_start}go back{link_end} to enroll directly.{paragraph_end}'
                  ).format(strong_start='<strong>',
                           strong_end='</strong>',
                           paragraph_start='<p>',
                           paragraph_end='</p>',
                           link_start='<a href="{course_about}">'.format(
                               course_about=course_about_url),
                           link_end='</a>'),
                extra_tags='safe')

        return {
            'product_title': course_name,
            'course_key': course_key,
            'image_url': image_url,
            'product_description': short_description,
            'course_start': course_start,
            'course_end': course_end,
        }
예제 #14
0
파일: views.py 프로젝트: uzairr/ecommerce
    def get_context_data(self, **kwargs):
        context = super(BasketSummaryView, self).get_context_data(**kwargs)
        formset = context.get('formset', [])
        lines = context.get('line_list', [])
        lines_data = []
        is_verification_required = is_bulk_purchase = False
        switch_link_text = partner_sku = ''
        basket = self.request.basket
        site = self.request.site
        site_configuration = site.siteconfiguration

        for line in lines:
            course_key = CourseKey.from_string(line.product.attr.course_key)
            course_name = None
            image_url = None
            short_description = None
            try:
                course = get_course_info_from_catalog(self.request.site, course_key)
                try:
                    image_url = course['image']['src']
                except (KeyError, TypeError):
                    image_url = ''
                short_description = course.get('short_description', '')
                course_name = course.get('title', '')
            except (ConnectionError, SlumberBaseException, Timeout):
                logger.exception('Failed to retrieve data from Catalog Service for course [%s].', course_key)

            if self.request.site.siteconfiguration.enable_enrollment_codes:
                # Get variables for the switch link that toggles from enrollment codes and seat.
                switch_link_text, partner_sku = get_basket_switch_data(line.product)
                if line.product.get_product_class().name == ENROLLMENT_CODE_PRODUCT_CLASS_NAME:
                    is_bulk_purchase = True
                    # Iterate on message storage so all messages are marked as read.
                    # This will hide the success messages when a user updates the quantity
                    # for an item in the basket.
                    list(messages.get_messages(self.request))

            if line.has_discount:
                benefit = basket.applied_offers().values()[0].benefit
                benefit_value = format_benefit_value(benefit)
            else:
                benefit_value = None

            lines_data.append({
                'seat_type': self._determine_seat_type(line.product),
                'course_name': course_name,
                'course_key': course_key,
                'image_url': image_url,
                'course_short_description': short_description,
                'benefit_value': benefit_value,
                'enrollment_code': line.product.get_product_class().name == ENROLLMENT_CODE_PRODUCT_CLASS_NAME,
                'line': line,
            })

            user = self.request.user
            context.update({
                'analytics_data': prepare_analytics_data(
                    user,
                    self.request.site.siteconfiguration.segment_key,
                    unicode(course_key)
                ),
                'enable_client_side_checkout': False,
            })

            if site_configuration.client_side_payment_processor \
                    and waffle.flag_is_active(self.request, CLIENT_SIDE_CHECKOUT_FLAG_NAME):
                payment_processor_class = site_configuration.get_client_side_payment_processor_class()

                if payment_processor_class:
                    payment_processor = payment_processor_class(site)

                    context.update({
                        'enable_client_side_checkout': True,
                        'payment_form': PaymentForm(user=user, initial={'basket': basket}, label_suffix=''),
                        'payment_url': payment_processor.client_side_payment_url,
                    })
                else:
                    msg = 'Unable to load client-side payment processor [{processor}] for ' \
                          'site configuration [{sc}]'.format(processor=site_configuration.client_side_payment_processor,
                                                             sc=site_configuration.id)
                    raise SiteConfigurationError(msg)

            # Check product attributes to determine if ID verification is required for this basket
            try:
                is_verification_required = line.product.attr.id_verification_required \
                    and line.product.attr.certificate_type != 'credit'
            except AttributeError:
                pass

        context.update({
            'free_basket': context['order_total'].incl_tax == 0,
            'payment_processors': site_configuration.get_payment_processors(),
            'homepage_url': get_lms_url(''),
            'formset_lines_data': zip(formset, lines_data),
            'is_verification_required': is_verification_required,
            'min_seat_quantity': 1,
            'is_bulk_purchase': is_bulk_purchase,
            'switch_link_text': switch_link_text,
            'partner_sku': partner_sku,
        })

        return context
예제 #15
0
    def get_transaction_parameters(self,
                                   basket,
                                   request=None,
                                   use_client_side_checkout=False,
                                   **kwargs):
        """
        """
        # Get the basket, and make sure it belongs to the current user.
        try:
            basket = request.user.baskets.get(id=basket.id)
        except ObjectDoesNotExist:
            return {}

        try:
            # Freeze the basket so that it cannot be modified
            basket.strategy = request.strategy
            Applicator().apply(basket, request.user, request)
            # basket.freeze()
            if basket.total_incl_tax <= 0:
                return {}

            out_trade_no = create_trade_id(basket.id)
            try:
                course_data = get_course_info_from_catalog(
                    request.site,
                    basket.all_lines()[0].product)
                body = course_data.get('title')
            except Exception, e:
                logger.exception(e)
                body = 'buy course'
            order_price = basket.total_incl_tax
            total_fee = int(order_price * 100)
            attach_data = urljoin(get_ecommerce_url(),
                                  reverse('wechatpay:execute'))

            wxpayconf_pub = WxPayConf_pub()
            unifiedorder_pub = UnifiedOrder_pub()
            unifiedorder_pub.setParameter("body", body)
            unifiedorder_pub.setParameter("out_trade_no", out_trade_no)
            unifiedorder_pub.setParameter("total_fee", str(total_fee))
            unifiedorder_pub.setParameter("notify_url",
                                          wxpayconf_pub.NOTIFY_URL)
            unifiedorder_pub.setParameter("trade_type", "NATIVE")
            unifiedorder_pub.setParameter("attach", attach_data)

            code_url = unifiedorder_pub.getCodeUrl()
            img = qrcode.make(code_url)
            buf = BytesIO()
            img.save(buf, format="PNG")
            qrcode_img = base64.b64encode(buf.getvalue())
            if not PaymentProcessorResponse.objects.filter(
                    processor_name=self.NAME,
                    basket=basket).update(transaction_id=out_trade_no):
                self.record_processor_response({},
                                               transaction_id=out_trade_no,
                                               basket=basket)

            parameters = {
                'qrcode_img': qrcode_img,
            }
            return parameters
예제 #16
0
    def is_satisfied(self, offer, basket):  # pylint: disable=unused-argument
        """
        Determines if a user is eligible for an enterprise customer offer
        based on their association with the enterprise customer.

        It also filter out the offer if the `enterprise_customer_catalog_uuid`
        value set on the offer condition does not match with the basket catalog
        value when explicitly provided by the enterprise learner.

        Note: Currently there is no mechanism to prioritize or apply multiple
        offers that may apply as opposed to disqualifying offers if the
        catalog doesn't explicitly match.

        Arguments:
            basket (Basket): Contains information about order line items, the current site,
                             and the user attempting to make the purchase.
        Returns:
            bool
        """
        if not basket.owner:
            # An anonymous user is never linked to any EnterpriseCustomer.
            return False

        enterprise_in_condition = str(self.enterprise_customer_uuid)
        enterprise_catalog = str(self.enterprise_customer_catalog_uuid) if self.enterprise_customer_catalog_uuid \
            else None
        enterprise_name_in_condition = str(self.enterprise_customer_name)
        username = basket.owner.username

        # This variable will hold both course keys and course run identifiers.
        course_ids = []
        for line in basket.all_lines():
            if line.product.is_course_entitlement_product:
                try:
                    response = get_course_info_from_catalog(
                        basket.site, line.product)
                except (ReqConnectionError, KeyError, SlumberHttpBaseException,
                        Timeout) as exc:
                    logger.exception(
                        '[Code Redemption Failure] Unable to apply enterprise offer because basket '
                        'contains a course entitlement product but we failed to get course info from  '
                        'course entitlement product.'
                        'User: %s, Offer: %s, Message: %s, Enterprise: %s, Catalog: %s, Course UUID: %s',
                        username, offer.id, exc, enterprise_in_condition,
                        enterprise_catalog, line.product.attr.UUID)
                    return False
                else:
                    course_ids.append(response['key'])

                    # Skip to the next iteration.
                    continue

            course = line.product.course
            if not course:
                # Basket contains products not related to a course_run.
                # Only log for non-site offers to avoid noise.
                if offer.offer_type != ConditionalOffer.SITE:
                    logger.warning(
                        '[Code Redemption Failure] Unable to apply enterprise offer because '
                        'the Basket contains a product not related to a course_run. '
                        'User: %s, Offer: %s, Product: %s, Enterprise: %s, Catalog: %s',
                        username, offer.id, line.product.id,
                        enterprise_in_condition, enterprise_catalog)
                return False

            course_ids.append(course.id)

        courses_in_basket = ','.join(course_ids)
        user_enterprise = get_enterprise_id_for_user(basket.site, basket.owner)
        if user_enterprise and enterprise_in_condition != user_enterprise:
            # Learner is not linked to the EnterpriseCustomer associated with this condition.
            if offer.offer_type == ConditionalOffer.VOUCHER:
                logger.warning(
                    '[Code Redemption Failure] Unable to apply enterprise offer because Learner\'s '
                    'enterprise (%s) does not match this conditions\'s enterprise (%s). '
                    'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s',
                    user_enterprise, enterprise_in_condition, username,
                    offer.id, enterprise_in_condition, enterprise_catalog,
                    courses_in_basket)

                logger.info(
                    '[Code Redemption Issue] Linking learner with the enterprise in Condition. '
                    'User [%s], Enterprise [%s]', username,
                    enterprise_in_condition)
                get_or_create_enterprise_customer_user(
                    basket.site, enterprise_in_condition, username, False)
                msg = _(
                    'This coupon has been made available through {new_enterprise}. '
                    'To redeem this coupon, you must first logout. When you log back in, '
                    'please select {new_enterprise} as your enterprise '
                    'and try again.').format(
                        new_enterprise=enterprise_name_in_condition)
                messages.warning(
                    crum.get_current_request(),
                    msg,
                )

            return False

        # Verify that the current conditional offer is related to the provided
        # enterprise catalog, this will also filter out offers which don't
        # have `enterprise_customer_catalog_uuid` value set on the condition.
        catalog = self._get_enterprise_catalog_uuid_from_basket(basket)
        if catalog:
            if offer.condition.enterprise_customer_catalog_uuid != catalog:
                logger.warning(
                    'Unable to apply enterprise offer %s because '
                    'Enterprise catalog id on the basket (%s) '
                    'does not match the catalog for this condition (%s).',
                    offer.id, catalog,
                    offer.condition.enterprise_customer_catalog_uuid)
                return False

        try:
            catalog_contains_course = catalog_contains_course_runs(
                basket.site,
                course_ids,
                enterprise_in_condition,
                enterprise_customer_catalog_uuid=enterprise_catalog)
        except (ReqConnectionError, KeyError, SlumberHttpBaseException,
                Timeout) as exc:
            logger.exception(
                '[Code Redemption Failure] Unable to apply enterprise offer because '
                'we failed to check if course_runs exist in the catalog. '
                'User: %s, Offer: %s, Message: %s, Enterprise: %s, Catalog: %s, Courses: %s',
                username, offer.id, exc, enterprise_in_condition,
                enterprise_catalog, courses_in_basket)
            return False

        if not catalog_contains_course:
            # Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs
            # associated with the EnterpriseCustomer.
            logger.warning(
                '[Code Redemption Failure] Unable to apply enterprise offer because '
                'Enterprise catalog does not contain the course(s) in this basket. '
                'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s',
                username, offer.id, enterprise_in_condition,
                enterprise_catalog, courses_in_basket)
            return False

        if not is_offer_max_discount_available(basket, offer):
            logger.warning(
                '[Enterprise Offer Failure] Unable to apply enterprise offer because bookings limit is consumed.'
                'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s, BookingsLimit: %s, TotalDiscount: %s',
                username,
                offer.id,
                enterprise_in_condition,
                enterprise_catalog,
                courses_in_basket,
                offer.max_discount,
                offer.total_discount,
            )
            return False

        if not is_offer_max_user_discount_available(basket, offer):
            logger.warning(
                '[Enterprise Offer Failure] Unable to apply enterprise offer because user bookings limit is consumed.'
                'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s, UserBookingsLimit: %s',
                username, offer.id, enterprise_in_condition,
                enterprise_catalog, courses_in_basket, offer.max_user_discount)
            return False

        return True