${_("Select your contribution for this course (min. $")} ${min_price} ${currency}${_("):")}
-
- % if error:
-
-
-
${error}
+
+
${_("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.")}
+
+
+
+
+ ${_("Benefits of a verified Certificate")}
+
+
+
${_("Official")}: ${_("Receive an instructor-signed certificate with the institution's logo")}
+
${_("Easily sharable")}: ${_("Add the certificate to your CV or resume, or post it directly on LinkedIn")}
+
${_("Motivating")}: ${_("Give yourself an additional incentive to complete the course")}
diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py
index 5849755de0..a51b97dba7 100644
--- a/lms/djangoapps/verify_student/models.py
+++ b/lms/djangoapps/verify_student/models.py
@@ -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):
diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py
index 39dca7c193..86934d9aed 100644
--- a/lms/djangoapps/verify_student/views.py
+++ b/lms/djangoapps/verify_student/views.py
@@ -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.
diff --git a/lms/static/js/spec/verify_student/make_payment_step_view_spec.js b/lms/static/js/spec/verify_student/make_payment_step_view_spec.js
index 6da5b85d34..fa90b016e3 100644
--- a/lms/static/js/spec/verify_student/make_payment_step_view_spec.js
+++ b/lms/static/js/spec/verify_student/make_payment_step_view_spec.js
@@ -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 ) );
+ var sel = $( 'input[name="contribution"]' );
- // 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 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
});
diff --git a/lms/static/js/verify_student/pay_and_verify.js b/lms/static/js/verify_student/pay_and_verify.js
index d7c2eb943c..c7a8a8b043 100644
--- a/lms/static/js/verify_student/pay_and_verify.js
+++ b/lms/static/js/verify_student/pay_and_verify.js
@@ -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'),
diff --git a/lms/static/js/verify_student/views/make_payment_step_view.js b/lms/static/js/verify_student/views/make_payment_step_view.js
index ad519a9cc1..0dd80092b4 100644
--- a/lms/static/js/verify_student/views/make_payment_step_view.js
+++ b/lms/static/js/verify_student/views/make_payment_step_view.js
@@ -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.
diff --git a/lms/static/sass/views/_verification.scss b/lms/static/sass/views/_verification.scss
index 9549b2093a..c530b9a2fa 100644
--- a/lms/static/sass/views/_verification.scss
+++ b/lms/static/sass/views/_verification.scss
@@ -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 {
diff --git a/lms/templates/verify_student/_verification_header.html b/lms/templates/verify_student/_verification_header.html
index 04eea86a15..519b14ae8d 100644
--- a/lms/templates/verify_student/_verification_header.html
+++ b/lms/templates/verify_student/_verification_header.html
@@ -2,71 +2,28 @@
<%namespace name='static' file='../static_content.html'/>
-
-
-
- <% organization = '' + course_org + '' %>
- <% course_name_html = '' + course_num + '' + course_name + '' %>
- % if upgrade:
-
- ${_("You are upgrading your enrollment for {organization}'s {course_name}").format(
- organization=organization,
- course_name=course_name_html
- )}
-
- % elif reverify:
-
- ${_("You are re-verifying for {organization}'s {course_name}").format(
- organization=organization,
- course_name=course_name_html
- )}
-
- % elif modes_dict and "professional" in modes_dict:
-
- ${_("You are enrolling in {organization}'s {course_name}").format(
- organization=organization,
- course_name=course_name_html
- )}
-
- % else:
-
- ${_("Congrats! You are now enrolled in {organization}'s {course_name}").format(
- organization=organization,
- course_name=course_name_html
- )}
-
- % endif
-
-
- % if modes_dict and "professional" in modes_dict:
-
-
- ${_("Professional Education")}
-
-
+
+