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