Merge pull request #20674 from edx/REVEM-354
add discount banner to track selection and discount price to upgrade button
This commit is contained in:
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -38,3 +38,4 @@ lms/djangoapps/experiments/ @edx/rev-team
|
||||
lms/djangoapps/learner_dashboard/ @edx/platform-discovery
|
||||
openedx/features/content_type_gating/ @edx/rev-team
|
||||
openedx/features/course_duration_limits/ @edx/rev-team
|
||||
openedx/features/discounts/ @edx/rev-team
|
||||
|
||||
@@ -23,6 +23,8 @@ from lms.djangoapps.commerce.tests import test_utils as ecomm_test_utils
|
||||
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
|
||||
from openedx.core.djangoapps.embargo.test_utils import restrict_course
|
||||
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
|
||||
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
|
||||
from openedx.features.course_experience import FIRST_PURCHASE_OFFER_BANNER_DISPLAY
|
||||
from student.models import CourseEnrollment
|
||||
from student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
from util.testing import UrlResetMixin
|
||||
@@ -397,6 +399,36 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
|
||||
self.assertEquals(course_mode, expected_mode)
|
||||
|
||||
@patch('openedx.features.course_experience.utils.can_receive_discount')
|
||||
@patch('openedx.features.course_experience.utils.discount_percentage')
|
||||
@override_waffle_flag(FIRST_PURCHASE_OFFER_BANNER_DISPLAY, active=True)
|
||||
def test_discount_on_track_selection(self, discount_percentage_mock, can_receive_discount_mock):
|
||||
can_receive_discount_mock.return_value = True
|
||||
discount_percentage_mock.return_value = 15
|
||||
parameters = {
|
||||
'mode_slug': 'verified',
|
||||
'mode_display_name': 'Verified Certificate',
|
||||
'min_price': 10
|
||||
}
|
||||
|
||||
url = reverse('create_mode', args=[six.text_type(self.course.id)])
|
||||
response = self.client.get(url, parameters)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('course_modes_choose', args=[six.text_type(self.course.id)]),
|
||||
follow=False,
|
||||
)
|
||||
|
||||
bannerText = u'''<div class="first-purchase-offer-banner"><span class="first-purchase-offer-banner-bold">
|
||||
15% off your first upgrade.</span> Discount automatically applied.</div>'''
|
||||
button = u'''<button type="submit" name="verified_mode">
|
||||
<span>Pursue a Verified Certificate</span>
|
||||
(<span class="upgrade-price-string">$8.50 USD</span>
|
||||
<del> <span class="upgrade-price-string">$10 USD</span></del>)
|
||||
</button>'''
|
||||
self.assertContains(response, bannerText, html=True)
|
||||
self.assertContains(response, button, html=True)
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
def test_multiple_mode_creation(self):
|
||||
# Create an honor mode
|
||||
|
||||
@@ -34,6 +34,8 @@ from openedx.core.djangoapps.catalog.utils import get_currency_data
|
||||
from openedx.core.djangoapps.embargo import api as embargo_api
|
||||
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
|
||||
from openedx.features.course_experience.utils import get_first_purchase_offer_banner_fragment
|
||||
from openedx.features.discounts.applicability import discount_percentage
|
||||
from student.models import CourseEnrollment
|
||||
from util.db import outer_atomic
|
||||
from xmodule.modulestore.django import modulestore
|
||||
@@ -190,11 +192,22 @@ class ChooseModeView(View):
|
||||
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"] = verified_mode.min_price
|
||||
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()
|
||||
|
||||
@@ -49,7 +49,7 @@ class TrackSelectionPage(PageObject):
|
||||
if mode == "verified":
|
||||
# Check the first contribution option, then click the enroll button
|
||||
self.q(css=".contribution-option > input").first.click()
|
||||
self.q(css="input[name='verified_mode']").click()
|
||||
self.q(css="button[name='verified_mode']").click()
|
||||
return PaymentAndVerificationFlow(self.browser, self._course_id).wait_for_page()
|
||||
|
||||
elif mode == "audit":
|
||||
|
||||
@@ -1556,10 +1556,15 @@
|
||||
@include text-align(left);
|
||||
}
|
||||
|
||||
.action-select input {
|
||||
.action-select input, .action-select button {
|
||||
@extend %btn-verify-primary;
|
||||
}
|
||||
|
||||
.action-select button[name="verified_mode"] {
|
||||
font-weight: 600;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
// extra register options/info
|
||||
.title-expand {
|
||||
@extend %t-copy-sub1;
|
||||
@@ -2249,6 +2254,21 @@
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// First purchase offer banner
|
||||
.first-purchase-offer-banner {
|
||||
background-color: #dee3f1;
|
||||
font-size: 16px;
|
||||
border-radius: 7px;
|
||||
padding: 20px;
|
||||
|
||||
.first-purchase-offer-banner-bold {
|
||||
font-weight: bold;
|
||||
color: #23419f;
|
||||
margin-right: 3px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.reverify-blocked {
|
||||
|
||||
25
lms/templates/course_modes/_upgrade_button.html
Normal file
25
lms/templates/course_modes/_upgrade_button.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<%page args="content_gating_enabled, course_duration_limit_enabled, min_price, price_before_discount" expression_filter="h"/>
|
||||
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
|
||||
<li class="action action-select">
|
||||
<input type="hidden" name="contribution" value="${price_before_discount or min_price}" />
|
||||
% if content_gating_enabled or course_duration_limit_enabled:
|
||||
<button type="submit" name="verified_mode">
|
||||
<span>${_('Pursue the Verified Track')}</span>
|
||||
% else:
|
||||
<button type="submit" name="verified_mode">
|
||||
<span>${_('Pursue a Verified Certificate')}</span>
|
||||
% endif
|
||||
% if price_before_discount:
|
||||
(<span class="upgrade-price-string">$${min_price} USD</span> <del> <span class="upgrade-price-string">${Text('${price} USD').format(price=price_before_discount)}</span></del>)
|
||||
% else:
|
||||
(<span class="upgrade-price-string">$${min_price} USD</span>)
|
||||
% endif
|
||||
</button>
|
||||
</li>
|
||||
@@ -42,7 +42,7 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
});
|
||||
|
||||
% if use_ecommerce_payment_flow:
|
||||
$('input[name=verified_mode]').click(function(e){
|
||||
$('button[name=verified_mode]').click(function(e){
|
||||
e.preventDefault();
|
||||
window.location.href = '${ecommerce_payment_page | n, js_escaped_string}?sku=' +
|
||||
encodeURIComponent('${sku | n, js_escaped_string}');
|
||||
@@ -77,6 +77,10 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
</h3>
|
||||
</header>
|
||||
|
||||
% if offer_banner_fragment:
|
||||
${HTML(offer_banner_fragment.content)}
|
||||
% endif
|
||||
|
||||
<form class="form-register-choose" method="post" name="enrollment_mode_form" id="enrollment_mode_form">
|
||||
<%
|
||||
b_tag_kwargs = {'b_start': HTML('<b>'), 'b_end': HTML('</b>')}
|
||||
@@ -120,10 +124,7 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
</div>
|
||||
<div class="copy-inline list-actions">
|
||||
<ul class="list-actions">
|
||||
<li class="action action-select">
|
||||
<input type="hidden" name="contribution" value="${min_price}" />
|
||||
<input type="submit" name="verified_mode" value="${_('Pursue a Verified Certificate')} ($${min_price} USD)" />
|
||||
</li>
|
||||
<%include file='_upgrade_button.html' args='content_gating_enabled=content_gating_enabled, course_duration_limit_enabled=course_duration_limit_enabled, min_price=min_price, price_before_discount=price_before_discount' />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,18 +165,11 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
</div>
|
||||
<div class="copy-inline list-actions">
|
||||
<ul class="list-actions">
|
||||
<li class="action action-select">
|
||||
<input type="hidden" name="contribution" value="${min_price}" />
|
||||
% if content_gating_enabled or course_duration_limit_enabled:
|
||||
<input type="submit" name="verified_mode" value="${_('Pursue the Verified Track')} ($${min_price} USD)" />
|
||||
% else:
|
||||
<input type="submit" name="verified_mode" value="${_('Pursue a Verified Certificate')} ($${min_price} USD)" />
|
||||
% endif
|
||||
</li>
|
||||
<%include file='_upgrade_button.html' args='content_gating_enabled=content_gating_enabled, course_duration_limit_enabled=course_duration_limit_enabled, min_price=min_price, price_before_discount=price_before_discount' />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
<div id="currency_data" value='{"CAN": {"rate": 2.2, "code": "CAD", "symbol": "$"}}'></div>
|
||||
<input type="submit" name="verified_mode" value="Pursue a Verified Certificate ($100 USD)">
|
||||
<button type="submit" class="no-discount" name="verified_mode"><span>Pursue a Verified Certificate</span>(<span class="upgrade-price-string">$100 USD</span>)</button>
|
||||
<button type="submit" class="discount" name="verified_mode"><span>Pursue a Verified Certificate</span>(<span class="upgrade-price-string">$90 USD</span> <del><span class="upgrade-price-string">$100 USD</span></del>)</button>
|
||||
|
||||
@@ -4,17 +4,28 @@ import $ from 'jquery'; // eslint-disable-line import/extensions
|
||||
|
||||
export class Currency { // eslint-disable-line import/prefer-default-export
|
||||
|
||||
setPrice() {
|
||||
editText(price) {
|
||||
const l10nCookie = this.countryL10nData;
|
||||
const lmsregex = /(\$)(\d*)( USD)/g;
|
||||
const price = $('input[name="verified_mode"]').filter(':visible')[0];
|
||||
const regexMatch = lmsregex.exec(price.value);
|
||||
const dollars = parseFloat(regexMatch[2]);
|
||||
const converted = dollars * l10nCookie.rate;
|
||||
const string = `${l10nCookie.symbol}${Math.round(converted)} ${l10nCookie.code}`;
|
||||
// Use regex to change displayed price on track selection
|
||||
// based on edx-price-l10n cookie currency_data
|
||||
price.value = price.value.replace(regexMatch[0], string);
|
||||
const lmsregex = /(\$)([\d|.]*)( USD)/g;
|
||||
const priceText = price.text();
|
||||
const regexMatch = lmsregex.exec(priceText);
|
||||
if (regexMatch) {
|
||||
const currentPrice = regexMatch[2];
|
||||
const dollars = parseFloat(currentPrice);
|
||||
const newPrice = dollars * l10nCookie.rate;
|
||||
const newPriceString = `${l10nCookie.symbol}${Math.round(newPrice)} ${l10nCookie.code}`;
|
||||
// Change displayed price based on edx-price-l10n cookie currency_data
|
||||
price.text(newPriceString);
|
||||
}
|
||||
}
|
||||
|
||||
setPrice() {
|
||||
$('.upgrade-price-string').each((i, price) => {
|
||||
// When the button includes two prices (discounted and previous)
|
||||
// we call the method twice, since it modifies one price at a time.
|
||||
// Could also be used to modify all prices on any page
|
||||
this.editText($(price));
|
||||
});
|
||||
}
|
||||
|
||||
getCountry() {
|
||||
|
||||
@@ -36,12 +36,17 @@ describe('Currency factory', () => {
|
||||
it('when location is the default (US)', () => {
|
||||
$.cookie('edx-price-l10n', '{"rate":1,"code":"USD","symbol":"$","countryCode":"US"}', { path: '/' });
|
||||
currency = new Currency();
|
||||
expect($('input[name="verified_mode"]').filter(':visible')[0].value).toEqual('Pursue a Verified Certificate ($100 USD)');
|
||||
expect($('[name="verified_mode"].no-discount').filter(':visible').text()).toEqual('Pursue a Verified Certificate($100 USD)');
|
||||
});
|
||||
it('when cookie is set to a different country', () => {
|
||||
$.cookie('edx-price-l10n', '{"rate":2.2,"code":"CAD","symbol":"$","countryCode":"CAN"}', { expires: 1 });
|
||||
currency = new Currency();
|
||||
expect($('input[name="verified_mode"]').filter(':visible')[0].value).toEqual('Pursue a Verified Certificate ($220 CAD)');
|
||||
expect($('[name="verified_mode"].no-discount').filter(':visible').text()).toEqual('Pursue a Verified Certificate($220 CAD)');
|
||||
});
|
||||
it('when cookie is set to a different country with a discount', () => {
|
||||
$.cookie('edx-price-l10n', '{"rate":2.2,"code":"CAD","symbol":"$","countryCode":"CAN"}', { expires: 1 });
|
||||
currency = new Currency();
|
||||
expect($('[name="verified_mode"].discount').filter(':visible').text()).toEqual('Pursue a Verified Certificate($198 CAD $220 CAD)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<%page args="content_gating_enabled, course_duration_limit_enabled, min_price, price_before_discount" expression_filter="h"/>
|
||||
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
%>
|
||||
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
|
||||
<li class="action action-select">
|
||||
<input type="hidden" name="contribution" value="${price_before_discount or min_price}" />
|
||||
% if content_gating_enabled or course_duration_limit_enabled:
|
||||
<button type="submit" name="verified_mode">
|
||||
<span>${_('Pursue the Verified Track')}</span>
|
||||
% else:
|
||||
<button type="submit" name="verified_mode">
|
||||
<span>${_('Pursue a Verified Certificate')}</span>
|
||||
% endif
|
||||
% if price_before_discount:
|
||||
(<span class="upgrade-price-string">$${min_price} USD</span> <del> <span class="upgrade-price-string">${Text('${price} USD').format(price=price_before_discount)}</span></del>)
|
||||
% else:
|
||||
(<span class="upgrade-price-string">$${min_price} USD</span>)
|
||||
% endif
|
||||
</button>
|
||||
</li>
|
||||
@@ -45,16 +45,11 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
});
|
||||
|
||||
% if use_ecommerce_payment_flow:
|
||||
$('input[name=verified_mode]').click(function(e){
|
||||
$('button[name=verified_mode]').click(function(e){
|
||||
e.preventDefault();
|
||||
window.location.href = '${ecommerce_payment_page | n, js_escaped_string}?sku=' +
|
||||
encodeURIComponent('${sku | n, js_escaped_string}');
|
||||
});
|
||||
$('.v2 button[name=verified_mode]').click(function(e){
|
||||
e.preventDefault();
|
||||
window.location.href = 'https://ecommerce.edx.org/coupons/redeem/?code=EDXTSV35&sku=' +
|
||||
encodeURIComponent('${sku | n, js_escaped_string}');
|
||||
});
|
||||
% endif
|
||||
});
|
||||
</script>
|
||||
@@ -83,54 +78,27 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
%endif
|
||||
|
||||
<div id="currency_data" value="${currency_data}"></div>
|
||||
|
||||
<div class="container">
|
||||
<section class="wrapper">
|
||||
<div class="wrapper-register-choose wrapper-content-main">
|
||||
<article class="register-choose content-main">
|
||||
<header class="page-header content-main">
|
||||
<h3 class="title v1">
|
||||
<h3 class="title">
|
||||
${title_content}
|
||||
</h3>
|
||||
<!-- This div was added as part of the LEARNER-1726 experiment. The v2 class should be removed if the experiment is implemented-->
|
||||
<h3 class="title v2 hidden">
|
||||
Next, Select Your Learning Path
|
||||
</h3>
|
||||
</header>
|
||||
|
||||
% if offer_banner_fragment:
|
||||
${HTML(offer_banner_fragment.content)}
|
||||
% endif
|
||||
|
||||
<form class="form-register-choose" method="post" name="enrollment_mode_form" id="enrollment_mode_form">
|
||||
<%
|
||||
b_tag_kwargs = {'b_start': HTML('<b>'), 'b_end': HTML('</b>')}
|
||||
%>
|
||||
% if "verified" in modes:
|
||||
<!-- This div was added as part of the LEARNER-1726 experiment. The v2 class should be removed if the experiment is implemented-->
|
||||
<div class="register-choice register-choice-certificate v2 hidden">
|
||||
<h4 class="title">Pursue a Verified Certificate</h4>
|
||||
<div class="wrapper-copy-inline">
|
||||
<div class="wrapper-copy-inline">
|
||||
<div class="wrapper-copy">
|
||||
Get premium level support throughout the course and commit to completion up front.
|
||||
</div>
|
||||
<img src="/static/images/cert-verified-thumb.png" class="visual-reference img-certificate" alt="Visual representation of a Certificate"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="copy-inline">
|
||||
<ul class="list-actions">
|
||||
<li class="action action-select">
|
||||
<input type="hidden" name="contribution" value="${min_price}" />
|
||||
<!-- The class verified_mode should be added to this selector if the experiment is implemented-->
|
||||
<div class="upgradev1">
|
||||
<input type="submit" name="verified_mode" value="Upgrade to a Certificate ($${min_price} USD)" />
|
||||
</div>
|
||||
<div class="upgradev2 hidden">
|
||||
<button type="submit" name="verified_mode">Upgrade to a Certificate (<del>$${min_price} USD</del>)</button>
|
||||
<br>
|
||||
<div class="savings-message">Save 5% if you upgrade now! ($${int(min_price * .95)} USD)</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="register-choice register-choice-certificate v1">
|
||||
<div class="register-choice register-choice-certificate">
|
||||
<div class="wrapper-copy">
|
||||
<span class="deco-ribbon"></span>
|
||||
% if has_credit_upsell:
|
||||
@@ -169,10 +137,7 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
</div>
|
||||
<div class="copy-inline list-actions">
|
||||
<ul class="list-actions">
|
||||
<li class="action action-select">
|
||||
<input type="hidden" name="contribution" value="${min_price}" />
|
||||
<input type="submit" name="verified_mode" value="${_('Pursue a Verified Certificate')} ($${min_price} USD)" />
|
||||
</li>
|
||||
<%include file='_upgrade_button.html' args='content_gating_enabled=content_gating_enabled, course_duration_limit_enabled=course_duration_limit_enabled, min_price=min_price, price_before_discount=price_before_discount' />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -215,14 +180,7 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
</div>
|
||||
<div class="copy-inline list-actions">
|
||||
<ul class="list-actions">
|
||||
<li class="action action-select">
|
||||
<input type="hidden" name="contribution" value="${min_price}" />
|
||||
% if content_gating_enabled or course_duration_limit_enabled:
|
||||
<input type="submit" name="verified_mode" value="${_('Pursue the Verified Track')} ($${min_price} USD)" />
|
||||
% else:
|
||||
<input type="submit" name="verified_mode" value="${_('Pursue a Verified Certificate')} ($${min_price} USD)" />
|
||||
% endif
|
||||
</li>
|
||||
<%include file='_upgrade_button.html' args='content_gating_enabled=content_gating_enabled, course_duration_limit_enabled=course_duration_limit_enabled, min_price=min_price, price_before_discount=price_before_discount' />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -254,50 +212,11 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
</ul>
|
||||
</div>
|
||||
% elif "audit" in modes:
|
||||
<span class="deco-divider v1">
|
||||
<span class="copy">${_("or")}</span>
|
||||
</span>
|
||||
<!-- This div was added as part of the LEARNER-1726 experiment. The v2 class should be removed if the experiment is implemented-->
|
||||
<span class="deco-divider v2 hidden">
|
||||
<span class="deco-divider">
|
||||
<span class="copy">${_("or")}</span>
|
||||
</span>
|
||||
|
||||
<!-- This div was added as part of the LEARNER-1726 experiment. The v2 class should be removed if the experiment is implemented-->
|
||||
<div class="register-choice register-choice-continue v2 hidden">
|
||||
<h4 class="title">
|
||||
I Don't Want to Upgrade or Donate Today
|
||||
</h4>
|
||||
<div class="wrapper-copy-inline">
|
||||
<div class="wrapper-copy">
|
||||
If you do not want to add a certificate or donate to edX's mission today, you can skip this step for now and continue to the course.
|
||||
</div>
|
||||
<img src="/static/images/edx-home-graphic.png" class="visual-reference img-donate" alt="Visual of two hands forming a heart shape" >
|
||||
</div>
|
||||
<div class="copy-inline">
|
||||
<ul class="list-actions">
|
||||
<li class="action action-select">
|
||||
<a class="continue-link" href="/dashboard">Continue to Course</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- This div was added as part of the LEARNER-1726 experiment. The v2 class should be removed if the experiment is implemented-->
|
||||
<div class="register-choice register-choice-v2-donate register-choice-view v2 hidden">
|
||||
<h4 class="title">Donate to Support our Non-Profit Mission</h4>
|
||||
<div class="wrapper-copy-inline">
|
||||
<div class="wrapper-copy">
|
||||
Even if you are not interested in pursuing a Verified Certificate, a donation helps edX continue to work towards its non-profit mission of making the world's best education more accessible to learners everywhere.
|
||||
</div>
|
||||
<div class="copy-inline">
|
||||
<ul class="list-actions">
|
||||
<a class="donation-link" href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AG9VK2LC29L5Y">Donate and Continue to Course</a>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="register-choice register-choice-audit v1">
|
||||
<div class="register-choice register-choice-audit">
|
||||
<div class="wrapper-copy">
|
||||
<span class="deco-ribbon"></span>
|
||||
<h4 class="title">${_("Audit This Course (No Certificate)")}</h4>
|
||||
|
||||
Reference in New Issue
Block a user