Commit 2c10eeb3 by zubair-arbi

update track selection page, use only min price for course modes

ECOM-1228
parent 2c080af6
......@@ -65,8 +65,9 @@ class CourseModeAdmin(admin.ModelAdmin):
search_fields = ('course_id',)
list_display = (
'id', 'course_id', 'mode_slug', 'mode_display_name', 'min_price',
'suggested_prices', 'currency', 'expiration_date', 'expiration_datetime_custom', 'sku'
'currency', 'expiration_date', 'expiration_datetime_custom', 'sku'
)
exclude = ('suggested_prices',)
def expiration_datetime_custom(self, obj):
"""adding custom column to show the expiry_datetime"""
......
......@@ -553,8 +553,8 @@ class CourseMode(models.Model):
)
def __unicode__(self):
return u"{} : {}, min={}, prices={}".format(
self.course_id.to_deprecated_string(), self.mode_slug, self.min_price, self.suggested_prices
return u"{} : {}, min={}".format(
self.course_id, self.mode_slug, self.min_price
)
......
<ul class="list-fields contribution-options">
% for price in suggested_prices:
<li class="field contribution-option">
<input type="radio" name="contribution" value="${price|h}" ${'checked' if price == chosen_price else ''} id="contribution-${price|h}" />
<label for="contribution-${price|h}">
<span class="deco-denomination">$</span>
<span class="label-value">${price}</span>
<span class="denomination-name">${currency}</span>
</label>
</li>
% endfor
<li class="field contribution-option">
<ul class="field-group field-group-other">
<li class="contribution-option contribution-option-other1">
<input type="radio" id="contribution-other" name="contribution" value="" ${'checked' if (chosen_price and chosen_price not in suggested_prices) else ''} />
<label for="contribution-other"><span class="sr">Other</span></label>
</li>
<li class="contribution-option contribution-option-other2">
<label for="contribution-other-amt">
<span class="sr">Other Amount</span>
</label>
<div class="wrapper">
<span class="deco-denomination">$</span>
<input type="text" size="9" name="contribution-other-amt" id="contribution-other-amt" value="${chosen_price if (chosen_price and chosen_price not in suggested_prices) else ''}"/>
<span class="denomination-name">${currency}</span>
</div>
</li>
</ul>
</li>
</ul>
......@@ -62,13 +62,9 @@
<div class="container">
<section class="wrapper">
<%include file="/verify_student/_verification_header.html" args="course_name=course_name" />
<div class="wrapper-register-choose wrapper-content-main">
<article class="register-choose content-main">
% if not upgrade:
<h3 class="title">${_("Now choose your course track:")}</h3>
% endif
<%include file="/verify_student/_verification_header.html" args="course_name=course_name" />
<form class="form-register-choose" method="post" name="enrollment_mode_form" id="enrollment_mode_form">
% if "verified" in modes:
......@@ -77,39 +73,31 @@
<span class="deco-ribbon"></span>
<h4 class="title">${_("Pursue a Verified Certificate")}</h4>
% if upgrade:
<div class="copy">
<p>${_("Plan to use your completed coursework for job applications, career advancement, or school applications? Upgrade to work toward a Verified Certificate of Achievement to document your accomplishment. A minimum fee applies.")}</p>
</div>
% else:
<div class="copy">
<p>${_("Plan to use your completed coursework for job applications, career advancement, or school applications? Then work toward a Verified Certificate of Achievement to document your accomplishment. A minimum fee applies.")}</p>
</div>
% endif
</div>
<div class="field field-certificate-contribution">
<h5 class="label">${_("Select your contribution for this course (min. $")} ${min_price} <span class="denomination-name">${currency}</span>${_("):")}</h5>
% if error:
<div class="msg msg-error msg-inline">
<div class="copy">
<p><i class="msg-icon icon fa fa-exclamation-triangle"></i> ${error}</p>
<div class="copy">
<p>${_("Highlight you new knowledge and skills with a verified certificate. Use this valuable credential to improve your job prospects and advance your career, or highlight your certificate in school applications.")}</p>
<p>
<div class="wrapper-copy-inline">
<div class="copy-inline">
<h4>
${_("Benefits of a verified Certificate")}
</h4>
<ul>
<li><b>${_("Official")}: </b>${_("Receive an instructor-signed certificate with the institution's logo")}</li>
<li><b>${_("Easily sharable")}: </b>${_("Add the certificate to your CV or resume, or post it directly on LinkedIn")}</li>
<li><b>${_("Motivating")}: </b>${_("Give yourself an additional incentive to complete the course")}</li>
</ul>
</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})" />
</li>
</ul>
</div>
</div>
</div>
% endif
<%include file="_contribution.html" args="suggested_prices=suggested_prices, currency=currency, chosen_price=chosen_price, min_price=min_price"/>
<ul class="list-actions">
<li class="action action-select">
% if upgrade:
<input type="submit" name="verified_mode" value="${_('Upgrade Your Enrollment')}" />
% else:
<input type="submit" name="verified_mode" value="${_('Pursue a Verified Certificate')}" />
% endif
</li>
</ul>
</p>
</div>
</div>
</div>
% endif
......@@ -131,7 +119,7 @@
<ul class="list-actions">
<li class="action action-select">
<input type="submit" name="honor_mode" value="${_('Pursue an Honor Code Certificate')}" />
<input type="submit" name="honor_mode" value="${_('Audit This Course')}" />
</li>
</ul>
</div>
......
......@@ -215,19 +215,29 @@ class PhotoVerification(StatusModel):
).exists()
@classmethod
def user_has_valid_or_pending(cls, user, earliest_allowed_date=None, window=None, queryset=None):
def verification_valid_or_pending(cls, user, earliest_allowed_date=None, window=None, queryset=None):
"""
Return whether the user has a complete verification attempt that is or
*might* be good. This means that it's approved, been submitted, or would
have been submitted but had an non-user error when it was being
submitted. It's basically any situation in which the user has signed off
on the contents of the attempt, and we have not yet received a denial.
Check whether the user has a complete verification attempt that is
or *might* be good. This means that it's approved, been submitted,
or would have been submitted but had an non-user error when it was
being submitted.
It's basically any situation in which the user has signed off on
the contents of the attempt, and we have not yet received a denial.
If window=None, this will check for the user's *initial* verification. If
window is anything else, this will check for the reverification associated
with that window.
Arguments:
user:
earliest_allowed_date: earliest allowed date given in the
settings
window: If window=None, this will check for the user's
*initial* verification.
If window is anything else, this will check for the
reverification associated with that window.
queryset: If a queryset is provided, that will be used instead
of hitting the database.
If a queryset is provided, that will be used instead of hitting the database.
Returns:
queryset: queryset of 'PhotoVerification' sorted by 'created_at' in
descending order.
"""
valid_statuses = ['submitted', 'approved']
if not window:
......@@ -242,7 +252,17 @@ class PhotoVerification(StatusModel):
or cls._earliest_allowed_date()
),
window=window,
).exists()
).order_by('-created_at')
@classmethod
def user_has_valid_or_pending(cls, user, earliest_allowed_date=None, window=None, queryset=None):
"""
Check whether the user has an active or pending verification attempt
Returns:
bool: True or False according to existence of valid verifications
"""
return cls.verification_valid_or_pending(user, earliest_allowed_date, window, queryset).exists()
@classmethod
def active_for_user(cls, user, window=None):
......
......@@ -376,6 +376,9 @@ class PayAndVerifyView(View):
# so we can fire an analytics event upon payment.
request.session['attempting_upgrade'] = (message == self.UPGRADE_MSG)
# Determine the photo verification status
verification_good_until = self._verification_valid_until(request.user)
# Render the top-level page
context = {
'contribution_amount': contribution_amount,
......@@ -396,6 +399,8 @@ class PayAndVerifyView(View):
get_default_time_display(unexpired_paid_course_mode.expiration_datetime)
if unexpired_paid_course_mode.expiration_datetime else ""
),
'already_verified': already_verified,
'verification_good_until': verification_good_until,
}
return render_to_response("verify_student/pay_and_verify.html", context)
......@@ -579,6 +584,26 @@ class PayAndVerifyView(View):
return all_requirements
def _verification_valid_until(self, user, date_format="%m/%d/%Y"):
"""
Check whether the user has a valid or pending verification.
Arguments:
user:
date_format: optional parameter for formatting datetime
object to string in response
Returns:
datetime object in string format
"""
photo_verifications = SoftwareSecurePhotoVerification.verification_valid_or_pending(user)
# return 'expiration_datetime' of latest photo verification if found,
# otherwise implicitly return ''
if photo_verifications:
return photo_verifications[0].expiration_datetime.strftime(date_format)
return ''
def _check_already_verified(self, user):
"""Check whether the user has a valid or pending verification.
......
......@@ -20,10 +20,10 @@ define([
var STEP_DATA = {
minPrice: "12",
suggestedPrices: ["34.56", "78.90"],
currency: "usd",
purchaseEndpoint: PAYMENT_URL,
courseKey: "edx/test/test"
courseKey: "edx/test/test",
courseModeSlug: 'verified'
};
var SERVER_ERROR_MSG = "An error occurred!";
......@@ -41,40 +41,12 @@ define([
return view;
};
var expectPriceOptions = function( prices ) {
var sel;
_.each( prices, function( price ) {
sel = _.sprintf( 'input[name="contribution"][value="%s"]', price );
expect( $( sel ).length > 0 ).toBe( true );
});
};
var expectPriceSelected = function( price ) {
var sel = $( _.sprintf( 'input[name="contribution"][value="%s"]', price ) );
// If the option is available, it should be selected
if ( sel.length > 0 ) {
expect( sel.prop( 'checked' ) ).toBe( true );
} else {
// Otherwise, the text box amount should be filled in
expect( $( '#contribution-other' ).prop( 'checked' ) ).toBe( true );
expect( $( '#contribution-other-amt' ).val() ).toEqual( price );
}
};
var choosePriceOption = function( price ) {
var sel = _.sprintf( 'input[name="contribution"][value="%s"]', price );
$( sel ).trigger( 'click' );
};
var enterPrice = function( price ) {
$( '#contribution-other' ).trigger( 'click' );
$( '#contribution-other-amt' ).val( price );
};
var sel = $( 'input[name="contribution"]' );
var expectSinglePriceDisplayed = function( price ) {
var displayedPrice = $( '.contribution-option .label-value' ).text();
expect( displayedPrice ).toEqual( price );
// check that contribution value is same as price given
expect( sel.length ).toEqual(1);
expect( sel.val() ).toEqual(price);
};
var expectPaymentButtonEnabled = function( isEnabled ) {
......@@ -126,11 +98,6 @@ define([
expect(form.attr('action')).toEqual(PAYMENT_URL);
};
var expectErrorDisplayed = function( errorTitle ) {
var actualTitle = $( '#error h3.title' ).text();
expect( actualTitle ).toEqual( errorTitle );
};
beforeEach(function() {
window.analytics = jasmine.createSpyObj('analytics', ['track', 'page', 'trackLink']);
......@@ -138,29 +105,25 @@ define([
TemplateHelpers.installTemplate( 'templates/verify_student/make_payment_step' );
});
it( 'allows users to choose a suggested price', function() {
it( 'shows users only minimum price', function() {
var view = createView({}),
requests = AjaxHelpers.requests(this);
expectPriceOptions( STEP_DATA.suggestedPrices );
expectPaymentButtonEnabled( false );
choosePriceOption( STEP_DATA.suggestedPrices[1] );
expectPriceSelected( STEP_DATA.minPrice );
expectPaymentButtonEnabled( true );
goToPayment( requests, {
amount: STEP_DATA.suggestedPrices[1],
amount: STEP_DATA.minPrice,
courseId: STEP_DATA.courseKey,
succeeds: true
});
expectPaymentSubmitted( view, PAYMENT_PARAMS );
});
it( 'allows users to pay the minimum price if no suggested prices are given', function() {
var view = createView({ suggestedPrices: [] }),
it( 'by default minimum price is selected if no suggested prices are given', function() {
var view = createView(),
requests = AjaxHelpers.requests( this );
expectSinglePriceDisplayed( STEP_DATA.minPrice );
expectPriceSelected( STEP_DATA.minPrice);
expectPaymentButtonEnabled( true );
goToPayment( requests, {
......@@ -171,49 +134,14 @@ define([
expectPaymentSubmitted( view, PAYMENT_PARAMS );
});
it( 'allows the user to enter a contribution amount', function() {
var view = createView({}),
requests = AjaxHelpers.requests( this );
enterPrice( "67.89" );
expectPaymentButtonEnabled( true );
goToPayment( requests, {
amount: "67.89",
courseId: STEP_DATA.courseKey,
succeeds: true
});
expectPaymentSubmitted( view, PAYMENT_PARAMS );
});
it( 'selects in the contribution amount if provided', function() {
// Pre-select one of the suggested prices
createView({
contributionAmount: STEP_DATA.suggestedPrices[1]
});
// Expect that the price is selected
expectPriceSelected( STEP_DATA.suggestedPrices[1]);
});
it( 'fills in the contribution amount if provided', function() {
it( 'min price is always selected even if contribution amount is provided', function() {
// Pre-select a price NOT in the suggestions
createView({
contributionAmount: '99.99'
});
// Expect that the price is filled in
expectPriceSelected( '99.99' );
});
it( 'ignores the contribution pre-selected if no suggested prices are given', function() {
// No suggested prices, but a contribution is set
createView({
suggestedPrices: [],
contributionAmount: '99.99'
});
// Expect that the single price is displayed
expectSinglePriceDisplayed( STEP_DATA.minPrice );
expectPriceSelected( STEP_DATA.minPrice );
});
it( 'disables payment for inactive users', function() {
......@@ -225,9 +153,8 @@ define([
var requests = AjaxHelpers.requests( this ),
view = createView({});
choosePriceOption( STEP_DATA.suggestedPrices[0] );
goToPayment( requests, {
amount: STEP_DATA.suggestedPrices[0],
amount: STEP_DATA.minPrice,
courseId: STEP_DATA.courseKey,
succeeds: false
});
......
......@@ -60,7 +60,10 @@ var edx = edx || {};
),
currency: el.data('course-mode-currency'),
purchaseEndpoint: el.data('purchase-endpoint'),
verificationDeadline: el.data('verification-deadline')
verificationDeadline: el.data('verification-deadline'),
courseModeSlug: el.data('course-mode-slug'),
alreadyVerified: el.data('already-verified'),
verificationGoodUntil: el.data('verification-good-until')
},
'payment-confirmation-step': {
courseKey: el.data('course-key'),
......
......@@ -21,7 +21,10 @@ var edx = edx || {};
courseName: '',
requirements: {},
hasVisibleReqs: false,
platformName: ''
platformName: '',
alreadyVerified: false,
courseModeSlug: 'honor',
verificationGoodUntil: ''
};
},
......@@ -35,15 +38,6 @@ var edx = edx || {};
// Track a virtual pageview, for easy funnel reconstruction.
window.analytics.page( 'payment', this.templateName );
// Set the payment button to disabled by default
this.setPaymentEnabled( false );
// Update the contribution amount with the amount the user
// selected in a previous screen.
if ( templateContext.contributionAmount ) {
this.selectPaymentAmount( templateContext.contributionAmount );
}
// The contribution section is hidden by default
// Display it if the user hasn't already selected an amount
// or is upgrading.
......
......@@ -1231,6 +1231,17 @@
@extend %t-copy-base;
}
.wrapper-copy-inline {
@extend %t-copy-base;
display: inline-block;
width: 100%;
}
.copy-inline {
@extend %t-copy-base;
display: inline-block;
}
.action-select {
@include fill-parent;
......@@ -2415,6 +2426,10 @@
color: $m-blue-d1;
}
.title {
font-weight: 400;
}
// progress nav
.progress .progress-step {
......
......@@ -2,71 +2,28 @@
<%namespace name='static' file='../static_content.html'/>
<header class="page-header">
<h2 class="title">
<span class="wrapper-sts">
<% organization = '<span class="sts-course-org">' + course_org + '</span>' %>
<% course_name_html = '<span class="sts-course-number">' + course_num + '</span> <span class="sts-course-name">' + course_name + '</span>' %>
% if upgrade:
<span class="sts-label">
${_("You are upgrading your enrollment for {organization}'s {course_name}").format(
organization=organization,
course_name=course_name_html
)}
</span>
% elif reverify:
<span class="sts-label">
${_("You are re-verifying for {organization}'s {course_name}").format(
organization=organization,
course_name=course_name_html
)}
</span>
% elif modes_dict and "professional" in modes_dict:
<span class="sts-label">
${_("You are enrolling in {organization}'s {course_name}").format(
organization=organization,
course_name=course_name_html
)}
</span>
% else:
<span class="sts-label">
${_("Congrats! You are now enrolled in {organization}'s {course_name}").format(
organization=organization,
course_name=course_name_html
)}
</span>
% endif
</span>
% if modes_dict and "professional" in modes_dict:
<span class="sts-track professional-ed">
<span class="sts-track-value">
${_("Professional Education")}
</span>
</span>
<header class="page-header content-main">
<h3 class="title">
<% course_name_html = '<span class="sts-course-name">' + course_num + '</span>' %>
<% course_display_html = course_org + "'s " + course_num + " " + course_name %>
% if upgrade:
${_("You are upgrading your enrollment for: {course_name}").format(
course_name=course_name_html
)}
% elif reverify:
${_("You are re-verifying for: {course_name}").format(
course_name=course_name_html
)}
% elif modes_dict and "professional" in modes_dict:
${_("You are enrolling in: {course_name}").format(
course_name=course_name_html
)}
% else:
<span class="sts-track">
<span class="sts-track-value">
% if upgrade:
${_("{span_start}Upgrading to:{span_end} Verified").format(
span_start='<span class="context">',
span_end='</span>'
)}
% elif reverify:
${_("{span_start}Re-verifying for:{span_end} Verified").format(
span_start='<span class="context">',
span_end='</span>'
)}
% else:
${_("{span_start}Enrolling as:{span_end} Verified").format(
span_start='<span class="context">',
span_end='</span>'
)}
% endif
</span>
</span>
${_("Congratulations! You are now enrolled in {course_display}").format(
course_display=course_display_html
)}
% endif
</h2>
</h3>
</header>
<%block name="js_extra">
......
<div id="wrapper-review" class="wrapper-view make-payment-step">
<div id="wrapper-review" tab-index="0" class="wrapper-view make-payment-step">
<div class="review view">
<% if ( !upgrade ) { %>
<h2 class="title center-col">
......@@ -36,6 +36,7 @@
<% } %>
</div>
<% if ( requirements['account-activation-required'] || requirements['photo-id-required'] || requirements['webcam-required']) { %>
<div class="requirements-container">
<ul class="list-reqs <% if ( requirements['account-activation-required'] ) { %>account-not-activated<% } %>">
<% if ( requirements['account-activation-required'] ) { %>
......@@ -77,96 +78,31 @@
<% } %>
</ul>
</div>
<% } %>
<% if ( isActive && ( !hasVisibleReqs || upgrade || !contributionAmount) ) { %>
<div class="wrapper-task hidden" aria-hidden="true">
<ol class="review-tasks">
<% if ( suggestedPrices.length > 0 ) { %>
<li class="review-task review-task-contribution">
<h3 class="title">
<% if ( upgrade ) { %>
<%- gettext( "Choose your contribution for your Verified Track upgrade" ) %>
<% } else { %>
<%- gettext( "Enter Your Contribution Level" ) %>
<% } %>
</h3>
<div class="copy">
<p>
<% if ( !upgrade ) { %>
<%- _.sprintf(
gettext( "Please confirm your contribution for this course (min. %(minPrice)s %(currency)s)" ),
{ minPrice: minPrice, currency: currency }
) %>
<% } %>
</p>
</div>
<div class="list-fields contribution-options">
<% for ( var i = 0; i < suggestedPrices.length; i++ ) {
price = suggestedPrices[i];
%>
<div class="field contribution-option">
<input type="radio" name="contribution" value="<%- price %>" id="contribution-<%- price %>" />
<label for="contribution-<%- price %>">
<span class="deco-denomination">$</span>
<span class="label-value"><%- price %></span>
<span class="denomination-name"><%- currency %></span>
</label>
</div>
<% } %>
<div class="field contribution-option">
<div class="field-group field-group-other">
<div class="contribution-option contribution-option-other1">
<input type="radio" id="contribution-other" name="contribution" value="" />
<label for="contribution-other"><span class="sr">Other</span></label>
</div>
<div class="contribution-option contribution-option-other2">
<label for="contribution-other-amt">
<span class="sr">Other Amount</span>
</label>
<div class="wrapper">
<span class="deco-denomination">$</span>
<input type="text" size="9" name="contribution-other-amt" id="contribution-other-amt" value=""/>
<span class="denomination-name"><%- currency %></span>
</div>
</div>
</div>
</div>
</div>
</li>
<% } else {%>
<li class="review-task review-task-contribution">
<h3 class="title"><%- gettext( "Your Total Contribution" ) %></h3>
<div class="copy">
<p><%- gettext( "To complete your enrollment, you will need to pay:" ) %></p>
</div>
<div class="list-fields contribution-options">
<div class="field contribution-option">
<span class="deco-denomination">$</span>
<span class="label-value"><%- minPrice %></span>
<span class="denomination-name"><%- currency %></span>
</div>
</div>
</li>
<% } %>
</ol>
</div>
<% if ( courseModeSlug === 'no-id-professional') { %>
<div class="container register is-verified">
<h3 class="title"><%- gettext( "ID-Verification is not required for this Professional Education course." ) %></h3>
<p><%- gettext( "All professional education courses are fee-based, and require payment to complete the enrollment process." ) %></p>
</div>
<% } else if ( alreadyVerified && verificationGoodUntil ) { %>
<div class="container register is-verified">
<h3 class="title"><%- gettext( "You have already verified your ID!" ) %></h3>
<p>
<%= _.sprintf(
gettext( "Your verification status is good until %(verificationGoodUntil)s." ),
{ verificationGoodUntil: verificationGoodUntil }
) %>
</p>
</div>
<% } %>
<% if ( isActive ) { %>
<div class="nav-wizard is-ready <% if ( isActive && !upgrade ) { %>center<% } %>">
<% if ( upgrade ) { %>
<a class="next action-primary is-disabled right" id="pay_button" aria-disabled="true">
<%- gettext( "Next: Make payment" ) %>
</a>
<% } else { %>
<a class="next action-primary is-disabled" id="pay_button">
<%- gettext( "Continue to payment" ) %>
</a>
<% } %>
<div class="nav-wizard is-ready center">
<input type="hidden" name="contribution" value="<%- minPrice %>" />
<a class="next action-primary" id="pay_button" tab-index="0">
<%- gettext( "Continue to payment" ) %> ($<%- minPrice %>)
</a>
</div>
<% } %>
......
......@@ -74,6 +74,8 @@ from verify_student.views import PayAndVerifyView
data-requirements='${json.dumps(requirements)}'
data-msg-key='${message_key}'
data-is-active='${is_active}'
data-already-verified='${already_verified}'
data-verification-good-until='${verification_good_until}'
></div>
% if is_active:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment