Example #1
0
    def _set_single_enrollment_code_warning_if_needed(self, product, course):
        assert product.is_enrollment_code_product

        if self.request.basket.num_items == 1:
            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)

            message_code = 'single-enrollment-code-warning'
            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 ' + message_code)
            message_utils.add_message_data(message_code, 'course_about_url',
                                           course_about_url)
Example #2
0
    def test_enterprise_offer_course_redirect(self):
        """ Verify redirect to the courseware info page. """
        self.prepare_basket(10)
        self.prepare_enterprise_offer()
        self.assertEqual(Order.objects.count(), 0)
        response = self.client.get(self.path)
        self.assertEqual(Order.objects.count(), 1)

        expected_url = get_lms_course_about_url(self.course_run.id)
        self.assertRedirects(response,
                             expected_url,
                             fetch_redirect_response=False)
Example #3
0
    def test_enterprise_customer_successful_free_redemption(self):
        """ Verify the view redirects to LMS course page for enterprise customers on a successful free redemption. """
        code, _ = self.prepare_enterprise_data(
            benefit_value=100,
            consent_enabled=False,
            catalog=self.catalog,
            enterprise_customer_catalog=ENTERPRISE_CUSTOMER_CATALOG,
        )
        self.mock_assignable_enterprise_condition_calls(
            ENTERPRISE_CUSTOMER_CATALOG)
        self.mock_enterprise_learner_api_for_learner_with_no_enterprise()
        self.mock_enterprise_learner_post_api()

        response = self.client.get(self.redeem_url_with_params(code=code),
                                   follow=False)
        self.assertRedirects(response,
                             get_lms_course_about_url(self.course.id),
                             fetch_redirect_response=False)
Example #4
0
    def get(self, request):
        # lms/ecommerce has different user
        if 'username' in request.GET and request.user.username != request.GET.get(
                'username'):
            logout(request)
            query_dict = request.GET.dict()
            query_dict.pop('username')
            redirect_url = '{path}?{query_string}'.format(
                path=request.path, query_string=urlencode(query_dict))
            logger.info('logout user {username}'.format(
                username=request.GET.get('username')))
            return redirect(redirect_url)

        partner = get_partner_for_site(request)

        skus = [escape(sku) for sku in request.GET.getlist('sku')]
        code = request.GET.get('code', None)

        if not skus:
            return HttpResponseBadRequest(_('No SKUs provided.'))

        products = Product.objects.filter(stockrecords__partner=partner,
                                          stockrecords__partner_sku__in=skus)
        if not products:
            return HttpResponseBadRequest(
                _('Products with SKU(s) [{skus}] do not exist.').format(
                    skus=', '.join(skus)))

        try:
            lms_api = EdxRestApiClient(
                get_lms_url('/api/v1/vip/'),
                oauth_access_token=request.user.access_token,
                append_slash=False)
            # user is vip, redirect lms course about
            if lms_api.info().get().get('data', {}).get('status') is True:
                course_key = CourseKey.from_string(products[0].attr.course_key)
                return redirect(
                    get_lms_course_about_url(course_key=course_key))
        except Exception, e:
            logger.exception(e)
Example #5
0
    def test_enterprise_customer_successful_redemption(self):
        """ Verify the view redirects to LMS courseware page when valid consent is provided. """
        code, _ = self.prepare_enterprise_data(
            enterprise_customer_catalog=ENTERPRISE_CUSTOMER_CATALOG)
        self.mock_assignable_enterprise_condition_calls(
            ENTERPRISE_CUSTOMER_CATALOG)
        self.mock_enterprise_learner_api_for_learner_with_no_enterprise()
        self.mock_enterprise_learner_post_api()

        consent_token = get_enterprise_customer_data_sharing_consent_token(
            self.request.user.access_token, self.course.id,
            ENTERPRISE_CUSTOMER)

        response = self.redeem_coupon(code=code, consent_token=consent_token)
        self.assertRedirects(response,
                             get_lms_course_about_url(self.course.id),
                             fetch_redirect_response=False)

        last_request = responses.calls[-1].request

        self.assertEqual(last_request.path_url,
                         '/api/enrollment/v1/enrollment')
        self.assertEqual(last_request.method, 'POST')
Example #6
0
    def get_redirect_url(self, *args, **kwargs):
        request = self.request
        site = request.site
        basket = Basket.get_basket(request.user, site)

        if not basket.is_empty:
            # Need to re-apply the voucher to the basket.
            Applicator().apply(basket, request.user, request)
            if basket.total_incl_tax != Decimal(0):
                raise BasketNotFreeError(
                    'Basket [{}] is not free. User affected [{}]'.format(
                        basket.id, basket.owner.id))

            order = self.place_free_order(basket)

            if has_enterprise_offer(basket):
                # Skip the receipt page and redirect to the LMS
                # if the order is free due to an Enterprise-related offer.
                program_uuid = get_program_uuid(order)
                if program_uuid:
                    url = get_lms_program_dashboard_url(program_uuid)
                else:
                    course_run_id = order.lines.all()[:1].get(
                    ).product.course.id
                    url = get_lms_course_about_url(course_run_id)
            else:
                receipt_path = get_receipt_page_url(
                    order_number=order.number,
                    site_configuration=order.site.siteconfiguration,
                    disable_back_button=True,
                )
                url = site.siteconfiguration.build_lms_url(receipt_path)
        else:
            # If a user's basket is empty redirect the user to the basket summary
            # page which displays the appropriate message for empty baskets.
            url = reverse('basket:summary')
        return url
Example #7
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
        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,
        }
Example #8
0
    def get(self, request):  # pylint: disable=too-many-statements
        """
        Looks up the passed code and adds the matching product to a basket,
        then applies the voucher and if the basket total is FREE places the order and
        enrolls the user in the course.
        """
        template_name = 'coupons/_offer_error.html'
        code = request.GET.get('code')
        sku = request.GET.get('sku')
        failure_url = request.GET.get('failure_url')
        site_configuration = request.site.siteconfiguration

        if not code:
            return render(request, template_name,
                          {'error': _('Code not provided.')})
        if not sku:
            return render(request, template_name,
                          {'error': _('SKU not provided.')})

        try:
            voucher = Voucher.objects.get(code=code)
        except Voucher.DoesNotExist:
            msg = 'No voucher found with code {code}'.format(code=code)
            return render(request, template_name, {'error': _(msg)})

        try:
            product = StockRecord.objects.get(partner_sku=sku).product
        except StockRecord.DoesNotExist:
            return render(request, template_name,
                          {'error': _('The product does not exist.')})

        valid_voucher, msg, hide_error_message = voucher_is_valid(
            voucher, [product], request)
        if not valid_voucher:
            logger.warning(
                '[Code Redemption Failure] The voucher is not valid for this product. '
                'User: %s, Product: %s, Code: %s, Message: %s',
                request.user.username, product.id, voucher.code, msg)
            return render(request, template_name, {
                'error': msg,
                'hide_error_message': hide_error_message
            })

        offer = voucher.best_offer
        if not offer.is_email_valid(request.user.email):
            logger.warning(
                '[Code Redemption Failure] Unable to apply offer because the user\'s email '
                'does not meet the domain requirements. '
                'User: %s, Offer: %s, Code: %s', request.user.username,
                offer.id, voucher.code)
            return render(
                request, template_name,
                {'error': _('You are not eligible to use this coupon.')})

        email_confirmation_response = get_redirect_to_email_confirmation_if_required(
            request, offer, product)
        if email_confirmation_response:
            return email_confirmation_response

        try:
            enterprise_customer = get_enterprise_customer_from_voucher(
                request.site, voucher)
        except EnterpriseDoesNotExist as e:
            # If an EnterpriseException is caught while pulling the EnterpriseCustomer, that means there's no
            # corresponding EnterpriseCustomer in the Enterprise service (which should never happen).
            logger.exception(str(e))
            return render(
                request, template_name, {
                    'error':
                    _('Couldn\'t find a matching Enterprise Customer for this coupon.'
                      )
                })

        if enterprise_customer and product.is_course_entitlement_product:
            return render(
                request, template_name, {
                    'error':
                    _('This coupon is not valid for purchasing a program. Try using this on an individual '
                      'course in the program. If you need assistance, contact edX support.'
                      )
                })

        if enterprise_customer is not None and enterprise_customer_user_needs_consent(
                request.site,
                enterprise_customer['id'],
                product.course.id,
                request.user.username,
        ):
            consent_token = get_enterprise_customer_data_sharing_consent_token(
                request.user.access_token, product.course.id,
                enterprise_customer['id'])
            received_consent_token = request.GET.get('consent_token')
            if received_consent_token:
                # If the consent token is set, then the user is returning from the consent view. Render out an error
                # if the computed token doesn't match the one received from the redirect URL.
                if received_consent_token != consent_token:
                    logger.warning(
                        '[Code Redemption Failure] Unable to complete code redemption because of '
                        'invalid consent. User: %s, Offer: %s, Code: %s',
                        request.user.username, offer.id, voucher.code)
                    return render(request, template_name, {
                        'error':
                        _('Invalid data sharing consent token provided.')
                    })
            else:
                # The user hasn't been redirected to the interstitial consent view to collect consent, so
                # redirect them now.
                redirect_url = get_enterprise_course_consent_url(
                    request.site,
                    code,
                    sku,
                    consent_token,
                    product.course.id,
                    enterprise_customer['id'],
                    failure_url=failure_url,
                    consent_url_param_dict=parse_consent_params(request),
                )
                return HttpResponseRedirect(redirect_url)

        try:
            basket = prepare_basket(request, [product], voucher)
        except AlreadyPlacedOrderException:
            msg = _('You have already purchased {course} seat.').format(
                course=product.course.name)
            return render(request, template_name, {'error': msg})

        if basket.total_excl_tax == 0:
            try:
                order = self.place_free_order(basket)
                if enterprise_customer:
                    return HttpResponseRedirect(
                        get_lms_course_about_url(product.course.id))
                return HttpResponseRedirect(
                    get_receipt_page_url(
                        site_configuration,
                        order.number,
                        disable_back_button=True,
                    ), )
            except:  # pylint: disable=bare-except
                logger.exception(
                    'Failed to create a free order for basket [%d]', basket.id)
                return absolute_redirect(self.request, 'checkout:error')

        if enterprise_customer:
            if is_voucher_applied(basket, voucher):
                message = _(
                    'A discount has been applied, courtesy of {enterprise_customer_name}.'
                ).format(
                    enterprise_customer_name=enterprise_customer.get('name'))
                messages.info(self.request, message)
            else:
                # Display a generic message to the user if a condition-specific
                # message has not already been added by an unsatified Condition class.
                if not messages.get_messages(self.request):
                    messages.warning(
                        self.request,
                        _('This coupon code is not valid for this course. Try a different course.'
                          ))
                self.request.basket.vouchers.remove(voucher)

        # The coupon_redeem_redirect query param is used to communicate to the Payment MFE that it may redirect
        # and should not display the payment form before making that determination.
        # TODO: It would be cleaner if the user could be redirected to their final destination up front.
        redirect_url = get_payment_microfrontend_or_basket_url(
            self.request) + "?coupon_redeem_redirect=1"
        return HttpResponseRedirect(redirect_url)