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
055ad535
Commit
055ad535
authored
Aug 20, 2013
by
Jason Bau
Committed by
Diana Huang
Aug 22, 2013
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
100% coverage on CyberSource.py
parent
d140ffd8
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
233 additions
and
21 deletions
+233
-21
lms/djangoapps/shoppingcart/processors/CyberSource.py
+10
-17
lms/djangoapps/shoppingcart/processors/tests/test_CyberSource.py
+222
-3
lms/djangoapps/shoppingcart/views.py
+1
-1
No files found.
lms/djangoapps/shoppingcart/processors/CyberSource.py
View file @
055ad535
...
...
@@ -45,7 +45,7 @@ def process_postpay_callback(params):
except
CCProcessorException
as
e
:
return
{
'success'
:
False
,
'order'
:
None
,
#due to exception we may not have the order
'error_html'
:
get_processor_exception_html
(
params
,
e
)}
'error_html'
:
get_processor_exception_html
(
e
)}
def
hash
(
value
):
...
...
@@ -57,7 +57,7 @@ def hash(value):
return
binascii
.
b2a_base64
(
hash_obj
.
digest
())[:
-
1
]
# last character is a '\n', which we don't want
def
sign
(
params
):
def
sign
(
params
,
signed_fields_key
=
'orderPage_signedFields'
,
full_sig_key
=
'orderPage_signaturePublic'
):
"""
params needs to be an ordered dict, b/c cybersource documentation states that order is important.
Reverse engineered from PHP version provided by cybersource
...
...
@@ -74,8 +74,8 @@ def sign(params):
values
=
","
.
join
([
"{0}={1}"
.
format
(
i
,
params
[
i
])
for
i
in
params
.
keys
()])
fields_sig
=
hash
(
fields
)
values
+=
",signedFieldsPublicSignature="
+
fields_sig
params
[
'orderPage_signaturePublic'
]
=
hash
(
values
)
params
[
'orderPage_signedFields'
]
=
fields
params
[
full_sig_key
]
=
hash
(
values
)
params
[
signed_fields_key
]
=
fields
return
params
...
...
@@ -97,7 +97,7 @@ def verify_signatures(params, signed_fields_key='signedFields', full_sig_key='si
raise
CCProcessorSignatureException
()
def
render_purchase_form_html
(
cart
,
user
):
def
render_purchase_form_html
(
cart
):
"""
Renders the HTML of the hidden POST form that must be used to initiate a purchase with CyberSource
"""
...
...
@@ -111,14 +111,6 @@ def render_purchase_form_html(cart, user):
params
[
'currency'
]
=
cart
.
currency
params
[
'orderPage_transactionType'
]
=
'sale'
params
[
'orderNumber'
]
=
"{0:d}"
.
format
(
cart
.
id
)
idx
=
1
for
item
in
cart_items
:
prefix
=
"item_{0:d}_"
.
format
(
idx
)
params
[
prefix
+
'productSKU'
]
=
"{0:d}"
.
format
(
item
.
id
)
params
[
prefix
+
'quantity'
]
=
item
.
qty
params
[
prefix
+
'productName'
]
=
item
.
line_desc
params
[
prefix
+
'unitPrice'
]
=
item
.
unit_cost
params
[
prefix
+
'taxAmount'
]
=
"0.00"
signed_param_dict
=
sign
(
params
)
return
render_to_string
(
'shoppingcart/cybersource_form.html'
,
{
...
...
@@ -179,14 +171,14 @@ def payment_accepted(params):
else
:
raise
CCProcessorWrongAmountException
(
_
(
"The amount charged by the processor {0} {1} is different than the total cost of the order {2} {3}."
\
.
format
(
valid_params
[
'ccAuthReply_amount'
]
,
valid_params
[
'orderCurrency'
],
.
format
(
charged_amt
,
valid_params
[
'orderCurrency'
],
order
.
total_cost
,
order
.
currency
))
)
else
:
return
{
'accepted'
:
False
,
'amt_charged'
:
0
,
'currency'
:
'usd'
,
'order'
:
None
}
'order'
:
order
}
def
record_purchase
(
params
,
order
):
...
...
@@ -236,7 +228,7 @@ def get_processor_decline_html(params):
email
=
payment_support_email
)
def
get_processor_exception_html
(
params
,
exception
):
def
get_processor_exception_html
(
exception
):
"""Return error HTML associated with exception"""
payment_support_email
=
settings
.
PAYMENT_SUPPORT_EMAIL
...
...
@@ -267,10 +259,11 @@ def get_processor_exception_html(params, exception):
<p class="error_msg">
Sorry! Our payment processor sent us back a corrupted message regarding your charge, so we are
unable to validate that the message actually came from the payment processor.
The specific error message is: <span class="exception_msg">{msg}</span>.
We apologize that we cannot verify whether the charge went through and take further action on your order.
Your credit card may possibly have been charged. Contact us with payment-specific questions at {email}.
</p>
"""
.
format
(
email
=
payment_support_email
)))
"""
.
format
(
msg
=
exception
.
message
,
email
=
payment_support_email
)))
return
msg
# fallthrough case, which basically never happens
...
...
lms/djangoapps/shoppingcart/processors/tests/test_CyberSource.py
View file @
055ad535
...
...
@@ -5,8 +5,12 @@ from collections import OrderedDict
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
from
django.conf
import
settings
from
student.tests.factories
import
UserFactory
from
shoppingcart.models
import
Order
,
OrderItem
from
shoppingcart.processors.CyberSource
import
*
from
shoppingcart.processors.exceptions
import
CCProcessorSignatureException
from
shoppingcart.processors.exceptions
import
*
from
mock
import
patch
,
Mock
TEST_CC_PROCESSOR
=
{
'CyberSource'
:
{
...
...
@@ -25,8 +29,8 @@ class CyberSourceTests(TestCase):
pass
def
test_override_settings
(
self
):
self
.
assertEqual
s
(
settings
.
CC_PROCESSOR
[
'CyberSource'
][
'MERCHANT_ID'
],
'edx_test'
)
self
.
assertEqual
s
(
settings
.
CC_PROCESSOR
[
'CyberSource'
][
'SHARED_SECRET'
],
'secret'
)
self
.
assertEqual
(
settings
.
CC_PROCESSOR
[
'CyberSource'
][
'MERCHANT_ID'
],
'edx_test'
)
self
.
assertEqual
(
settings
.
CC_PROCESSOR
[
'CyberSource'
][
'SHARED_SECRET'
],
'secret'
)
def
test_hash
(
self
):
"""
...
...
@@ -66,4 +70,218 @@ class CyberSourceTests(TestCase):
with
self
.
assertRaises
(
CCProcessorSignatureException
):
verify_signatures
(
params
)
def
test_get_processor_decline_html
(
self
):
"""
Tests the processor decline html message
"""
DECISION
=
'REJECT'
for
code
,
reason
in
REASONCODE_MAP
.
iteritems
():
params
=
{
'decision'
:
DECISION
,
'reasonCode'
:
code
,
}
html
=
get_processor_decline_html
(
params
)
self
.
assertIn
(
DECISION
,
html
)
self
.
assertIn
(
reason
,
html
)
self
.
assertIn
(
code
,
html
)
self
.
assertIn
(
settings
.
PAYMENT_SUPPORT_EMAIL
,
html
)
def
test_get_processor_exception_html
(
self
):
"""
Tests the processor exception html message
"""
for
type
in
[
CCProcessorSignatureException
,
CCProcessorWrongAmountException
,
CCProcessorDataException
]:
error_msg
=
"An exception message of with exception type {0}"
.
format
(
str
(
type
))
exception
=
type
(
error_msg
)
html
=
get_processor_exception_html
(
exception
)
self
.
assertIn
(
settings
.
PAYMENT_SUPPORT_EMAIL
,
html
)
self
.
assertIn
(
'Sorry!'
,
html
)
self
.
assertIn
(
error_msg
,
html
)
# test base case
self
.
assertIn
(
"EXCEPTION!"
,
get_processor_exception_html
(
CCProcessorException
()))
def
test_record_purchase
(
self
):
"""
Tests record_purchase with good and without returned CCNum
"""
student1
=
UserFactory
()
student1
.
save
()
student2
=
UserFactory
()
student2
.
save
()
params_cc
=
{
'card_accountNumber'
:
'1234'
,
'card_cardType'
:
'001'
,
'billTo_firstName'
:
student1
.
first_name
}
params_nocc
=
{
'card_accountNumber'
:
''
,
'card_cardType'
:
'002'
,
'billTo_firstName'
:
student2
.
first_name
}
order1
=
Order
.
get_cart_for_user
(
student1
)
order2
=
Order
.
get_cart_for_user
(
student2
)
record_purchase
(
params_cc
,
order1
)
record_purchase
(
params_nocc
,
order2
)
self
.
assertEqual
(
order1
.
bill_to_ccnum
,
'1234'
)
self
.
assertEqual
(
order1
.
bill_to_cardtype
,
'Visa'
)
self
.
assertEqual
(
order1
.
bill_to_first
,
student1
.
first_name
)
self
.
assertEqual
(
order1
.
status
,
'purchased'
)
order2
=
Order
.
objects
.
get
(
user
=
student2
)
self
.
assertEqual
(
order2
.
bill_to_ccnum
,
'####'
)
self
.
assertEqual
(
order2
.
bill_to_cardtype
,
'MasterCard'
)
self
.
assertEqual
(
order2
.
bill_to_first
,
student2
.
first_name
)
self
.
assertEqual
(
order2
.
status
,
'purchased'
)
def
test_payment_accepted_invalid_dict
(
self
):
"""
Tests exception is thrown when params to payment_accepted don't have required key
or have an bad value
"""
baseline
=
{
'orderNumber'
:
'1'
,
'orderCurrency'
:
'usd'
,
'decision'
:
'ACCEPT'
,
}
wrong
=
{
'orderNumber'
:
'k'
,
}
# tests for missing key
for
key
in
baseline
:
params
=
baseline
.
copy
()
del
params
[
key
]
with
self
.
assertRaises
(
CCProcessorDataException
):
payment_accepted
(
params
)
# tests for keys with value that can't be converted to proper type
for
key
in
wrong
:
params
=
baseline
.
copy
()
params
[
key
]
=
wrong
[
key
]
with
self
.
assertRaises
(
CCProcessorDataException
):
payment_accepted
(
params
)
def
test_payment_accepted_order
(
self
):
"""
Tests payment_accepted cases with an order
"""
student1
=
UserFactory
()
student1
.
save
()
order1
=
Order
.
get_cart_for_user
(
student1
)
params
=
{
'card_accountNumber'
:
'1234'
,
'card_cardType'
:
'001'
,
'billTo_firstName'
:
student1
.
first_name
,
'orderNumber'
:
str
(
order1
.
id
),
'orderCurrency'
:
'usd'
,
'decision'
:
'ACCEPT'
,
'ccAuthReply_amount'
:
'0.00'
}
# tests for an order number that doesn't match up
params_bad_ordernum
=
params
.
copy
()
params_bad_ordernum
[
'orderNumber'
]
=
str
(
order1
.
id
+
10
)
with
self
.
assertRaises
(
CCProcessorDataException
):
payment_accepted
(
params_bad_ordernum
)
# tests for a reply amount of the wrong type
params_wrong_type_amt
=
params
.
copy
()
params_wrong_type_amt
[
'ccAuthReply_amount'
]
=
'ab'
with
self
.
assertRaises
(
CCProcessorDataException
):
payment_accepted
(
params_wrong_type_amt
)
# tests for a reply amount of the wrong type
params_wrong_amt
=
params
.
copy
()
params_wrong_amt
[
'ccAuthReply_amount'
]
=
'1.00'
with
self
.
assertRaises
(
CCProcessorWrongAmountException
):
payment_accepted
(
params_wrong_amt
)
# tests for a not accepted order
params_not_accepted
=
params
.
copy
()
params_not_accepted
[
'decision'
]
=
"REJECT"
self
.
assertFalse
(
payment_accepted
(
params_not_accepted
)[
'accepted'
])
# finally, tests an accepted order
self
.
assertTrue
(
payment_accepted
(
params
)[
'accepted'
])
@patch
(
'shoppingcart.processors.CyberSource.render_to_string'
,
autospec
=
True
)
def
test_render_purchase_form_html
(
self
,
render
):
"""
Tests the rendering of the purchase form
"""
student1
=
UserFactory
()
student1
.
save
()
order1
=
Order
.
get_cart_for_user
(
student1
)
item1
=
OrderItem
(
order
=
order1
,
user
=
student1
,
unit_cost
=
1.0
,
line_cost
=
1.0
)
item1
.
save
()
html
=
render_purchase_form_html
(
order1
)
((
template
,
context
),
render_kwargs
)
=
render
.
call_args
self
.
assertEqual
(
template
,
'shoppingcart/cybersource_form.html'
)
self
.
assertDictContainsSubset
({
'amount'
:
'1.00'
,
'currency'
:
'usd'
,
'orderPage_transactionType'
:
'sale'
,
'orderNumber'
:
str
(
order1
.
id
)},
context
[
'params'
])
def
test_process_postpay_exception
(
self
):
"""
Tests the exception path of process_postpay_callback
"""
baseline
=
{
'orderNumber'
:
'1'
,
'orderCurrency'
:
'usd'
,
'decision'
:
'ACCEPT'
,
}
# tests for missing key
for
key
in
baseline
:
params
=
baseline
.
copy
()
del
params
[
key
]
result
=
process_postpay_callback
(
params
)
self
.
assertFalse
(
result
[
'success'
])
self
.
assertIsNone
(
result
[
'order'
])
self
.
assertIn
(
'error_msg'
,
result
[
'error_html'
])
@patch
(
'shoppingcart.processors.CyberSource.verify_signatures'
,
Mock
(
return_value
=
True
))
def
test_process_postpay_accepted
(
self
):
"""
Tests the ACCEPTED path of process_postpay
"""
student1
=
UserFactory
()
student1
.
save
()
order1
=
Order
.
get_cart_for_user
(
student1
)
params
=
{
'card_accountNumber'
:
'1234'
,
'card_cardType'
:
'001'
,
'billTo_firstName'
:
student1
.
first_name
,
'orderNumber'
:
str
(
order1
.
id
),
'orderCurrency'
:
'usd'
,
'decision'
:
'ACCEPT'
,
'ccAuthReply_amount'
:
'0.00'
}
result
=
process_postpay_callback
(
params
)
self
.
assertTrue
(
result
[
'success'
])
self
.
assertEqual
(
result
[
'order'
],
order1
)
order1
=
Order
.
objects
.
get
(
id
=
order1
.
id
)
# reload from DB to capture side-effect of process_postpay_callback
self
.
assertEqual
(
order1
.
status
,
'purchased'
)
self
.
assertFalse
(
result
[
'error_html'
])
@patch
(
'shoppingcart.processors.CyberSource.verify_signatures'
,
Mock
(
return_value
=
True
))
def
test_process_postpay_not_accepted
(
self
):
"""
Tests the non-ACCEPTED path of process_postpay
"""
student1
=
UserFactory
()
student1
.
save
()
order1
=
Order
.
get_cart_for_user
(
student1
)
params
=
{
'card_accountNumber'
:
'1234'
,
'card_cardType'
:
'001'
,
'billTo_firstName'
:
student1
.
first_name
,
'orderNumber'
:
str
(
order1
.
id
),
'orderCurrency'
:
'usd'
,
'decision'
:
'REJECT'
,
'ccAuthReply_amount'
:
'0.00'
,
'reasonCode'
:
'207'
}
result
=
process_postpay_callback
(
params
)
self
.
assertFalse
(
result
[
'success'
])
self
.
assertEqual
(
result
[
'order'
],
order1
)
self
.
assertEqual
(
order1
.
status
,
'cart'
)
self
.
assertIn
(
REASONCODE_MAP
[
'207'
],
result
[
'error_html'
])
\ No newline at end of file
lms/djangoapps/shoppingcart/views.py
View file @
055ad535
...
...
@@ -47,7 +47,7 @@ def show_cart(request):
total_cost
=
cart
.
total_cost
amount
=
"{0:0.2f}"
.
format
(
total_cost
)
cart_items
=
cart
.
orderitem_set
.
all
()
form_html
=
render_purchase_form_html
(
cart
,
request
.
user
)
form_html
=
render_purchase_form_html
(
cart
)
return
render_to_response
(
"shoppingcart/list.html"
,
{
'shoppingcart_items'
:
cart_items
,
'amount'
:
amount
,
...
...
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