def _set_single_enrollment_code_warning_if_needed(self, product, course): assert product.is_enrollment_code_product if self.request.basket.num_items == 1: course_key = CourseKey.from_string(product.attr.course_key) if course and course.get('marketing_url', None): course_about_url = course['marketing_url'] else: course_about_url = get_lms_course_about_url( course_key=course_key) message_code = 'single-enrollment-code-warning' messages.info( self.request, _('{strong_start}Purchasing just for yourself?{strong_end}{paragraph_start}If you are ' 'purchasing a single code for someone else, please continue with checkout. However, if you are the ' 'learner {link_start}go back{link_end} to enroll directly.{paragraph_end}' ).format(strong_start='<strong>', strong_end='</strong>', paragraph_start='<p>', paragraph_end='</p>', link_start='<a href="{course_about}">'.format( course_about=course_about_url), link_end='</a>'), extra_tags='safe ' + message_code) message_utils.add_message_data(message_code, 'course_about_url', course_about_url)
def test_enterprise_offer_course_redirect(self): """ Verify redirect to the courseware info page. """ self.prepare_basket(10) self.prepare_enterprise_offer() self.assertEqual(Order.objects.count(), 0) response = self.client.get(self.path) self.assertEqual(Order.objects.count(), 1) expected_url = get_lms_course_about_url(self.course_run.id) self.assertRedirects(response, expected_url, fetch_redirect_response=False)
def test_enterprise_customer_successful_free_redemption(self): """ Verify the view redirects to LMS course page for enterprise customers on a successful free redemption. """ code, _ = self.prepare_enterprise_data( benefit_value=100, consent_enabled=False, catalog=self.catalog, enterprise_customer_catalog=ENTERPRISE_CUSTOMER_CATALOG, ) self.mock_assignable_enterprise_condition_calls( ENTERPRISE_CUSTOMER_CATALOG) self.mock_enterprise_learner_api_for_learner_with_no_enterprise() self.mock_enterprise_learner_post_api() response = self.client.get(self.redeem_url_with_params(code=code), follow=False) self.assertRedirects(response, get_lms_course_about_url(self.course.id), fetch_redirect_response=False)
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 test_enterprise_customer_successful_redemption(self): """ Verify the view redirects to LMS courseware page when valid consent is provided. """ code, _ = self.prepare_enterprise_data( enterprise_customer_catalog=ENTERPRISE_CUSTOMER_CATALOG) self.mock_assignable_enterprise_condition_calls( ENTERPRISE_CUSTOMER_CATALOG) self.mock_enterprise_learner_api_for_learner_with_no_enterprise() self.mock_enterprise_learner_post_api() consent_token = get_enterprise_customer_data_sharing_consent_token( self.request.user.access_token, self.course.id, ENTERPRISE_CUSTOMER) response = self.redeem_coupon(code=code, consent_token=consent_token) self.assertRedirects(response, get_lms_course_about_url(self.course.id), fetch_redirect_response=False) last_request = responses.calls[-1].request self.assertEqual(last_request.path_url, '/api/enrollment/v1/enrollment') self.assertEqual(last_request.method, 'POST')
def get_redirect_url(self, *args, **kwargs): request = self.request site = request.site basket = Basket.get_basket(request.user, site) if not basket.is_empty: # Need to re-apply the voucher to the basket. Applicator().apply(basket, request.user, request) if basket.total_incl_tax != Decimal(0): raise BasketNotFreeError( 'Basket [{}] is not free. User affected [{}]'.format( basket.id, basket.owner.id)) order = self.place_free_order(basket) if has_enterprise_offer(basket): # Skip the receipt page and redirect to the LMS # if the order is free due to an Enterprise-related offer. program_uuid = get_program_uuid(order) if program_uuid: url = get_lms_program_dashboard_url(program_uuid) else: course_run_id = order.lines.all()[:1].get( ).product.course.id url = get_lms_course_about_url(course_run_id) else: receipt_path = get_receipt_page_url( order_number=order.number, site_configuration=order.site.siteconfiguration, disable_back_button=True, ) url = site.siteconfiguration.build_lms_url(receipt_path) else: # If a user's basket is empty redirect the user to the basket summary # page which displays the appropriate message for empty baskets. url = reverse('basket:summary') return url
def _get_course_data(self, product): """ Return course data. Args: product (Product): A product that has course_key as attribute (seat or bulk enrollment coupon) Returns: Dictionary containing course name, course key, course image URL and description. """ if product.is_seat_product: course_key = CourseKey.from_string(product.attr.course_key) else: course_key = None course_name = None image_url = None short_description = None course_start = None course_end = None course = None try: course = get_course_info_from_catalog(self.request.site, product) try: image_url = course['image']['src'] except (KeyError, TypeError): image_url = '' short_description = course.get('short_description', '') course_name = course.get('title', '') # The course start/end dates are not currently used # in the default basket templates, but we are adding # the dates to the template context so that theme # template overrides can make use of them. course_start = self._deserialize_date(course.get('start')) course_end = self._deserialize_date(course.get('end')) except (ConnectionError, SlumberBaseException, Timeout): logger.exception( 'Failed to retrieve data from Discovery Service for course [%s].', course_key) if self.request.basket.num_items == 1 and product.is_enrollment_code_product: course_key = CourseKey.from_string(product.attr.course_key) if course and course.get('marketing_url', None): course_about_url = course['marketing_url'] else: course_about_url = get_lms_course_about_url( course_key=course_key) messages.info( self.request, _('{strong_start}Purchasing just for yourself?{strong_end}{paragraph_start}If you are ' 'purchasing a single code for someone else, please continue with checkout. However, if you are the ' 'learner {link_start}go back{link_end} to enroll directly.{paragraph_end}' ).format(strong_start='<strong>', strong_end='</strong>', paragraph_start='<p>', paragraph_end='</p>', link_start='<a href="{course_about}">'.format( course_about=course_about_url), link_end='</a>'), extra_tags='safe') return { 'product_title': course_name, 'course_key': course_key, 'image_url': image_url, 'product_description': short_description, 'course_start': course_start, 'course_end': course_end, }
def get(self, request): # pylint: disable=too-many-statements """ Looks up the passed code and adds the matching product to a basket, then applies the voucher and if the basket total is FREE places the order and enrolls the user in the course. """ template_name = 'coupons/_offer_error.html' code = request.GET.get('code') sku = request.GET.get('sku') failure_url = request.GET.get('failure_url') site_configuration = request.site.siteconfiguration if not code: return render(request, template_name, {'error': _('Code not provided.')}) if not sku: return render(request, template_name, {'error': _('SKU not provided.')}) try: voucher = Voucher.objects.get(code=code) except Voucher.DoesNotExist: msg = 'No voucher found with code {code}'.format(code=code) return render(request, template_name, {'error': _(msg)}) try: product = StockRecord.objects.get(partner_sku=sku).product except StockRecord.DoesNotExist: return render(request, template_name, {'error': _('The product does not exist.')}) valid_voucher, msg, hide_error_message = voucher_is_valid( voucher, [product], request) if not valid_voucher: logger.warning( '[Code Redemption Failure] The voucher is not valid for this product. ' 'User: %s, Product: %s, Code: %s, Message: %s', request.user.username, product.id, voucher.code, msg) return render(request, template_name, { 'error': msg, 'hide_error_message': hide_error_message }) offer = voucher.best_offer if not offer.is_email_valid(request.user.email): logger.warning( '[Code Redemption Failure] Unable to apply offer because the user\'s email ' 'does not meet the domain requirements. ' 'User: %s, Offer: %s, Code: %s', request.user.username, offer.id, voucher.code) return render( request, template_name, {'error': _('You are not eligible to use this coupon.')}) email_confirmation_response = get_redirect_to_email_confirmation_if_required( request, offer, product) if email_confirmation_response: return email_confirmation_response try: enterprise_customer = get_enterprise_customer_from_voucher( request.site, voucher) except EnterpriseDoesNotExist as e: # If an EnterpriseException is caught while pulling the EnterpriseCustomer, that means there's no # corresponding EnterpriseCustomer in the Enterprise service (which should never happen). logger.exception(str(e)) return render( request, template_name, { 'error': _('Couldn\'t find a matching Enterprise Customer for this coupon.' ) }) if enterprise_customer and product.is_course_entitlement_product: return render( request, template_name, { 'error': _('This coupon is not valid for purchasing a program. Try using this on an individual ' 'course in the program. If you need assistance, contact edX support.' ) }) if enterprise_customer is not None and enterprise_customer_user_needs_consent( request.site, enterprise_customer['id'], product.course.id, request.user.username, ): consent_token = get_enterprise_customer_data_sharing_consent_token( request.user.access_token, product.course.id, enterprise_customer['id']) received_consent_token = request.GET.get('consent_token') if received_consent_token: # If the consent token is set, then the user is returning from the consent view. Render out an error # if the computed token doesn't match the one received from the redirect URL. if received_consent_token != consent_token: logger.warning( '[Code Redemption Failure] Unable to complete code redemption because of ' 'invalid consent. User: %s, Offer: %s, Code: %s', request.user.username, offer.id, voucher.code) return render(request, template_name, { 'error': _('Invalid data sharing consent token provided.') }) else: # The user hasn't been redirected to the interstitial consent view to collect consent, so # redirect them now. redirect_url = get_enterprise_course_consent_url( request.site, code, sku, consent_token, product.course.id, enterprise_customer['id'], failure_url=failure_url, consent_url_param_dict=parse_consent_params(request), ) return HttpResponseRedirect(redirect_url) try: basket = prepare_basket(request, [product], voucher) except AlreadyPlacedOrderException: msg = _('You have already purchased {course} seat.').format( course=product.course.name) return render(request, template_name, {'error': msg}) if basket.total_excl_tax == 0: try: order = self.place_free_order(basket) if enterprise_customer: return HttpResponseRedirect( get_lms_course_about_url(product.course.id)) return HttpResponseRedirect( get_receipt_page_url( site_configuration, order.number, disable_back_button=True, ), ) except: # pylint: disable=bare-except logger.exception( 'Failed to create a free order for basket [%d]', basket.id) return absolute_redirect(self.request, 'checkout:error') if enterprise_customer: if is_voucher_applied(basket, voucher): message = _( 'A discount has been applied, courtesy of {enterprise_customer_name}.' ).format( enterprise_customer_name=enterprise_customer.get('name')) messages.info(self.request, message) else: # Display a generic message to the user if a condition-specific # message has not already been added by an unsatified Condition class. if not messages.get_messages(self.request): messages.warning( self.request, _('This coupon code is not valid for this course. Try a different course.' )) self.request.basket.vouchers.remove(voucher) # The coupon_redeem_redirect query param is used to communicate to the Payment MFE that it may redirect # and should not display the payment form before making that determination. # TODO: It would be cleaner if the user could be redirected to their final destination up front. redirect_url = get_payment_microfrontend_or_basket_url( self.request) + "?coupon_redeem_redirect=1" return HttpResponseRedirect(redirect_url)