Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
198d33dc
Commit
198d33dc
authored
Nov 19, 2015
by
Awais
Committed by
Awais Qureshi
Dec 04, 2015
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
New AB Testing URL for checkout page.
ECOM-2866
parent
068b439a
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
484 additions
and
5 deletions
+484
-5
lms/djangoapps/verify_student/tests/test_views.py
+0
-0
lms/djangoapps/verify_student/urls.py
+10
-0
lms/djangoapps/verify_student/views.py
+2
-0
lms/static/images/icon-sm-professional.png
+0
-0
lms/static/js/spec/main.js
+1
-0
lms/static/js/spec/verify_student/make_payment_step_view_ab_testing_spec.js
+225
-0
lms/static/js/spec/verify_student/make_payment_step_view_spec.js
+7
-0
lms/static/js/verify_student/pay_and_verify.js
+2
-1
lms/static/js/verify_student/views/make_payment_step_view.js
+13
-3
lms/static/sass/views/_verification.scss
+111
-0
lms/templates/verify_student/make_payment_step_ab_testing.underscore
+105
-0
lms/templates/verify_student/pay_and_verify.html
+8
-1
No files found.
lms/djangoapps/verify_student/tests/test_views.py
View file @
198d33dc
This diff is collapsed.
Click to expand it.
lms/djangoapps/verify_student/urls.py
View file @
198d33dc
...
...
@@ -23,6 +23,16 @@ urlpatterns = patterns(
}
),
# This is for A/B testing.
url
(
r'^begin-flow/{course}/$'
.
format
(
course
=
settings
.
COURSE_ID_PATTERN
),
views
.
PayAndVerifyView
.
as_view
(),
name
=
"verify_student_begin_flow"
,
kwargs
=
{
'message'
:
views
.
PayAndVerifyView
.
FIRST_TIME_VERIFY_MSG
}
),
# The user is enrolled in a non-paid mode and wants to upgrade.
# This is the same as the "start verification" flow,
# except with slight messaging changes.
...
...
lms/djangoapps/verify_student/views.py
View file @
198d33dc
...
...
@@ -423,7 +423,9 @@ class PayAndVerifyView(View):
'verification_good_until'
:
verification_good_until
,
'capture_sound'
:
staticfiles_storage
.
url
(
"audio/camera_capture.wav"
),
'nav_hidden'
:
True
,
'is_ab_testing'
:
'begin-flow'
in
request
.
path
,
}
return
render_to_response
(
"verify_student/pay_and_verify.html"
,
context
)
def
_redirect_if_necessary
(
...
...
lms/static/images/icon-sm-professional.png
0 → 100644
View file @
198d33dc
2.82 KB
lms/static/js/spec/main.js
View file @
198d33dc
...
...
@@ -674,6 +674,7 @@
'lms/include/js/spec/verify_student/image_input_spec.js'
,
'lms/include/js/spec/verify_student/review_photos_step_view_spec.js'
,
'lms/include/js/spec/verify_student/make_payment_step_view_spec.js'
,
'lms/include/js/spec/verify_student/make_payment_step_view_ab_testing_spec.js'
,
'lms/include/js/spec/edxnotes/utils/logger_spec.js'
,
'lms/include/js/spec/edxnotes/views/notes_factory_spec.js'
,
'lms/include/js/spec/edxnotes/views/shim_spec.js'
,
...
...
lms/static/js/spec/verify_student/make_payment_step_view_ab_testing_spec.js
0 → 100644
View file @
198d33dc
define
([
'jquery'
,
'underscore'
,
'backbone'
,
'common/js/spec_helpers/ajax_helpers'
,
'common/js/spec_helpers/template_helpers'
,
'js/verify_student/views/make_payment_step_view'
],
function
(
$
,
_
,
Backbone
,
AjaxHelpers
,
TemplateHelpers
,
MakePaymentStepView
)
{
'use strict'
;
var
checkPaymentButtons
,
expectPaymentSubmitted
,
goToPayment
,
expectPaymentDisabledBecauseInactive
,
expectPaymentButtonEnabled
,
expectPriceSelected
,
createView
,
SERVER_ERROR_MSG
=
'An error occurred!'
;
describe
(
'edx.verify_student.MakePaymentStepView'
,
function
()
{
var
STEP_DATA
=
{
minPrice
:
'12'
,
currency
:
'usd'
,
processors
:
[
'test-payment-processor'
],
courseKey
:
'edx/test/test'
,
courseModeSlug
:
'verified'
,
isABTesting
:
true
};
createView
=
function
(
stepDataOverrides
)
{
var
view
=
new
MakePaymentStepView
({
el
:
$
(
'#current-step-container'
),
stepData
:
_
.
extend
(
_
.
clone
(
STEP_DATA
),
stepDataOverrides
),
errorModel
:
new
(
Backbone
.
Model
.
extend
({})
)()
}).
render
();
// Stub the payment form submission
spyOn
(
view
,
'submitForm'
).
andCallFake
(
function
()
{}
);
return
view
;
};
expectPriceSelected
=
function
(
price
)
{
var
sel
=
$
(
'input[name="contribution"]'
);
// check that contribution value is same as price given
expect
(
sel
.
length
).
toEqual
(
1
);
expect
(
sel
.
val
()
).
toEqual
(
price
);
};
expectPaymentButtonEnabled
=
function
(
isEnabled
)
{
var
el
=
$
(
'.payment-button'
),
appearsDisabled
=
el
.
hasClass
(
'is-disabled'
),
isDisabled
=
el
.
prop
(
'disabled'
);
expect
(
appearsDisabled
).
not
.
toEqual
(
isEnabled
);
expect
(
isDisabled
).
not
.
toEqual
(
isEnabled
);
};
expectPaymentDisabledBecauseInactive
=
function
()
{
var
payButton
=
$
(
'.payment-button'
);
// Payment button should be hidden
expect
(
payButton
.
length
).
toEqual
(
0
);
};
goToPayment
=
function
(
requests
,
kwargs
)
{
var
params
=
{
contribution
:
kwargs
.
amount
||
''
,
course_id
:
kwargs
.
courseId
||
''
,
processor
:
kwargs
.
processor
||
''
,
sku
:
kwargs
.
sku
||
''
};
// Click the "go to payment" button
$
(
'.payment-button'
).
click
();
// Verify that the request was made to the server
AjaxHelpers
.
expectPostRequest
(
requests
,
'/verify_student/create_order/'
,
$
.
param
(
params
)
);
// Simulate the server response
if
(
kwargs
.
succeeds
)
{
// TODO put fixture responses in the right place
AjaxHelpers
.
respondWithJson
(
requests
,
{
payment_page_url
:
'http://payment-page-url/'
,
payment_form_data
:
{
foo
:
'bar'
}}
);
}
else
{
AjaxHelpers
.
respondWithTextError
(
requests
,
400
,
SERVER_ERROR_MSG
);
}
};
expectPaymentSubmitted
=
function
(
view
,
params
)
{
var
form
;
expect
(
view
.
submitForm
).
toHaveBeenCalled
();
form
=
view
.
submitForm
.
mostRecentCall
.
args
[
0
];
expect
(
form
.
serialize
()).
toEqual
(
$
.
param
(
params
));
expect
(
form
.
attr
(
'method'
)).
toEqual
(
'POST'
);
expect
(
form
.
attr
(
'action'
)).
toEqual
(
'http://payment-page-url/'
);
};
checkPaymentButtons
=
function
(
requests
,
buttons
)
{
var
$el
=
$
(
'.payment-button'
);
expect
(
$el
.
length
).
toEqual
(
_
.
size
(
buttons
));
_
.
each
(
buttons
,
function
(
expectedText
,
expectedId
)
{
var
buttonEl
=
$
(
'#'
+
expectedId
),
request
;
buttonEl
.
removeAttr
(
'disabled'
);
expect
(
buttonEl
.
length
).
toEqual
(
1
);
expect
(
buttonEl
[
0
]
).
toHaveClass
(
'payment-button'
);
expect
(
buttonEl
[
0
]
).
toHaveText
(
expectedText
);
expect
(
buttonEl
[
0
]
).
toHaveClass
(
'action-primary-blue'
);
buttonEl
[
0
].
click
();
expect
(
buttonEl
[
0
]
).
toHaveClass
(
'is-selected'
);
expectPaymentButtonEnabled
(
false
);
request
=
AjaxHelpers
.
currentRequest
(
requests
);
expect
(
request
.
requestBody
.
split
(
'&'
)).
toContain
(
'processor='
+
expectedId
);
AjaxHelpers
.
respondWithJson
(
requests
,
{});
});
};
beforeEach
(
function
()
{
window
.
analytics
=
jasmine
.
createSpyObj
(
'analytics'
,
[
'track'
,
'page'
,
'trackLink'
]);
setFixtures
(
'<div id="current-step-container"></div>'
);
TemplateHelpers
.
installTemplate
(
'templates/verify_student/make_payment_step_ab_testing'
);
});
it
(
'A/B Testing: check Initialize method with AB testing enable '
,
function
()
{
var
view
=
createView
();
expect
(
view
.
templateName
).
toEqual
(
'make_payment_step_ab_testing'
);
expect
(
view
.
btnClass
).
toEqual
(
'action-primary-blue'
);
});
it
(
'shows users only minimum price'
,
function
()
{
var
view
=
createView
(),
requests
=
AjaxHelpers
.
requests
(
this
);
expectPriceSelected
(
STEP_DATA
.
minPrice
);
expectPaymentButtonEnabled
(
true
);
goToPayment
(
requests
,
{
amount
:
STEP_DATA
.
minPrice
,
courseId
:
STEP_DATA
.
courseKey
,
processor
:
STEP_DATA
.
processors
[
0
],
succeeds
:
true
});
expectPaymentSubmitted
(
view
,
{
foo
:
'bar'
}
);
});
it
(
'A/B Testing: provides working payment buttons for a single processor'
,
function
()
{
createView
({
processors
:
[
'cybersource'
]});
checkPaymentButtons
(
AjaxHelpers
.
requests
(
this
),
{
cybersource
:
'Checkout'
});
});
it
(
'A/B Testing: provides working payment buttons for multiple processors'
,
function
()
{
createView
({
processors
:
[
'cybersource'
,
'paypal'
,
'other'
]});
checkPaymentButtons
(
AjaxHelpers
.
requests
(
this
),
{
cybersource
:
'Checkout'
,
paypal
:
'Checkout with PayPal'
,
other
:
'Checkout with other'
});
});
it
(
'A/B Testing: by default minimum price is selected if no suggested prices are given'
,
function
()
{
var
view
=
createView
(),
requests
=
AjaxHelpers
.
requests
(
this
);
expectPriceSelected
(
STEP_DATA
.
minPrice
);
expectPaymentButtonEnabled
(
true
);
goToPayment
(
requests
,
{
amount
:
STEP_DATA
.
minPrice
,
courseId
:
STEP_DATA
.
courseKey
,
processor
:
STEP_DATA
.
processors
[
0
],
succeeds
:
true
});
expectPaymentSubmitted
(
view
,
{
foo
:
'bar'
}
);
});
it
(
'A/B Testing: 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
(
STEP_DATA
.
minPrice
);
});
it
(
'A/B Testing: disables payment for inactive users'
,
function
()
{
createView
({
isActive
:
false
});
expectPaymentDisabledBecauseInactive
();
});
it
(
'A/B Testing: displays an error if the order could not be created'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
),
view
=
createView
();
goToPayment
(
requests
,
{
amount
:
STEP_DATA
.
minPrice
,
courseId
:
STEP_DATA
.
courseKey
,
processor
:
STEP_DATA
.
processors
[
0
],
succeeds
:
false
});
// Expect that an error is displayed
expect
(
view
.
errorModel
.
get
(
'shown'
)
).
toBe
(
true
);
expect
(
view
.
errorModel
.
get
(
'errorTitle'
)
).
toEqual
(
'Could not submit order'
);
expect
(
view
.
errorModel
.
get
(
'errorMsg'
)
).
toEqual
(
SERVER_ERROR_MSG
);
// Expect that the payment button is re-enabled
expectPaymentButtonEnabled
(
true
);
});
});
}
);
lms/static/js/spec/verify_student/make_payment_step_view_spec.js
View file @
198d33dc
...
...
@@ -108,6 +108,7 @@ define([
buttonEl
.
removeAttr
(
'disabled'
);
expect
(
buttonEl
.
length
).
toEqual
(
1
);
expect
(
buttonEl
[
0
]
).
toHaveClass
(
'payment-button'
);
expect
(
buttonEl
[
0
]
).
toHaveClass
(
'action-primary'
);
expect
(
buttonEl
[
0
]
).
toHaveText
(
expectedText
);
buttonEl
[
0
].
click
();
...
...
@@ -216,6 +217,12 @@ define([
'Try the transaction again in a few minutes.'
);
});
it
(
'check Initialize method without AB testing '
,
function
()
{
var
view
=
createView
();
expect
(
view
.
templateName
).
toEqual
(
'make_payment_step'
);
expect
(
view
.
btnClass
).
toEqual
(
'action-primary'
);
});
});
}
);
lms/static/js/verify_student/pay_and_verify.js
View file @
198d33dc
...
...
@@ -66,7 +66,8 @@ var edx = edx || {};
verificationDeadline
:
el
.
data
(
'verification-deadline'
),
courseModeSlug
:
el
.
data
(
'course-mode-slug'
),
alreadyVerified
:
el
.
data
(
'already-verified'
),
verificationGoodUntil
:
el
.
data
(
'verification-good-until'
)
verificationGoodUntil
:
el
.
data
(
'verification-good-until'
),
isABTesting
:
el
.
data
(
'is-ab-testing'
)
},
'payment-confirmation-step'
:
{
courseKey
:
el
.
data
(
'course-key'
),
...
...
lms/static/js/verify_student/views/make_payment_step_view.js
View file @
198d33dc
...
...
@@ -11,6 +11,15 @@ var edx = edx || {};
edx
.
verify_student
.
MakePaymentStepView
=
edx
.
verify_student
.
StepView
.
extend
({
templateName
:
"make_payment_step"
,
btnClass
:
'action-primary'
,
initialize
:
function
(
obj
)
{
_
.
extend
(
this
,
obj
);
if
(
this
.
templateContext
().
isABTesting
)
{
this
.
templateName
=
'make_payment_step_ab_testing'
;
this
.
btnClass
=
'action-primary-blue'
;
}
},
defaultContext
:
function
()
{
return
{
...
...
@@ -27,7 +36,8 @@ var edx = edx || {};
platformName
:
''
,
alreadyVerified
:
false
,
courseModeSlug
:
'audit'
,
verificationGoodUntil
:
''
verificationGoodUntil
:
''
,
isABTesting
:
false
};
},
...
...
@@ -61,8 +71,8 @@ var edx = edx || {};
_getPaymentButtonHtml
:
function
(
processorName
)
{
var
self
=
this
;
return
_
.
template
(
'<button class="next
action-primary
payment-button" id="<%- name %>" ><%- text %></button> '
)({
name
:
processorName
,
text
:
self
.
_getPaymentButtonText
(
processorName
)});
'<button class="next
<%- btnClass %>
payment-button" id="<%- name %>" ><%- text %></button> '
)({
name
:
processorName
,
text
:
self
.
_getPaymentButtonText
(
processorName
)
,
btnClass
:
this
.
btnClass
});
},
postRender
:
function
()
{
...
...
lms/static/sass/views/_verification.scss
View file @
198d33dc
...
...
@@ -175,6 +175,14 @@
color
:
$white
!
important
;
}
// elements - controls
.action-primary-blue
{
@extend
%btn-primary-blue
;
// needed for override due to .register a:link styling
border
:
0
!
important
;
color
:
$white
!
important
;
}
.action-confirm
{
@extend
%btn-verify-primary
;
// needed for override due to .register a:link styling
...
...
@@ -821,6 +829,109 @@
// indiv slides - review
#wrapper-review
{
color
:
$black
;
.page-title
{
@extend
%t-strong
;
border-bottom
:
2px
solid
$m-gray-d3
;
padding-bottom
:
(
$baseline
*
0
.75
);
margin-bottom
:
$baseline
;
text-transform
:
inherit
;
}
.review
{
.certificate
{
@include
font-size
(
18
);
background-repeat
:
no-repeat
;
padding-left
:
(
$baseline
*
2
.5
);
overflow
:
hidden
;
min-height
:
32px
;
p
{
@include
line-height
(
22
);
@extend
%t-strong
;
margin-top
:
0
;
color
:
$black
;
}
.purchase
{
@include
float
(
right
);
@include
margin-left
(
$baseline
*
0
.75
);
text-align
:
right
;
.product-info
{
@include
font-size
(
22
);
@extend
%t-strong
;
color
:
$blue
;
}
}
&
.verified_icon
{
background-image
:
url('
#{
$static-path
}
/images/icon-sm-verified.png')
;
}
&
.no-id-professional_icon
,
&
.professional_icon
{
background-image
:
url('
#{
$static-path
}
/images/icon-sm-professional.png')
;
}
}
.payment-buttons
{
overflow
:
auto
;
padding-bottom
:
(
$baseline
/
4
);
margin
:
{
top
:
(
$baseline
/
2
);
bottom
:
(
$baseline
*
0
.75
);
};
.payment-button
{
padding
:
(
$baseline
*
0
.4
)
$baseline
;
min-width
:
200px
;
}
.action-primary-blue
{
&
.is-selected
{
background
:
$blue
!
important
;
}
}
}
.border-gray
{
border-bottom
:
2px
solid
$gray
;
margin
:
(
$baseline
*
1
.12
)
0
;
}
}
.container
{
padding
:
(
$baseline
*
0
.75
)
0
;
p
{
@include
line-height
(
22
);
color
:
$black
;
}
.photo-requirement
{
@include
font-size
(
12
);
position
:
relative
;
padding-left
:
(
$baseline
*
2
);
margin-top
:
(
$baseline
*
0
.75
);
background-repeat
:
no-repeat
;
background-position
:
left
top
;
.fa
{
position
:
absolute
;
left
:
0
;
color
:
$mediumGrey
;
}
h6
{
font-weight
:
bold
;
color
:
$extraDarkGrey
;
}
}
}
.review-task
{
margin-bottom
:
(
$baseline
*
1
.5
);
...
...
lms/templates/verify_student/make_payment_step_ab_testing.underscore
0 → 100644
View file @
198d33dc
<div id="wrapper-review" tab-index="0" class="wrapper-view make-payment-step">
<div class="review view">
<% if (!isActive ) { %>
<h2 class="page-title">
<%- gettext("Account Not Activated")%>
</h2>
<% } else if ( !upgrade ) { %>
<h2 class="page-title">
<%= _.sprintf(
gettext( "You are enrolling in %(courseName)s"),
{ courseName: '<span class="course-title">' + courseName + '</span>' }
) %>
</h2>
<% } else { %>
<h2 class="page-title">
<%= _.sprintf(
gettext( "Upgrade to a Verified Certificate for %(courseName)s"),
{ courseName: '<span class="course-title">' + courseName + '</span>' }
) %>
</h2>
<% } %>
<% if ( !isActive ) { %>
<p>
<%- gettext("Before you upgrade to a certificate track, you must activate your account.") %>
<%- gettext("Check your email for an activation message.") %>
</p>
<% } else { %>
<div class="certificate <%- courseModeSlug %>_icon">
<div class="purchase">
<p class="product-info"><span class="product-name"></span> <%- gettext( "Total" ) %>: <span class="price">$<%- minPrice %> USD</span></p>
</div>
<p>
<% if ( courseModeSlug === 'no-id-professional' || courseModeSlug === 'professional') { %>
<%= _.sprintf(
gettext( "Professional Certificate for %(courseName)s"),{ courseName: courseName }
)%>
<% } else { %>
<%= _.sprintf(
gettext( "Verified Certificate for %(courseName)s"),{ courseName: courseName }
)%>
<% } %>
</p>
</div>
<% } %>
<% if ( isActive ) { %>
<div class="payment-buttons is-ready center">
<input type="hidden" name="contribution" value="<%- minPrice %>" />
<input type="hidden" name="sku" value="<%- sku %>" />
<div class="pay-options">
<%
// payment buttons will go here
%>
</div>
</div>
<div class="border-gray"></div>
<% } %>
</div>
<% if ( isActive ) { %>
<div class="container">
<% if ( _.some( requirements, function( isVisible ) { return isVisible; } ) ) { %>
<p>
<% if ( verificationDeadline ) { %>
<%- _.sprintf(
gettext( "To receive a certificate, you must also verify your identity before %(date)s." ),
{ date: verificationDeadline }
) %>
<% } else { %>
<%- gettext( "To receive a certificate, you must also verify your identity." ) %>
<% } %>
<%- gettext("To verify your identity, you need a webcam and a government-issued photo ID.") %>
</p>
<% if ( requirements['photo-id-required'] ) { %>
<div class="photo-requirement user_icon">
<i class="fa fa-user fa-2x" aria-hidden="true"></i>
<h6>
<%- gettext("Photo ID") %>
</h6>
<p>
<%- gettext("Your ID must be a government-issued photo ID that clearly shows your face.") %>
</p>
</div>
<% } %>
<% if ( requirements['webcam-required'] ) { %>
<div class="photo-requirement cam_icon">
<i class="fa fa-video-camera fa-2x" aria-hidden="true"></i>
<h6>
<%- gettext("Webcam") %>
</h6>
<p>
<%- gettext("You will use your webcam to take a picture of your face and of your government-issued photo ID.") %>
</p>
</div>
<% } %>
<% } %>
</div>
<% } %>
<form id="payment-processor-form"></form>
</div>
lms/templates/verify_student/pay_and_verify.html
View file @
198d33dc
...
...
@@ -24,9 +24,15 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView
<
%
template_names =
(
["
webcam_photo
",
"
image_input
",
"
error
"]
+
["
intro_step
",
"
make_payment_step
",
"
payment_confirmation_step
"]
+
["
intro_step
",
"
payment_confirmation_step
"]
+
["
face_photo_step
",
"
id_photo_step
",
"
review_photos_step
",
"
enrollment_confirmation_step
"]
)
if
not
is_ab_testing:
template_names
.
append
("
make_payment_step
")
else:
template_names
.
append
("
make_payment_step_ab_testing
")
%
>
% for template_name in template_names:
<script
type=
"text/template"
id=
"${template_name}-tpl"
>
...
...
@@ -76,6 +82,7 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView
data-already-verified=
'${already_verified}'
data-verification-good-until=
'${verification_good_until}'
data-capture-sound=
'${capture_sound}'
data-is-ab-testing=
'${json.dumps(is_ab_testing)}'
##
If
we
reached
the
verification
flow
from
an
in-course
checkpoint
,
##
then
pass
the
checkpoint
location
in
so
that
we
can
associate
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment