Exemple #1
0
    def get_context_data(self, **kwargs):
        context = super(Checkout, self).get_context_data(**kwargs)

        course = get_object_or_404(Course, id=kwargs.get('course_id'))
        context['course'] = course

        deadline = self._check_credit_eligibility(self.request.user, kwargs.get('course_id'))
        if not deadline:
            context.update({
                'error': _('An error has occurred. We could not confirm that you are eligible for course credit. '
                           'Try the transaction again.')
            })
            return context

        partner = get_partner_for_site(self.request)
        strategy = self.request.strategy
        # Audit seats do not have a `certificate_type` attribute, so
        # we use getattr to avoid an exception.
        credit_seats = []

        for seat in course.seat_products:
            if getattr(seat.attr, 'certificate_type', None) != self.CREDIT_MODE:
                continue

            purchase_info = strategy.fetch_for_product(seat)
            if purchase_info.availability.is_available_to_buy and seat.stockrecords.filter(partner=partner).exists():
                credit_seats.append(seat)

        if not credit_seats:
            msg = _(
                'Credit is not currently available for "{course_name}". If you are currently enrolled in the '
                'course, please try again after all grading is complete. If you need additional assistance, '
                'please contact the {site_name} Support Team.'
            ).format(
                course_name=course.name,
                site_name=self.request.site.name
            )

            context.update({'error': msg})
            return context

        providers = self._get_providers_detail(credit_seats)
        if not providers:
            context.update({
                'error': _('An error has occurred. We could not confirm that the institution you selected offers this '
                           'course credit. Try the transaction again.')
            })
            return context

        context.update({
            'analytics_data': prepare_analytics_data(
                self.request.user,
                self.request.site.siteconfiguration.segment_key,
            ),
            'course': course,
            'deadline': deadline,
            'providers': providers,
        })

        return context
Exemple #2
0
    def get(self, request):
        partner = get_partner_for_site(request)

        skus = 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)))

        voucher = Voucher.objects.get(code=code) if code else None
        try:
            prepare_basket(request, products, voucher)
        except AlreadyPlacedOrderException:
            return render(
                request, 'edx/error.html',
                {'error': _('You have already purchased these products')})
        messages.add_message(
            request, messages.INFO,
            'Already purchased products will not be added to basket.')
        return HttpResponseRedirect(reverse('basket:summary'), status=303)
Exemple #3
0
    def get(self, request):
        partner = get_partner_for_site(request)

        skus = 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)))

        voucher = Voucher.objects.get(code=code) if code else None

        if voucher is None:
            # If there is an Enterprise entitlement available for this basket,
            # we redirect to the CouponRedeemView to apply the discount to the
            # basket and handle the data sharing consent requirement.
            code_redemption_redirect = get_enterprise_code_redemption_redirect(
                request,
                products,
                skus,
                'basket:add-multi'
            )
            if code_redemption_redirect:
                return code_redemption_redirect

        try:
            prepare_basket(request, products, voucher)
        except AlreadyPlacedOrderException:
            return render(request, 'edx/error.html', {'error': _('You have already purchased these products')})
        url = add_utm_params_to_url(reverse('basket:summary'), self.request.GET.items())
        return HttpResponseRedirect(url, status=303)
Exemple #4
0
    def _get_providers_detail(self, credit_seats):
        """ Get details for the credit providers for the given credit seats.

        Arguments:
            credit_seats (Products[]): List of credit_seats objects.

        Returns:
            A list of dictionaries with provider(s) detail.
        """
        providers = self._get_providers_from_lms(credit_seats)
        if not providers:
            return None

        providers_dict = {}
        for provider in providers:
            providers_dict[provider['id']] = provider

        partner = get_partner_for_site(self.request)
        for seat in credit_seats:
            stockrecord = seat.stockrecords.filter(partner=partner).first()
            providers_dict[seat.attr.credit_provider].update({
                'price': stockrecord.price_excl_tax,
                'sku': stockrecord.partner_sku,
                'credit_hours': seat.attr.credit_hours
            })

        return providers_dict.values()
Exemple #5
0
    def get(self, request):
        partner = get_partner_for_site(request)

        sku = request.GET.get('sku', None)
        code = request.GET.get('code', None)

        if not sku:
            return HttpResponseBadRequest(_('No SKU provided.'))

        if code:
            voucher, __ = get_voucher_from_code(code=code)
        else:
            voucher = None

        try:
            product = StockRecord.objects.get(partner=partner, partner_sku=sku).product
        except StockRecord.DoesNotExist:
            return HttpResponseBadRequest(_('SKU [{sku}] does not exist.'.format(sku=sku)))

        purchase_info = request.strategy.fetch_for_product(product)
        if not purchase_info.availability.is_available_to_buy:
            return HttpResponseBadRequest(_('Product [{product}] not available to buy.'.format(product=product.title)))

        prepare_basket(request, product, voucher)
        return HttpResponseRedirect(reverse('basket:summary'), status=303)
Exemple #6
0
    def get(self, request):
        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)))

        logger.info('Starting payment flow for user[%s] for products[%s].', request.user.username, skus)

        voucher = Voucher.objects.get(code=code) if code else None

        if voucher is None:
            # If there is an Enterprise entitlement available for this basket,
            # we redirect to the CouponRedeemView to apply the discount to the
            # basket and handle the data sharing consent requirement.
            code_redemption_redirect = get_enterprise_code_redemption_redirect(
                request,
                products,
                skus,
                'basket:basket-add'
            )
            if code_redemption_redirect:
                return code_redemption_redirect

        # check availability of products
        unavailable_product_ids = []
        for product in products:
            purchase_info = request.strategy.fetch_for_product(product)
            if not purchase_info.availability.is_available_to_buy:
                logger.warning('Product [%s] is not available to buy.', product.title)
                unavailable_product_ids.append(product.id)

        available_products = products.exclude(id__in=unavailable_product_ids)
        if not available_products:
            msg = _('No product is available to buy.')
            return HttpResponseBadRequest(msg)

        # Associate the user's email opt in preferences with the basket in
        # order to opt them in later as part of fulfillment
        BasketAttribute.objects.update_or_create(
            basket=request.basket,
            attribute_type=BasketAttributeType.objects.get(name=EMAIL_OPT_IN_ATTRIBUTE),
            defaults={'value_text': request.GET.get('email_opt_in') == 'true'},
        )

        try:
            prepare_basket(request, available_products, voucher)
        except AlreadyPlacedOrderException:
            return render(request, 'edx/error.html', {'error': _('You have already purchased these products')})
        url = add_utm_params_to_url(reverse('basket:summary'), self.request.GET.items())
        return HttpResponseRedirect(url, status=303)
Exemple #7
0
 def _get_products(self, request, skus):
     partner = get_partner_for_site(request)
     products = Product.objects.filter(stockrecords__partner=partner,
                                       stockrecords__partner_sku__in=skus)
     if not products:
         raise BadRequestException(
             _('Products with SKU(s) [{skus}] do not exist.').format(
                 skus=', '.join(skus)))
     return products
Exemple #8
0
    def get(self, request):
        partner = get_partner_for_site(request)

        sku = request.GET.get('sku', None)
        code = request.GET.get('code', None)

        if not sku:
            return HttpResponseBadRequest(_('No SKU provided.'))

        if code:
            voucher, __ = get_voucher_from_code(code=code)
        else:
            voucher = None

        try:
            product = StockRecord.objects.get(partner=partner, partner_sku=sku).product
            course_key = product.attr.course_key

            api = EdxRestApiClient(
                get_lms_enrollment_base_api_url(),
                oauth_access_token=request.user.access_token,
                append_slash=False
            )
            logger.debug(
                'Getting enrollment information for [%s] in [%s].',
                request.user.username,
                course_key
            )
            status = api.enrollment(','.join([request.user.username, course_key])).get()
            username = request.user.username
            seat_type = mode_for_seat(product)
            if status and status.get('mode') == seat_type and status.get('is_active'):
                logger.warning(
                    'User [%s] attempted to repurchase the [%s] seat of course [%s]',
                    username,
                    seat_type,
                    course_key
                )
                return HttpResponseBadRequest(_('You are already enrolled in {course}.').format(
                    course=product.course.name))
        except StockRecord.DoesNotExist:
            return HttpResponseBadRequest(_('SKU [{sku}] does not exist.').format(sku=sku))
        except (ConnectionError, SlumberBaseException, Timeout) as ex:
            logger.exception(
                'Failed to retrieve enrollment details for [%s] in course [%s], Because of [%s]',
                request.user.username,
                course_key,
                ex,
            )
            return HttpResponseBadRequest(_('An error occurred while retrieving enrollment details. Please try again.'))
        purchase_info = request.strategy.fetch_for_product(product)
        if not purchase_info.availability.is_available_to_buy:
            return HttpResponseBadRequest(_('Product [{product}] not available to buy.').format(product=product.title))

        prepare_basket(request, product, voucher)
        return HttpResponseRedirect(reverse('basket:summary'), status=303)
    def get(self, request, *_args, **_kwargs):
        course_ids = request.GET.get('course_ids')
        commit = request.GET.get('commit', False)
        commit = commit in ('1', 'true')
        partner = get_partner_for_site(request)

        # Capture all output and logging
        out = StringIO()
        err = StringIO()
        log = StringIO()

        root_logger = logging.getLogger()
        log_handler = logging.StreamHandler(log)
        formatter = logging.Formatter(
            "%(asctime)s - %(name)s - %(levelname)s - %(message)s")
        log_handler.setFormatter(formatter)
        root_logger.addHandler(log_handler)

        try:
            # Log who ran this request
            msg = u'User [%s] requested course migration for [%s]. '
            if commit:
                msg += u'The changes will be committed to the database.'
            else:
                msg += u'The changes will NOT be committed to the database.'

            user = request.user
            logger.info(msg, user.username, course_ids)

            if not course_ids:
                return HttpResponse('No course_ids specified.', status=400)

            course_ids = course_ids.split(',')

            call_command('migrate_course',
                         *course_ids,
                         access_token=user.access_token,
                         commit=commit,
                         partner_short_code=partner.short_code,
                         settings=os.environ['DJANGO_SETTINGS_MODULE'],
                         stdout=out,
                         stderr=err)

            # Format the output for display
            output = u'STDOUT\n{out}\n\nSTDERR\n{err}\n\nLOG\n{log}'.format(
                out=out.getvalue(), err=err.getvalue(), log=log.getvalue())

            return HttpResponse(output, content_type='text/plain')
        finally:
            # Remove the log capture handler and close all streams
            root_logger.removeHandler(log_handler)
            log.close()
            out.close()
            err.close()
Exemple #10
0
    def get(self, request):
        partner = get_partner_for_site(request)

        sku = request.GET.get('sku', None)
        code = request.GET.get('code', None)

        if not sku:
            return HttpResponseBadRequest(_('No SKU provided.'))

        voucher = Voucher.objects.get(code=code) if code else None

        try:
            product = StockRecord.objects.get(partner=partner,
                                              partner_sku=sku).product
        except StockRecord.DoesNotExist:
            return HttpResponseBadRequest(
                _('SKU [{sku}] does not exist.').format(sku=sku))

        if voucher is None:
            # Find and apply the enterprise entitlement on the learner basket
            voucher = get_entitlement_voucher(request, product)

        # If the product isn't available then there's no reason to continue with the basket addition
        purchase_info = request.strategy.fetch_for_product(product)
        if not purchase_info.availability.is_available_to_buy:
            msg = _('Product [{product}] not available to buy.').format(
                product=product.title)
            return HttpResponseBadRequest(msg)

        # If the product is not an Enrollment Code, we check to see if the user is already
        # enrolled to prevent double-enrollment and/or accidental coupon usage
        if product.get_product_class(
        ).name != ENROLLMENT_CODE_PRODUCT_CLASS_NAME:
            try:
                if request.user.is_user_already_enrolled(request, product):
                    logger.warning(
                        'User [%s] attempted to repurchase the [%s] seat of course [%s]',
                        request.user.username, mode_for_seat(product),
                        product.attr.course_key)
                    msg = _('You are already enrolled in {course}.').format(
                        course=product.course.name)
                    return HttpResponseBadRequest(msg)
            except (ConnectionError, SlumberBaseException, Timeout):
                msg = _(
                    'An error occurred while retrieving enrollment details. Please try again.'
                )
                return HttpResponseBadRequest(msg)

        # At this point we're either adding an Enrollment Code product to the basket,
        # or the user is adding a Seat product for which they are not already enrolled
        prepare_basket(request, product, voucher)
        return HttpResponseRedirect(reverse('basket:summary'), status=303)
Exemple #11
0
    def _get_providers_detail(self, credit_seats):
        """ Get details for the credit providers for the given credit seats.

        Arguments:
            credit_seats (Products[]): List of credit_seats objects.

        Returns:
            A list of dictionaries with provider(s) detail.
        """
        code = self.request.GET.get('code')
        if code:
            voucher = Voucher.objects.get(code=code)
            discount_type = voucher.benefit.type
            discount_value = voucher.benefit.value

        providers = self._get_providers_from_lms(credit_seats)
        if not providers:
            return None

        providers_dict = {}
        for provider in providers:
            providers_dict[provider['id']] = provider

        partner = get_partner_for_site(self.request)
        for seat in credit_seats:
            stockrecord = seat.stockrecords.filter(partner=partner).first()
            new_price = None
            discount = None
            if code:
                discount = format_benefit_value(voucher.benefit)
                if discount_type == 'Percentage':
                    new_price = stockrecord.price_excl_tax - (
                        stockrecord.price_excl_tax * (discount_value / 100))
                else:
                    new_price = stockrecord.price_excl_tax - discount_value
                new_price = '{0:.2f}'.format(new_price)
            providers_dict[seat.attr.credit_provider].update({
                'price':
                stockrecord.price_excl_tax,
                'sku':
                stockrecord.partner_sku,
                'credit_hours':
                seat.attr.credit_hours,
                'discount':
                discount,
                'new_price':
                new_price
            })

        return providers_dict.values()
Exemple #12
0
    def get(self, request, *_args, **_kwargs):
        course_ids = request.GET.get('course_ids')
        commit = request.GET.get('commit', False)
        commit = commit in ('1', 'true')
        partner = get_partner_for_site(request)

        # Capture all output and logging
        out = StringIO()
        err = StringIO()
        log = StringIO()

        root_logger = logging.getLogger()
        log_handler = logging.StreamHandler(log)
        formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
        log_handler.setFormatter(formatter)
        root_logger.addHandler(log_handler)

        try:
            # Log who ran this request
            msg = u'User [%s] requested course migration for [%s]. '
            if commit:
                msg += u'The changes will be committed to the database.'
            else:
                msg += u'The changes will NOT be committed to the database.'

            user = request.user
            logger.info(msg, user.username, course_ids)

            if not course_ids:
                return HttpResponse('No course_ids specified.', status=400)

            course_ids = course_ids.split(',')

            call_command('migrate_course', *course_ids, access_token=user.access_token, commit=commit,
                         partner_short_code=partner.short_code, settings=os.environ['DJANGO_SETTINGS_MODULE'],
                         stdout=out, stderr=err)

            # Format the output for display
            output = u'STDOUT\n{out}\n\nSTDERR\n{err}\n\nLOG\n{log}'.format(out=out.getvalue(), err=err.getvalue(),
                                                                            log=log.getvalue())

            return HttpResponse(output, content_type='text/plain')
        finally:
            # Remove the log capture handler and close all streams
            root_logger.removeHandler(log_handler)
            log.close()
            out.close()
            err.close()
Exemple #13
0
    def get(self, request):
        partner = get_partner_for_site(request)

        sku = request.GET.get('sku', None)
        code = request.GET.get('code', None)

        if not sku:
            return HttpResponseBadRequest(_('No SKU provided.'))

        voucher = Voucher.objects.get(code=code) if code else None

        try:
            product = StockRecord.objects.get(partner=partner,
                                              partner_sku=sku).product
        except StockRecord.DoesNotExist:
            return HttpResponseBadRequest(
                _('SKU [{sku}] does not exist.').format(sku=sku))

        logger.info('Starting payment flow for user[%s] for product[%s].',
                    request.user.username, sku)

        if voucher is None:
            # If there is an Enterprise entitlement available for this basket,
            # we redirect to the CouponRedeemView to apply the discount to the
            # basket and handle the data sharing consent requirement.
            code_redemption_redirect = get_enterprise_code_redemption_redirect(
                request, [product], [sku], 'basket:single-item')
            if code_redemption_redirect:
                return code_redemption_redirect

        # If the product isn't available then there's no reason to continue with the basket addition
        purchase_info = request.strategy.fetch_for_product(product)
        if not purchase_info.availability.is_available_to_buy:
            msg = _('Product [{product}] not available to buy.').format(
                product=product.title)
            return HttpResponseBadRequest(msg)

        # At this point we're either adding an Enrollment Code product to the basket,
        # or the user is adding a Seat product for which they are not already enrolled
        try:
            prepare_basket(request, [product], voucher)
        except AlreadyPlacedOrderException:
            msg = _('You have already purchased {course} seat.').format(
                course=product.title)
            return render(request, 'edx/error.html', {'error': msg})
        url = add_utm_params_to_url(reverse('basket:summary'),
                                    self.request.GET.items())
        return HttpResponseRedirect(url, status=303)
Exemple #14
0
    def get(self, request):
        partner = get_partner_for_site(request)

        sku = request.GET.get('sku', None)
        code = request.GET.get('code', None)

        if not sku:
            return HttpResponseBadRequest(_('No SKU provided.'))

        voucher = Voucher.objects.get(code=code) if code else None

        try:
            product = StockRecord.objects.get(partner=partner, partner_sku=sku).product
        except StockRecord.DoesNotExist:
            return HttpResponseBadRequest(_('SKU [{sku}] does not exist.').format(sku=sku))

        if voucher is None:
            # Find and apply the enterprise entitlement on the learner basket
            voucher = get_entitlement_voucher(request, product)

        # If the product isn't available then there's no reason to continue with the basket addition
        purchase_info = request.strategy.fetch_for_product(product)
        if not purchase_info.availability.is_available_to_buy:
            msg = _('Product [{product}] not available to buy.').format(product=product.title)
            return HttpResponseBadRequest(msg)

        # If the product is not an Enrollment Code, we check to see if the user is already
        # enrolled to prevent double-enrollment and/or accidental coupon usage
        if product.get_product_class().name != ENROLLMENT_CODE_PRODUCT_CLASS_NAME:
            try:
                if request.user.is_user_already_enrolled(request, product):
                    logger.warning(
                        'User [%s] attempted to repurchase the [%s] seat of course [%s]',
                        request.user.username,
                        mode_for_seat(product),
                        product.attr.course_key
                    )
                    msg = _('You are already enrolled in {course}.').format(course=product.course.name)
                    return HttpResponseBadRequest(msg)
            except (ConnectionError, SlumberBaseException, Timeout):
                msg = _('An error occurred while retrieving enrollment details. Please try again.')
                return HttpResponseBadRequest(msg)

        # At this point we're either adding an Enrollment Code product to the basket,
        # or the user is adding a Seat product for which they are not already enrolled
        prepare_basket(request, product, voucher)
        return HttpResponseRedirect(reverse('basket:summary'), status=303)
Exemple #15
0
    def get(self, request):
        partner = get_partner_for_site(request)

        skus = 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)))

        voucher = Voucher.objects.get(code=code) if code else None
        prepare_basket(request, products, voucher)
        return HttpResponseRedirect(reverse('basket:summary'), status=303)
Exemple #16
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)
Exemple #17
0
    def get(self, request):
        """ Calculate basket totals given a list of sku's

        Create a temporary basket add the sku's and apply an optional voucher code.
        Then calculate the total price less discounts. If a voucher code is not
        provided apply a voucher in the Enterprise entitlements available
        to the user.

        Arguments:
            sku (string): A list of sku(s) to calculate
            code (string): Optional voucher code to apply to the basket.
            username (string): Optional username of a user for which to caclulate the basket.

        Returns:
            JSON: {
                    'total_incl_tax_excl_discounts': basket.total_incl_tax_excl_discounts,
                    'total_incl_tax': basket.total_incl_tax,
                    'currency': basket.currency
                }
        """
        partner = get_partner_for_site(request)
        skus = request.GET.getlist('sku')
        if not skus:
            return HttpResponseBadRequest(_('No SKUs provided.'))

        code = request.GET.get('code', None)
        try:
            voucher = Voucher.objects.get(code=code) if code else None
        except Voucher.DoesNotExist:
            voucher = None

        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)))

        # If there is only one product apply an Enterprise entitlement voucher
        if not voucher and len(products) == 1:
            voucher = get_entitlement_voucher(request, products[0])

        username = request.GET.get('username', default='')
        user = request.user
        # If a username is passed in, validate that the user has staff access or is the same user.
        if username:
            if user.is_staff or (user.username.lower() == username.lower()):
                try:
                    user = User.objects.get(username=username)
                except User.DoesNotExist:
                    logger.debug('Request username: [%s] does not exist',
                                 username)
            else:
                return HttpResponseForbidden('Unauthorized user credentials')

        # We wrap this in an atomic operation so we never commit this to the db.
        # This is to avoid merging this temporary basket with a real user basket.
        try:
            with transaction.atomic():
                basket = Basket(owner=user, site=request.site)
                basket.strategy = Selector().strategy(user=user)

                for product in products:
                    basket.add_product(product, 1)

                if voucher:
                    basket.vouchers.add(voucher)

                # Calculate any discounts on the basket.
                Applicator().apply(basket, user=user, request=request)

                discounts = []
                if basket.offer_discounts:
                    discounts = basket.offer_discounts
                if basket.voucher_discounts:
                    discounts.extend(basket.voucher_discounts)

                response = {
                    'total_incl_tax_excl_discounts':
                    basket.total_incl_tax_excl_discounts,
                    'total_incl_tax': basket.total_incl_tax,
                    'currency': basket.currency
                }
                raise api_exceptions.TemporaryBasketException
        except api_exceptions.TemporaryBasketException:
            pass
        except:  # pylint: disable=bare-except
            logger.exception(
                'Failed to calculate basket discount for SKUs [%s] and voucher [%s].',
                skus, code)
            raise

        return Response(response)
Exemple #18
0
    def get(self, request):
        """ Calculate basket totals given a list of sku's

        Create a temporary basket add the sku's and apply an optional voucher code.
        Then calculate the total proce less discounts.

        Arguments:
            sku (string): A list of sku(s) to calculate
            code (string): Optional voucher code to apply to the basket.

        Returns:
            JSON: {
                    'total_incl_tax_excl_discounts': basket.total_incl_tax_excl_discounts,
                    'total_incl_tax': basket.total_incl_tax,
                    'currency': basket.currency
                }
        """
        partner = get_partner_for_site(request)
        skus = request.GET.getlist('sku')
        if not skus:
            return HttpResponseBadRequest(_('No SKUs provided.'))

        code = request.GET.get('code', None)
        try:
            voucher = Voucher.objects.get(code=code) if code else None
        except Voucher.DoesNotExist:
            voucher = None

        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)))

        # We wrap this in an atomic operation so we never commit this to the db.
        # This is to avoid merging this temporary basket with a real user basket.
        try:
            with transaction.atomic():
                basket = Basket(owner=request.user)
                basket.strategy = Selector().strategy(user=request.user)

                for product in products:
                    basket.add_product(product, 1)

                if voucher:
                    basket.vouchers.add(voucher)

                # Calculate any discounts on the basket.
                Applicator().apply(basket, request)

                discounts = []

                if basket.offer_discounts:
                    discounts = basket.offer_discounts
                if basket.voucher_discounts:
                    discounts.extend(basket.voucher_discounts)

                response = {
                    'total_incl_tax_excl_discounts':
                    basket.total_incl_tax_excl_discounts,
                    'total_incl_tax': basket.total_incl_tax,
                    'currency': basket.currency
                }
                raise Exception
        except:  # pylint: disable=bare-except
            pass

        return Response(response)
Exemple #19
0
 def test_get_partner_for_site(self):
     """ Verify the function returns the Partner associated with the request's Site. """
     request = Mock()
     request.site = self.site
     self.assertEqual(get_partner_for_site(request), self.partner)
Exemple #20
0
    def get_context_data(self, **kwargs):
        context = super(Checkout, self).get_context_data(**kwargs)

        course = get_object_or_404(Course, id=kwargs.get('course_id'))
        context['course'] = course

        deadline = self._check_credit_eligibility(self.request.user, kwargs.get('course_id'))
        if not deadline:
            context.update({
                'error': _('An error has occurred. We could not confirm that you are eligible for course credit. '
                           'Try the transaction again.')
            })
            return context

        partner = get_partner_for_site(self.request)
        strategy = self.request.strategy
        # Audit seats do not have a `certificate_type` attribute, so
        # we use getattr to avoid an exception.
        credit_seats = []

        for seat in course.seat_products:
            if getattr(seat.attr, 'certificate_type', None) != self.CREDIT_MODE:
                continue

            purchase_info = strategy.fetch_for_product(seat)
            if purchase_info.availability.is_available_to_buy and seat.stockrecords.filter(partner=partner).exists():
                credit_seats.append(seat)

        if not credit_seats:
            msg = _(
                'Credit is not currently available for "{course_name}". If you are currently enrolled in the '
                'course, please try again after all grading is complete. If you need additional assistance, '
                'please contact the {site_name} Support Team.'
            ).format(
                course_name=course.name,
                site_name=self.request.site.name
            )

            context.update({'error': msg})
            return context

        providers = self._get_providers_detail(credit_seats)
        if not providers:
            context.update({
                'error': _('An error has occurred. We could not confirm that the institution you selected offers this '
                           'course credit. Try the transaction again.')
            })
            return context

        # Make button text for each processor which will be shown to user.
        processors_dict = OrderedDict()
        for path in settings.PAYMENT_PROCESSORS:
            processor_class = get_processor_class(path)
            if not processor_class.is_enabled():
                continue
            processor = processor_class.NAME.lower()
            if processor == 'cybersource':
                processors_dict[processor] = 'Checkout'
            elif processor == 'paypal':
                processors_dict[processor] = 'Checkout with PayPal'
            else:
                processors_dict[processor] = 'Checkout with {}'.format(processor)
        if len(processors_dict) == 0:
            context.update({
                'error': _(
                    'All payment options are currently unavailable. Try the transaction again in a few minutes.'
                )
            })

        context.update({
            'course': course,
            'payment_processors': processors_dict,
            'deadline': deadline,
            'providers': providers,
            'analytics_data': prepare_analytics_data(
                self.request.user,
                self.request.site.siteconfiguration.segment_key,
                course.id
            ),
        })

        return context
Exemple #21
0
    def get(self, request):
        """ Calculate basket totals given a list of sku's

        Create a temporary basket add the sku's and apply an optional voucher code.
        Then calculate the total price less discounts. If a voucher code is not
        provided apply a voucher in the Enterprise entitlements available
        to the user.

        Arguments:
            sku (string): A list of sku(s) to calculate
            code (string): Optional voucher code to apply to the basket.
            username (string): Optional username of a user for which to caclulate the basket.

        Returns:
            JSON: {
                    'total_incl_tax_excl_discounts': basket.total_incl_tax_excl_discounts,
                    'total_incl_tax': basket.total_incl_tax,
                    'currency': basket.currency
                }
        """
        partner = get_partner_for_site(request)
        skus = request.GET.getlist('sku')
        if not skus:
            return HttpResponseBadRequest(_('No SKUs provided.'))

        code = request.GET.get('code', None)
        try:
            voucher = Voucher.objects.get(code=code) if code else None
        except Voucher.DoesNotExist:
            voucher = None

        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)))

        # If there is only one product apply an Enterprise entitlement voucher
        if not voucher and len(products) == 1:
            voucher = get_entitlement_voucher(request, products[0])

        username = request.GET.get('username', default='')
        user = request.user

        # True if this request was made by the marketing user, and does not include a username
        # query param, and thus is calculating the non-logged in (anonymous) price.
        # Note: We need to verify separately that all calls without a username query param
        # can be treated in this same way.
        is_marketing_anonymous_request = False

        # If a username is passed in, validate that the user has staff access or is the same user.
        if username:
            if user.is_staff or (user.username.lower() == username.lower()):
                try:
                    user = User.objects.get(username=username)
                except User.DoesNotExist:
                    logger.debug('Request username: [%s] does not exist',
                                 username)
            else:
                return HttpResponseForbidden('Unauthorized user credentials')
        elif user.username == self.MARKETING_USER:
            is_marketing_anonymous_request = True

        cache_key = None
        # Since we know we can't have any enrollments or entitlements, we can directly get
        # the cached price.
        if is_marketing_anonymous_request:
            cache_key = get_cache_key(site_comain=request.site,
                                      resource_name='calculate',
                                      skus=skus)
            if waffle.flag_is_active(
                    request, "use_cached_basket_calculate_for_marketing_user"):
                basket_calculate_results = cache.get(cache_key)
                if basket_calculate_results:
                    return Response(basket_calculate_results)

        response = self._calculate_basket(user, request, products, voucher,
                                          skus, code)

        if response and is_marketing_anonymous_request:
            cache.set(cache_key, response,
                      settings.ANONYMOUS_BASKET_CALCULATE_CACHE_TIMEOUT)

        return Response(response)
Exemple #22
0
 def get_serializer_context(self):
     context = super(SubscriptionViewSet, self).get_serializer_context()
     context['partner'] = get_partner_for_site(self.request)
     return context
 def test_partner_for_site(self):
     request = RequestFactory().get('/')
     partner = get_partner_for_site(request)
     self.assertEqual('dummy', partner.short_code)
Exemple #24
0
 def test_get_partner_for_site(self):
     """ Verify the function returns the Partner associated with the request's Site. """
     request = Mock()
     request.site = self.site
     self.assertEqual(get_partner_for_site(request), self.partner)
Exemple #25
0
    def get_context_data(self, **kwargs):
        context = super(Checkout, self).get_context_data(**kwargs)

        course = get_object_or_404(Course, id=kwargs.get('course_id'))

        deadline = self._check_credit_eligibility(self.request.user, kwargs.get('course_id'))
        if not deadline:
            return {
                'error': _(u'An error has occurred. We could not confirm that you are eligible for course credit. '
                           u'Try the transaction again.')
            }

        partner = get_partner_for_site(self.request)
        # Audit seats do not have a `certificate_type` attribute, so
        # we use getattr to avoid an exception.
        credit_seats = [
            seat for seat in course.seat_products
            if getattr(seat.attr, 'certificate_type', None) == self.CREDIT_MODE and
            seat.stockrecords.filter(partner=partner).exists()
        ]

        if not credit_seats:
            return {
                'error': _(u'No credit seat is available for this course.')
            }

        providers = self._get_providers_detail(credit_seats)
        if not providers:
            return {
                'error': _(u'An error has occurred. We could not confirm that the institution you selected offers this '
                           u'course credit. Try the transaction again.')
            }

        # Make button text for each processor which will be shown to user.
        processors_dict = OrderedDict()
        for path in settings.PAYMENT_PROCESSORS:
            processor_class = get_processor_class(path)
            if not processor_class.is_enabled():
                continue
            processor = processor_class.NAME.lower()
            if processor == 'cybersource':
                processors_dict[processor] = 'Checkout'
            elif processor == 'paypal':
                processors_dict[processor] = 'Checkout with PayPal'
            else:
                processors_dict[processor] = 'Checkout with {}'.format(processor)
        if len(processors_dict) == 0:
            context.update({
                'error': _(
                    u'All payment options are currently unavailable. Try the transaction again in a few minutes.'
                )
            })

        context.update({
            'course': course,
            'payment_processors': processors_dict,
            'deadline': deadline,
            'providers': providers,
            'analytics_data': json.dumps({
                'course': {
                    'courseId': course.id
                },
                'tracking': {
                    'segmentApplicationId': settings.SEGMENT_KEY
                },
                'user': {
                    'username': self.request.user.get_username(),
                    'name': self.request.user.get_full_name(),
                    'email': self.request.user.email
                }
            })
        })

        return context
Exemple #26
0
    def get(self, request):
        partner = get_partner_for_site(request)

        sku = request.GET.get('sku', None)
        code = request.GET.get('code', None)
        consent_failed = request.GET.get(CONSENT_FAILED_PARAM, False)

        if not sku:
            return HttpResponseBadRequest(_('No SKU provided.'))

        voucher = Voucher.objects.get(code=code) if code else None

        try:
            product = StockRecord.objects.get(partner=partner,
                                              partner_sku=sku).product
        except StockRecord.DoesNotExist:
            return HttpResponseBadRequest(
                _('SKU [{sku}] does not exist.').format(sku=sku))

        if not consent_failed and voucher is None:
            # Find and apply the enterprise entitlement on the learner basket. First, check two things:
            # 1. We don't already have an existing voucher parsed from a URL parameter
            # 2. The `consent_failed` URL parameter is falsey, or missing, meaning that we haven't already
            # attempted to apply an Enterprise voucher at least once, but the user rejected consent. Failing
            # to make that check would result in the user being repeatedly prompted to grant consent for the
            # same coupon they already declined consent on.
            voucher = get_entitlement_voucher(request, product)
            if voucher is not None:
                params = urlencode(
                    OrderedDict([
                        ('code', voucher.code),
                        ('sku', sku),
                        # This view does not handle getting data sharing consent. However, the coupon redemption
                        # view does. By adding the `failure_url` parameter, we're informing that view that, in the
                        # event required consent for a coupon can't be collected, the user ought to be directed
                        # back to this single-item basket view, with the `consent_failed` parameter applied so that
                        # we know not to try to apply the enterprise coupon again.
                        (
                            'failure_url',
                            request.build_absolute_uri(
                                '{path}?{params}'.format(
                                    path=reverse('basket:single-item'),
                                    params=urlencode(
                                        OrderedDict([
                                            (CONSENT_FAILED_PARAM, True),
                                            ('sku', sku),
                                        ])))),
                        ),
                    ]))
                return HttpResponseRedirect('{path}?{params}'.format(
                    path=reverse('coupons:redeem'), params=params))

        # If the product isn't available then there's no reason to continue with the basket addition
        purchase_info = request.strategy.fetch_for_product(product)
        if not purchase_info.availability.is_available_to_buy:
            msg = _('Product [{product}] not available to buy.').format(
                product=product.title)
            return HttpResponseBadRequest(msg)

        # At this point we're either adding an Enrollment Code product to the basket,
        # or the user is adding a Seat product for which they are not already enrolled
        try:
            prepare_basket(request, [product], voucher)
        except AlreadyPlacedOrderException:
            msg = _('You have already purchased {course} seat.').format(
                course=product.course.name)
            return render(request, 'edx/error.html', {'error': msg})
        return HttpResponseRedirect(reverse('basket:summary'), status=303)
Exemple #27
0
    def get_context_data(self, **kwargs):
        context = super(Checkout, self).get_context_data(**kwargs)

        course = get_object_or_404(Course, id=kwargs.get('course_id'))
        context['course'] = course

        deadline = self._check_credit_eligibility(self.request.user,
                                                  kwargs.get('course_id'))
        if not deadline:
            context.update({
                'error':
                _('An error has occurred. We could not confirm that you are eligible for course credit. '
                  'Try the transaction again.')
            })
            return context

        partner = get_partner_for_site(self.request)
        strategy = self.request.strategy
        # Audit seats do not have a `certificate_type` attribute, so
        # we use getattr to avoid an exception.
        credit_seats = []

        for seat in course.seat_products:
            if getattr(seat.attr, 'certificate_type',
                       None) != self.CREDIT_MODE:
                continue

            purchase_info = strategy.fetch_for_product(seat)
            if purchase_info.availability.is_available_to_buy and seat.stockrecords.filter(
                    partner=partner).exists():
                credit_seats.append(seat)

        if not credit_seats:
            msg = _(
                'Credit is not currently available for "{course_name}". If you are currently enrolled in the '
                'course, please try again after all grading is complete. If you need additional assistance, '
                'please contact the {site_name} Support Team.').format(
                    course_name=course.name, site_name=self.request.site.name)

            context.update({'error': msg})
            return context

        providers = self._get_providers_detail(credit_seats)
        if not providers:
            context.update({
                'error':
                _('An error has occurred. We could not confirm that the institution you selected offers this '
                  'course credit. Try the transaction again.')
            })
            return context

        # Make button text for each processor which will be shown to user.
        processors_dict = OrderedDict()
        for path in settings.PAYMENT_PROCESSORS:
            processor_class = get_processor_class(path)
            if not processor_class.is_enabled():
                continue
            processor = processor_class.NAME.lower()
            if processor == 'cybersource':
                processors_dict[processor] = 'Checkout'
            elif processor == 'paypal':
                processors_dict[processor] = 'Checkout with PayPal'
            else:
                processors_dict[processor] = 'Checkout with {}'.format(
                    processor)
        if len(processors_dict) == 0:
            context.update({
                'error':
                _('All payment options are currently unavailable. Try the transaction again in a few minutes.'
                  )
            })

        context.update({
            'course':
            course,
            'payment_processors':
            processors_dict,
            'deadline':
            deadline,
            'providers':
            providers,
            'analytics_data':
            prepare_analytics_data(
                self.request.user,
                self.request.site.siteconfiguration.segment_key, course.id),
        })

        return context
Exemple #28
0
    def get(self, request):
        partner = get_partner_for_site(request)

        sku = request.GET.get('sku', None)
        code = request.GET.get('code', None)

        if not sku:
            return HttpResponseBadRequest(_('No SKU provided.'))

        if code:
            voucher, __ = get_voucher_and_products_from_code(code=code)
        else:
            voucher = None

        try:
            product = StockRecord.objects.get(partner=partner, partner_sku=sku).product
        except StockRecord.DoesNotExist:
            return HttpResponseBadRequest(_('SKU [{sku}] does not exist.').format(sku=sku))

        # If the product isn't available then there's no reason to continue with the basket addition
        purchase_info = request.strategy.fetch_for_product(product)
        if not purchase_info.availability.is_available_to_buy:
            msg = _('Product [{product}] not available to buy.').format(product=product.title)
            return HttpResponseBadRequest(msg)

        # If the product is not an Enrollment Code, we check to see if the user is already
        # enrolled to prevent double-enrollment and/or accidental coupon usage
        if product.get_product_class().name != ENROLLMENT_CODE_PRODUCT_CLASS_NAME:

            course_key = product.attr.course_key

            # Submit a query to the LMS Enrollment API
            try:
                api = EdxRestApiClient(
                    get_lms_enrollment_base_api_url(),
                    oauth_access_token=request.user.access_token,
                    append_slash=False
                )
                logger.debug(
                    'Getting enrollment information for [%s] in [%s].',
                    request.user.username,
                    course_key
                )
                status = api.enrollment(','.join([request.user.username, course_key])).get()
            except (ConnectionError, SlumberBaseException, Timeout) as ex:
                logger.exception(
                    'Failed to retrieve enrollment details for [%s] in course [%s], Because of [%s]',
                    request.user.username,
                    course_key,
                    ex,
                )
                msg = _('An error occurred while retrieving enrollment details. Please try again.')
                return HttpResponseBadRequest(msg)

            # Enrollment API response received, now perform the actual enrollment check
            username = request.user.username
            seat_type = mode_for_seat(product)
            if status and status.get('mode') == seat_type and status.get('is_active'):
                logger.warning(
                    'User [%s] attempted to repurchase the [%s] seat of course [%s]',
                    username,
                    seat_type,
                    course_key
                )
                msg = _('You are already enrolled in {course}.').format(course=product.course.name)
                return HttpResponseBadRequest(msg)

        # At this point we're either adding an Enrollment Code product to the basket,
        # or the user is adding a Seat product for which they are not already enrolled
        prepare_basket(request, product, voucher)
        return HttpResponseRedirect(reverse('basket:summary'), status=303)
Exemple #29
0
 def get_serializer_context(self):
     context = super(AtomicPublicationView, self).get_serializer_context()
     context['access_token'] = self.request.user.access_token
     context['partner'] = get_partner_for_site(self.request)
     return context
Exemple #30
0
 def get_serializer_context(self):
     context = super(AtomicPublicationView, self).get_serializer_context()
     context[
         'access_token'] = self.request.user.access_token if self.request else None
     context['partner'] = get_partner_for_site(self.request)
     return context
Exemple #31
0
 def get_serializer_context(self):
     context = super(EdevateAtomicPublicationView, self).get_serializer_context()
     context['access_token'] = self.get_access_token()
     context['partner'] = get_partner_for_site(self.request)
     return context
Exemple #32
0
    def get(self, request):  # pylint: disable=too-many-statements
        """ Calculate basket totals given a list of sku's

        Create a temporary basket add the sku's and apply an optional voucher code.
        Then calculate the total price less discounts. If a voucher code is not
        provided apply a voucher in the Enterprise entitlements available
        to the user.

        Query Params:
            sku (string): A list of sku(s) to calculate
            code (string): Optional voucher code to apply to the basket.
            username (string): Optional username of a user for which to calculate the basket.

        Returns:
            JSON: {
                    'total_incl_tax_excl_discounts': basket.total_incl_tax_excl_discounts,
                    'total_incl_tax': basket.total_incl_tax,
                    'currency': basket.currency
                }
        """
        RequestCache.set(TEMPORARY_BASKET_CACHE_KEY,
                         True)  # TODO: LEARNER 5463

        partner = get_partner_for_site(request)
        skus = request.GET.getlist('sku')
        if not skus:
            return HttpResponseBadRequest(_('No SKUs provided.'))
        skus.sort()

        code = request.GET.get('code', None)
        try:
            voucher = Voucher.objects.get(code=code) if code else None
        except Voucher.DoesNotExist:
            voucher = None

        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)))

        # If there is only one product apply an Enterprise entitlement voucher
        if not voucher and len(products) == 1:
            voucher = get_entitlement_voucher(request, products[0])

        basket_owner = request.user

        requested_username = request.GET.get('username', default='')
        is_anonymous = request.GET.get('is_anonymous',
                                       'false').lower() == 'true'

        use_default_basket = is_anonymous

        # validate query parameters
        if requested_username and is_anonymous:
            return HttpResponseBadRequest(
                _('Provide username or is_anonymous query param, but not both')
            )
        elif not requested_username and not is_anonymous:
            logger.warning(
                "Request to Basket Calculate must supply either username or is_anonymous query"
                " param. Requesting user=%s. Future versions of this API will treat this "
                "WARNING as an ERROR and raise an exception.",
                basket_owner.username)
            requested_username = request.user.username

        # If a username is passed in, validate that the user has staff access or is the same user.
        if requested_username:
            if basket_owner.username.lower() == requested_username.lower():
                pass
            elif basket_owner.is_staff:
                try:
                    basket_owner = User.objects.get(
                        username=requested_username)
                except User.DoesNotExist:
                    # This case represents a user who is logged in to marketing, but
                    # doesn't yet have an account in ecommerce. These users have
                    # never purchased before.
                    use_default_basket = True
            else:
                return HttpResponseForbidden('Unauthorized user credentials')

        if basket_owner.username == self.MARKETING_USER and not use_default_basket:
            # For legacy requests that predate is_anonymous parameter, we will calculate
            # an anonymous basket if the calculated user is the marketing user.
            # TODO: LEARNER-5057: Remove this special case for the marketing user
            # once logs show no more requests with no parameters (see above).
            use_default_basket = True

        if use_default_basket:
            basket_owner = None

        cache_key = None
        if use_default_basket:
            # For an anonymous user we can directly get the cached price, because
            # there can't be any enrollments or entitlements.
            cache_key = get_cache_key(site_comain=request.site,
                                      resource_name='calculate',
                                      skus=skus)
            cached_response = TieredCache.get_cached_response(cache_key)
            if cached_response.is_hit:
                return Response(cached_response.value)

        if waffle.flag_is_active(
                request,
                "disable_calculate_temporary_basket_atomic_transaction"):
            response = self._calculate_temporary_basket(
                basket_owner, request, products, voucher, skus, code)
        else:
            response = self._calculate_temporary_basket_atomic(
                basket_owner, request, products, voucher, skus, code)

        if response and use_default_basket:
            TieredCache.set_all_tiers(
                cache_key, response,
                settings.ANONYMOUS_BASKET_CALCULATE_CACHE_TIMEOUT)

        return Response(response)
