def get_first_purchase_offer_banner_fragment(user, course): """ Return an HTML Fragment with First Purcahse Discount message, which has the discount_expiration_date, price, discount percentage and a link to upgrade. """ if user and course: discount_expiration_date = get_discount_expiration_date(user, course) if (discount_expiration_date and can_receive_discount(user=user, course=course, discount_expiration_date=discount_expiration_date)): # Translator: xgettext:no-python-format offer_message = _(u'{banner_open} Upgrade by {discount_expiration_date} and save {percentage}% ' u'[{strikeout_price}]{span_close}{br}Discount will be automatically applied at checkout. ' u'{a_open}Upgrade Now{a_close}{div_close}') return Fragment(HTML(offer_message).format( a_open=HTML(u'<a href="{upgrade_link}">').format( upgrade_link=verified_upgrade_deadline_link(user=user, course=course) ), a_close=HTML('</a>'), br=HTML('<br>'), banner_open=HTML( '<div class="first-purchase-offer-banner"><span class="first-purchase-offer-banner-bold">' ), discount_expiration_date=discount_expiration_date.strftime(u'%B %d'), percentage=discount_percentage(course), span_close=HTML('</span>'), div_close=HTML('</div>'), strikeout_price=HTML(format_strikeout_price(user, course, check_for_discount=False)[0]) )) return None
def _get_discount_prices(user, course, assume_discount=False): """ Return a tuple of (original, discounted, percentage) If assume_discount is True, we do not check if a discount applies and just go ahead with discount math anyway. Each returned price is a string with appropriate currency formatting added already. discounted and percentage will be returned as None if no discount is applicable. """ base_price = get_course_prices(course, verified_only=True)[0] can_discount = assume_discount or can_receive_discount(user, course) if can_discount: percentage = discount_percentage(course) discounted_price = base_price * ((100.0 - percentage) / 100) if discounted_price: # leave 0 prices alone, as format_course_price below will adjust to 'Free' if discounted_price == int(discounted_price): discounted_price = f'{discounted_price:0.0f}' else: discounted_price = f'{discounted_price:0.2f}' return format_course_price(base_price), format_course_price( discounted_price), percentage else: return format_course_price(base_price), None, None
def generate_offer_html(user, course): """ Create the actual HTML object with the offer text in it. Returns a openedx.core.djangolib.markup.HTML object, or None if the user should not be shown an offer message. """ if user and not user.is_anonymous and course: now = datetime.now(tz=pytz.UTC).strftime(u"%Y-%m-%d %H:%M:%S%z") saw_banner = ExperimentData.objects.filter( user=user, experiment_id=REV1008_EXPERIMENT_ID, key=str(course)) if not saw_banner: ExperimentData.objects.create(user=user, experiment_id=REV1008_EXPERIMENT_ID, key=str(course), value=now) discount_expiration_date = get_discount_expiration_date(user, course) if (discount_expiration_date and can_receive_discount( user=user, course=course, discount_expiration_date=discount_expiration_date)): # Translator: xgettext:no-python-format offer_message = _( u'{banner_open} Upgrade by {discount_expiration_date} and save {percentage}% ' u'[{strikeout_price}]{span_close}{br}Use code {b_open}{code}{b_close} at checkout! ' u'{a_open}Upgrade Now{a_close}{div_close}') message_html = HTML(offer_message).format( a_open=HTML(u'<a id="welcome" href="{upgrade_link}">').format( upgrade_link=verified_upgrade_deadline_link( user=user, course=course)), a_close=HTML('</a>'), b_open=HTML('<b>'), code=Text('BIENVENIDOAEDX') if get_language() == 'es-419' else Text('EDXWELCOME'), b_close=HTML('</b>'), br=HTML('<br>'), banner_open=HTML( '<div class="first-purchase-offer-banner" role="note">' '<span class="first-purchase-offer-banner-bold"><b>'), discount_expiration_date=discount_expiration_date.strftime( u'%B %d'), percentage=discount_percentage(course), span_close=HTML('</b></span>'), div_close=HTML('</div>'), strikeout_price=HTML( format_strikeout_price(user, course, check_for_discount=False)[0])) return message_html return None
def format_strikeout_price(user, course, base_price=None, check_for_discount=True): """ Return a formatted price, including a struck-out original price if a discount applies, and also whether a discount was applied, as the tuple (formatted_price, has_discount). """ if base_price is None: base_price = get_course_prices(course, verified_only=True)[0] original_price = format_course_price(base_price) if not check_for_discount or can_receive_discount(user, course): discount_price = base_price * ( (100.0 - discount_percentage(course)) / 100) if discount_price == int(discount_price): discount_price = format_course_price( "{:0.0f}".format(discount_price)) else: discount_price = format_course_price( "{:0.2f}".format(discount_price)) # Separate out this string because it has a lot of syntax but no actual information for # translators to translate formatted_discount_price = HTML( u"{s_dp}{discount_price}{e_p} {s_st}{s_op}{original_price}{e_p}{e_st}" ).format( original_price=original_price, discount_price=discount_price, s_op=HTML("<span class='price original'>"), s_dp=HTML("<span class='price discount'>"), s_st=HTML("<del aria-hidden='true'>"), e_p=HTML("</span>"), e_st=HTML("</del>"), ) return (HTML( _(u"{s_sr}Original price: {s_op}{original_price}{e_p}, discount price: {e_sr}{formatted_discount_price}" )).format( original_price=original_price, formatted_discount_price=formatted_discount_price, s_sr=HTML("<span class='sr-only'>"), s_op=HTML("<span class='price original'>"), e_p=HTML("</span>"), e_sr=HTML("</span>"), ), True) else: return (HTML(u"<span class='price'>{}</span>").format(original_price), False)
def get_first_purchase_offer_banner_fragment(user, course): if user and course and can_receive_discount(user=user, course=course): # Translator: xgettext:no-python-format offer_message = _( u'{banner_open}{percentage}% off your first upgrade.{span_close}' u' Discount automatically applied.{div_close}') return Fragment( HTML(offer_message).format(banner_open=HTML( '<div class="first-purchase-offer-banner"><span class="first-purchase-offer-banner-bold">' ), percentage=discount_percentage(), span_close=HTML('</span>'), div_close=HTML('</div>'))) return None
def get_first_purchase_offer_banner_fragment(user, course): """ Return an HTML Fragment with First Purcahse Discount message, which has the discount_expiration_date, price, discount percentage and a link to upgrade. """ if user and not user.is_anonymous and course: now = datetime.now(tz=pytz.UTC) stop_bucketing_into_discount_experiment = datetime( 2019, 11, 22, 0, 0, 0, 0, pytz.UTC) saw_banner = ExperimentData.objects.filter( user=user, experiment_id=REV1008_EXPERIMENT_ID, key=str(course)) if not saw_banner and not now > stop_bucketing_into_discount_experiment: ExperimentData.objects.create( user=user, experiment_id=REV1008_EXPERIMENT_ID, key=str(course), value=now.strftime(u"%Y-%m-%d %H:%M:%S%z")) discount_expiration_date = get_discount_expiration_date(user, course) if (discount_expiration_date and can_receive_discount( user=user, course=course, discount_expiration_date=discount_expiration_date)): # Translator: xgettext:no-python-format offer_message = _( u'{banner_open} Upgrade by {discount_expiration_date} and save {percentage}% ' u'[{strikeout_price}]{span_close}{br}Discount will be automatically applied at checkout. ' u'{a_open}Upgrade Now{a_close}{div_close}') return Fragment( HTML(offer_message).format( a_open=HTML(u'<a href="{upgrade_link}">').format( upgrade_link=verified_upgrade_deadline_link( user=user, course=course)), a_close=HTML('</a>'), br=HTML('<br>'), banner_open=HTML( '<div class="first-purchase-offer-banner" role="note">' '<span class="first-purchase-offer-banner-bold">'), discount_expiration_date=discount_expiration_date.strftime( u'%B %d'), percentage=discount_percentage(course), span_close=HTML('</span>'), div_close=HTML('</div>'), strikeout_price=HTML( format_strikeout_price(user, course, check_for_discount=False)[0]))) return None
def get_first_purchase_offer_banner_fragment(user, course): if (FIRST_PURCHASE_OFFER_BANNER_DISPLAY.is_enabled() and user and course and can_receive_discount(user=user, course_key_string=unicode(course.id))): # Translator: xgettext:no-python-format offer_message = _(u'{banner_open}{percentage}% off your first upgrade.{span_close}' u' Discount automatically applied.{div_close}') return Fragment(HTML(offer_message).format( banner_open=HTML( '<div class="first-purchase-offer-banner"><span class="first-purchase-offer-banner-bold">' ), percentage=discount_percentage(), span_close=HTML('</span>'), div_close=HTML('</div>') ))
def get_first_purchase_offer_banner_fragment(user, course): if (FIRST_PURCHASE_OFFER_BANNER_DISPLAY.is_enabled() and user and course and can_receive_discount(user=user, course=course)): # Translator: xgettext:no-python-format offer_message = _(u'{banner_open}{percentage}% off your first upgrade.{span_close}' u' Discount automatically applied.{div_close}') return Fragment(HTML(offer_message).format( banner_open=HTML( '<div class="first-purchase-offer-banner"><span class="first-purchase-offer-banner-bold">' ), percentage=discount_percentage(), span_close=HTML('</span>'), div_close=HTML('</div>') ))
def get(self, request, course_id, error=None): """Displays the course mode choice page. Args: request (`Request`): The Django Request object. course_id (unicode): The slash-separated course key. Keyword Args: error (unicode): If provided, display this error message on the page. Returns: Response """ course_key = CourseKey.from_string(course_id) # Check whether the user has access to this course # based on country access rules. embargo_redirect = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path ) if embargo_redirect: return redirect(embargo_redirect) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) increment('track-selection.{}.{}'.format(enrollment_mode, 'active' if is_active else 'inactive')) increment('track-selection.views') if enrollment_mode is None: LOG.info('Rendering track selection for unenrolled user, referred by %s', request.META.get('HTTP_REFERER')) modes = CourseMode.modes_for_course_dict(course_key) ecommerce_service = EcommerceService() # We assume that, if 'professional' is one of the modes, it should be the *only* mode. # If there are both modes, default to non-id-professional. has_enrolled_professional = (CourseMode.is_professional_slug(enrollment_mode) and is_active) if CourseMode.has_professional_mode(modes) and not has_enrolled_professional: purchase_workflow = request.GET.get("purchase_workflow", "single") verify_url = reverse('verify_student_start_flow', kwargs={'course_id': six.text_type(course_key)}) redirect_url = "{url}?purchase_workflow={workflow}".format(url=verify_url, workflow=purchase_workflow) if ecommerce_service.is_enabled(request.user): professional_mode = modes.get(CourseMode.NO_ID_PROFESSIONAL_MODE) or modes.get(CourseMode.PROFESSIONAL) if purchase_workflow == "single" and professional_mode.sku: redirect_url = ecommerce_service.get_checkout_page_url(professional_mode.sku) if purchase_workflow == "bulk" and professional_mode.bulk_sku: redirect_url = ecommerce_service.get_checkout_page_url(professional_mode.bulk_sku) return redirect(redirect_url) course = modulestore().get_course(course_key) # If there isn't a verified mode available, then there's nothing # to do on this page. Send the user to the dashboard. if not CourseMode.has_verified_mode(modes): return redirect(reverse('dashboard')) # If a user has already paid, redirect them to the dashboard. if is_active and (enrollment_mode in CourseMode.VERIFIED_MODES + [CourseMode.NO_ID_PROFESSIONAL_MODE]): # If the course has started redirect to course home instead if course.has_started(): return redirect(reverse('openedx.course_experience.course_home', kwargs={'course_id': course_key})) return redirect(reverse('dashboard')) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(six.text_type(course_key), None) if CourseEnrollment.is_enrollment_closed(request.user, course): locale = to_locale(get_language()) enrollment_end_date = format_datetime(course.enrollment_end, 'short', locale=locale) params = six.moves.urllib.parse.urlencode({'course_closed': enrollment_end_date}) return redirect('{0}?{1}'.format(reverse('dashboard'), params)) # When a credit mode is available, students will be given the option # to upgrade from a verified mode to a credit mode at the end of the course. # This allows students who have completed photo verification to be eligible # for university credit. # Since credit isn't one of the selectable options on the track selection page, # we need to check *all* available course modes in order to determine whether # a credit mode is available. If so, then we show slightly different messaging # for the verified track. has_credit_upsell = any( CourseMode.is_credit_mode(mode) for mode in CourseMode.modes_for_course(course_key, only_selectable=False) ) course_id = text_type(course_key) context = { "course_modes_choose_url": reverse( "course_modes_choose", kwargs={'course_id': course_id} ), "modes": modes, "has_credit_upsell": has_credit_upsell, "course_name": course.display_name_with_default, "course_org": course.display_org_with_default, "course_num": course.display_number_with_default, "chosen_price": chosen_price, "error": error, "responsive": True, "nav_hidden": True, "content_gating_enabled": ContentTypeGatingConfig.enabled_for_enrollment( user=request.user, course_key=course_key ), "course_duration_limit_enabled": CourseDurationLimitConfig.enabled_for_enrollment( user=request.user, course_key=course_key ), } context.update( get_experiment_user_metadata_context( course, request.user, ) ) title_content = _("Congratulations! You are now enrolled in {course_name}").format( course_name=course.display_name_with_default ) context["title_content"] = title_content if "verified" in modes: verified_mode = modes["verified"] context["suggested_prices"] = [ decimal.Decimal(x.strip()) for x in verified_mode.suggested_prices.split(",") if x.strip() ] price_before_discount = verified_mode.min_price context["currency"] = verified_mode.currency.upper() context["min_price"] = price_before_discount context["verified_name"] = verified_mode.name context["verified_description"] = verified_mode.description offer_banner_fragment = get_first_purchase_offer_banner_fragment( request.user, course ) if offer_banner_fragment: context['offer_banner_fragment'] = offer_banner_fragment discounted_price = "{:0.2f}".format(price_before_discount * ((100.0 - discount_percentage()) / 100)) context["min_price"] = discounted_price context["price_before_discount"] = price_before_discount if verified_mode.sku: context["use_ecommerce_payment_flow"] = ecommerce_service.is_enabled(request.user) context["ecommerce_payment_page"] = ecommerce_service.payment_page_url() context["sku"] = verified_mode.sku context["bulk_sku"] = verified_mode.bulk_sku context['currency_data'] = [] if waffle.switch_is_active('local_currency'): if 'edx-price-l10n' not in request.COOKIES: currency_data = get_currency_data() try: context['currency_data'] = json.dumps(currency_data) except TypeError: pass return render_to_response("course_modes/choose.html", context)