Exemple #33
0
    def get(self, request):
        """ Calculate basket totals given a list of sku's

        Create a temporary basket add the sku's and apply an optional voucher code.
        Then calculate the total price less discounts. If a voucher code is not
        provided apply a voucher in the Enterprise entitlements available
        to the user.

        Query Params:
            sku (string): A list of sku(s) to calculate
            code (string): Optional voucher code to apply to the basket.
            username (string): Optional username of a user for which to calculate the basket.

        Returns:
            JSON: {
                    'total_incl_tax_excl_discounts': basket.total_incl_tax_excl_discounts,
                    'total_incl_tax': basket.total_incl_tax,
                    'currency': basket.currency
                }

         Side effects:
            If the basket owner does not have an LMS user id, tries to find it. If found, adds the id to the user and
            saves the user. If the id cannot be found, writes custom metrics to record this fact.
       """
        DEFAULT_REQUEST_CACHE.set(TEMPORARY_BASKET_CACHE_KEY, True)

        partner = get_partner_for_site(request)
        skus = request.GET.getlist('sku')
        if not skus:
            return HttpResponseBadRequest(_('No SKUs provided.'))
        skus.sort()

        code = request.GET.get('code', None)
        try:
            voucher = Voucher.objects.get(code=code) if code else None
        except Voucher.DoesNotExist:
            voucher = None

        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)))

        basket_owner = request.user

        requested_username = request.GET.get('username', default='')
        is_anonymous = request.GET.get('is_anonymous',
                                       'false').lower() == 'true'

        use_default_basket = is_anonymous

        # validate query parameters
        if requested_username and is_anonymous:
            return HttpResponseBadRequest(
                _('Provide username or is_anonymous query param, but not both')
            )
        if not requested_username and not is_anonymous:
            logger.warning(
                "Request to Basket Calculate must supply either username or is_anonymous query"
                " param. Requesting user=%s. Future versions of this API will treat this "
                "WARNING as an ERROR and raise an exception.",
                basket_owner.username)
            requested_username = request.user.username

        # If a username is passed in, validate that the user has staff access or is the same user.
        if requested_username:
            if basket_owner.username.lower() == requested_username.lower():
                pass
            elif basket_owner.is_staff:
                try:
                    basket_owner = User.objects.get(
                        username=requested_username)
                except User.DoesNotExist:
                    # This case represents a user who is logged in to marketing, but
                    # doesn't yet have an account in ecommerce. These users have
                    # never purchased before.
                    use_default_basket = True
            else:
                return HttpResponseForbidden('Unauthorized user credentials')

        if basket_owner.username == self.MARKETING_USER and not use_default_basket:
            # For legacy requests that predate is_anonymous parameter, we will calculate
            # an anonymous basket if the calculated user is the marketing user.
            # TODO: LEARNER-5057: Remove this special case for the marketing user
            # once logs show no more requests with no parameters (see above).
            use_default_basket = True

        if use_default_basket:
            basket_owner = None

        # If we have a basket owner, ensure they have an LMS user id
        try:
            if basket_owner:
                called_from = u'calculation of basket total'
                basket_owner.add_lms_user_id(
                    'ecommerce_missing_lms_user_id_calculate_basket_total',
                    called_from)
        except MissingLmsUserIdException:
            return self._report_bad_request(
                api_exceptions.LMS_USER_ID_NOT_FOUND_DEVELOPER_MESSAGE.format(
                    user_id=basket_owner.id),
                api_exceptions.LMS_USER_ID_NOT_FOUND_USER_MESSAGE)

        cache_key = None
        if use_default_basket:
            # For an anonymous user we can directly get the cached price, because
            # there can't be any enrollments or entitlements.
            cache_key = get_cache_key(site_comain=request.site,
                                      resource_name='calculate',
                                      skus=skus)
            cached_response = TieredCache.get_cached_response(cache_key)
            logger.info(
                'bundle debugging 1: Cache key [%s] site [%s] skus [%s] response [%s]',
                str(cache_key), str(request.site), str(skus),
                str(cached_response))
            if cached_response.is_found:
                return Response(cached_response.value)

        response = self._calculate_temporary_basket_atomic(
            basket_owner, request, products, voucher, skus, code)
        logger.info(
            'bundle debugging 2: Cache key [%s] response [%s] skus [%s] timeout [%s]',
            str(cache_key), str(response), str(skus),
            str(settings.ANONYMOUS_BASKET_CALCULATE_CACHE_TIMEOUT))
        if response and use_default_basket:
            logger.info(
                'bundle debugging 3: setting cache: Cache key [%s] response [%s] skus [%s]',
                str(cache_key), str(response), str(skus))
            TieredCache.set_all_tiers(
                cache_key, response,
                settings.ANONYMOUS_BASKET_CALCULATE_CACHE_TIMEOUT)

        return Response(response)
Exemple #34
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['sku'] = request.GET.getlist('sku')
            query_dict.pop('username')
            redirect_url = '{path}?{query_string}'.format(
                path=request.path,
                query_string=urlencode(query_dict, doseq=True))
            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)))

        # TODO Vip purchase and program purchase conflict
        # 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)

        logger.info('Starting payment flow for user[%s] for products[%s].',
                    request.user.username, skus)

        voucher = Voucher.objects.get(code=code) if code else None

        if voucher is None:
            # If there is an Enterprise entitlement available for this basket,
            # we redirect to the CouponRedeemView to apply the discount to the
            # basket and handle the data sharing consent requirement.
            code_redemption_redirect = get_enterprise_code_redemption_redirect(
                request, products, skus, 'basket:basket-add')
            if code_redemption_redirect:
                return code_redemption_redirect

        # check availability of products
        unavailable_product_ids = []
        for product in products:
            purchase_info = request.strategy.fetch_for_product(product)
            if not purchase_info.availability.is_available_to_buy:
                logger.warning('Product [%s] is not available to buy.',
                               product.title)
                unavailable_product_ids.append(product.id)

        available_products = products.exclude(id__in=unavailable_product_ids)
        if not available_products:
            msg = _('No product is available to buy.')
            return HttpResponseBadRequest(msg)

        # Associate the user's email opt in preferences with the basket in
        # order to opt them in later as part of fulfillment
        BasketAttribute.objects.update_or_create(
            basket=request.basket,
            attribute_type=BasketAttributeType.objects.get(
                name=EMAIL_OPT_IN_ATTRIBUTE),
            defaults={'value_text': request.GET.get('email_opt_in') == 'true'},
        )

        try:
            prepare_basket(request, available_products, voucher)
        except AlreadyPlacedOrderException:
            return render(
                request, 'edx/error.html', {
                    'error': _('You have already purchased these products'),
                    'lms_contact_url': get_lms_url('/contact')
                })
        url = add_utm_params_to_url(reverse('basket:summary'),
                                    self.request.GET.items())
        return HttpResponseRedirect(url, status=303)