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
ca3651fa
Commit
ca3651fa
authored
Aug 20, 2013
by
Jason Bau
Committed by
Diana Huang
Aug 22, 2013
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add handling of CyberSource non-ACCEPT decisions
parent
6f70c9b9
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
227 additions
and
37 deletions
+227
-37
lms/djangoapps/shoppingcart/processors/CyberSource.py
+208
-35
lms/djangoapps/shoppingcart/processors/exceptions.py
+3
-0
lms/djangoapps/shoppingcart/views.py
+2
-2
lms/templates/shoppingcart/error.html
+14
-0
No files found.
lms/djangoapps/shoppingcart/processors/CyberSource.py
View file @
ca3651fa
...
...
@@ -8,18 +8,21 @@ import binascii
import
re
import
json
from
collections
import
OrderedDict
,
defaultdict
from
decimal
import
Decimal
,
InvalidOperation
from
hashlib
import
sha1
from
textwrap
import
dedent
from
django.conf
import
settings
from
django.utils.translation
import
ugettext
as
_
from
mitxmako.shortcuts
import
render_to_string
from
shoppingcart.models
import
Order
from
.exceptions
import
CCProcessorException
,
CCProcessorDataException
,
CCProcessorWrongAmountException
from
.exceptions
import
*
shared_secret
=
settings
.
CC_PROCESSOR
[
'CyberSource'
]
.
get
(
'SHARED_SECRET'
,
''
)
merchant_id
=
settings
.
CC_PROCESSOR
[
'CyberSource'
]
.
get
(
'MERCHANT_ID'
,
''
)
serial_number
=
settings
.
CC_PROCESSOR
[
'CyberSource'
]
.
get
(
'SERIAL_NUMBER'
,
''
)
orderPage_version
=
settings
.
CC_PROCESSOR
[
'CyberSource'
]
.
get
(
'ORDERPAGE_VERSION'
,
'7'
)
purchase_endpoint
=
settings
.
CC_PROCESSOR
[
'CyberSource'
]
.
get
(
'PURCHASE_ENDPOINT'
,
''
)
payment_support_email
=
settings
.
PAYMENT_SUPPORT_EMAIL
def
process_postpay_callback
(
request
):
"""
...
...
@@ -34,27 +37,23 @@ def process_postpay_callback(request):
return a helpful-enough error message in error_html.
"""
params
=
request
.
POST
.
dict
()
if
verify_signatures
(
params
):
try
:
result
=
payment_accepted
(
params
)
if
result
[
'accepted'
]:
# SUCCESS CASE first, rest are some sort of oddity
record_purchase
(
params
,
result
[
'order'
])
return
{
'success'
:
True
,
'order'
:
result
[
'order'
],
'error_html'
:
''
}
else
:
return
{
'success'
:
False
,
'order'
:
result
[
'order'
],
'error_html'
:
get_processor_error_html
(
params
)}
except
CCProcessorException
as
e
:
try
:
verify_signatures
(
params
)
result
=
payment_accepted
(
params
)
if
result
[
'accepted'
]:
# SUCCESS CASE first, rest are some sort of oddity
record_purchase
(
params
,
result
[
'order'
])
return
{
'success'
:
True
,
'order'
:
result
[
'order'
],
'error_html'
:
''
}
else
:
return
{
'success'
:
False
,
'order'
:
None
,
#due to exception we may not have the order
'error_html'
:
get_
exception_html
(
params
,
e
)}
e
ls
e
:
'order'
:
result
[
'order'
],
'error_html'
:
get_
processor_decline_html
(
params
)}
e
xcept
CCProcessorException
as
e
:
return
{
'success'
:
False
,
'order'
:
None
,
'error_html'
:
get_
signature_error_html
(
params
)}
'order'
:
None
,
#due to exception we may not have the order
'error_html'
:
get_
processor_exception_html
(
params
,
e
)}
def
hash
(
value
):
...
...
@@ -87,15 +86,18 @@ def sign(params):
def
verify_signatures
(
params
):
"""
Verify the signatures accompanying the POST back from Cybersource Hosted Order Page
returns silently if verified
raises CCProcessorSignatureException if not verified
"""
signed_fields
=
params
.
get
(
'signedFields'
,
''
)
.
split
(
','
)
data
=
","
.
join
([
"{0}={1}"
.
format
(
k
,
params
.
get
(
k
,
''
))
for
k
in
signed_fields
])
signed_fields_sig
=
hash
(
params
.
get
(
'signedFields'
,
''
))
data
+=
",signedFieldsPublicSignature="
+
signed_fields_sig
returned_sig
=
params
.
get
(
'signedDataPublicSignature'
,
''
)
if
not
returned_sig
:
return
False
return
hash
(
data
)
==
returned_sig
if
hash
(
data
)
!=
returned_sig
:
raise
CCProcessorSignatureException
()
def
render_purchase_form_html
(
cart
,
user
):
...
...
@@ -130,11 +132,18 @@ def render_purchase_form_html(cart, user):
def
payment_accepted
(
params
):
"""
Check that cybersource has accepted the payment
params: a dictionary of POST parameters returned by CyberSource in their post-payment callback
returns: true if the payment was correctly accepted, for the right amount
false if the payment was not accepted
raises: CCProcessorDataException if the returned message did not provide required parameters
CCProcessorWrongAmountException if the amount charged is different than the order amount
"""
#make sure required keys are present and convert their values to the right type
valid_params
=
{}
for
key
,
type
in
[(
'orderNumber'
,
int
),
(
'ccAuthReply_amount'
,
float
),
(
'orderCurrency'
,
str
),
(
'decision'
,
str
)]:
if
key
not
in
params
:
...
...
@@ -154,7 +163,16 @@ def payment_accepted(params):
raise
CCProcessorDataException
(
_
(
"The payment processor accepted an order whose number is not in our system."
))
if
valid_params
[
'decision'
]
==
'ACCEPT'
:
if
valid_params
[
'ccAuthReply_amount'
]
==
order
.
total_cost
and
valid_params
[
'orderCurrency'
]
==
order
.
currency
:
try
:
# Moved reading of charged_amount from the valid_params loop above because
# only 'ACCEPT' messages have a 'ccAuthReply_amount' parameter
charged_amt
=
Decimal
(
params
[
'ccAuthReply_amount'
])
except
InvalidOperation
:
raise
CCProcessorDataException
(
_
(
"The payment processor returned a badly-typed value {0} for param {1}."
.
format
(
params
[
key
],
key
))
)
if
charged_amt
==
order
.
total_cost
and
valid_params
[
'orderCurrency'
]
==
order
.
currency
:
return
{
'accepted'
:
True
,
'amt_charged'
:
valid_params
[
'ccAuthReply_amount'
],
'currency'
:
valid_params
[
'orderCurrency'
],
...
...
@@ -197,21 +215,67 @@ def record_purchase(params, order):
processor_reply_dump
=
json
.
dumps
(
params
)
)
def
get_processor_error_html
(
params
):
"""Have to parse through the error codes for all the other cases"""
return
"<p>ERROR!</p>"
def
get_processor_decline_html
(
params
):
"""Have to parse through the error codes to return a helpful message"""
msg
=
_
(
dedent
(
"""
<p class="error_msg">
Sorry! Our payment processor did not accept your payment.
The decision in they returned was <span class="decision">{decision}</span>,
and the reason was <span class="reason">{reason_code}:{reason_msg}</span>.
You were not charged. Please try a different form of payment.
Contact us with payment-specific questions at {email}.
</p>
"""
))
return
msg
.
format
(
decision
=
params
[
'decision'
],
reason_code
=
params
[
'reasonCode'
],
reason_msg
=
REASONCODE_MAP
[
params
[
'reasonCode'
]],
email
=
payment_support_email
)
def
get_exception_html
(
params
,
exp
):
def
get_processor_exception_html
(
params
,
exception
):
"""Return error HTML associated with exception"""
return
"<p>EXCEPTION!</p>"
def
get_signature_error_html
(
params
):
"""Return error HTML associated with signature failure"""
return
"<p>EXCEPTION!</p>"
if
isinstance
(
exception
,
CCProcessorDataException
):
msg
=
_
(
dedent
(
"""
<p class="error_msg">
Sorry! Our payment processor sent us back a payment confirmation that had inconsistent data!
We apologize that we cannot verify whether the charge went through and take further action on your order.
The specific error message is: <span class="exception_msg">{msg}</span>.
Your credit card may possibly have been charged. Contact us with payment-specific questions at {email}.
</p>
"""
.
format
(
msg
=
exception
.
message
,
email
=
payment_support_email
)))
return
msg
elif
isinstance
(
exception
,
CCProcessorWrongAmountException
):
msg
=
_
(
dedent
(
"""
<p class="error_msg">
Sorry! Due to an error your purchase was charged for a different amount than the order total!
The specific error message is: <span class="exception_msg">{msg}</span>.
Your credit card has probably been charged. Contact us with payment-specific questions at {email}.
</p>
"""
.
format
(
msg
=
exception
.
message
,
email
=
payment_support_email
)))
return
msg
elif
isinstance
(
exception
,
CCProcessorSignatureException
):
msg
=
_
(
dedent
(
"""
<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.
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
)))
return
msg
# fallthrough case, which basically never happens
return
'<p class="error_msg">EXCEPTION!</p>'
CARDTYPE_MAP
=
defaultdict
(
lambda
:
"UNKNOWN"
)
CARDTYPE_MAP
.
update
(
CARDTYPE_MAP
=
defaultdict
(
lambda
:
"UNKNOWN"
)
.
update
(
{
'001'
:
'Visa'
,
'002'
:
'MasterCard'
,
...
...
@@ -233,3 +297,111 @@ CARDTYPE_MAP.update(
'043'
:
'GE Money UK card'
}
)
REASONCODE_MAP
=
defaultdict
(
lambda
:
"UNKNOWN REASON"
)
REASONCODE_MAP
.
update
(
{
'100'
:
_
(
'Successful transaction.'
),
'101'
:
_
(
'The request is missing one or more required fields.'
),
'102'
:
_
(
'One or more fields in the request contains invalid data.'
),
'104'
:
_
(
dedent
(
"""
The merchantReferenceCode sent with this authorization request matches the
merchantReferenceCode of another authorization request that you sent in the last 15 minutes.
Possible fix: retry the payment after 15 minutes.
"""
)),
'150'
:
_
(
'Error: General system failure. Possible fix: retry the payment after a few minutes.'
),
'151'
:
_
(
dedent
(
"""
Error: The request was received but there was a server timeout.
This error does not include timeouts between the client and the server.
Possible fix: retry the payment after some time.
"""
)),
'152'
:
_
(
dedent
(
"""
Error: The request was received, but a service did not finish running in time
Possible fix: retry the payment after some time.
"""
)),
'201'
:
_
(
'The issuing bank has questions about the request. Possible fix: retry with another form of payment'
),
'202'
:
_
(
dedent
(
"""
Expired card. You might also receive this if the expiration date you
provided does not match the date the issuing bank has on file.
Possible fix: retry with another form of payment
"""
)),
'203'
:
_
(
dedent
(
"""
General decline of the card. No other information provided by the issuing bank.
Possible fix: retry with another form of payment
"""
)),
'204'
:
_
(
'Insufficient funds in the account. Possible fix: retry with another form of payment'
),
# 205 was Stolen or lost card. Might as well not show this message to the person using such a card.
'205'
:
_
(
'Unknown reason'
),
'207'
:
_
(
'Issuing bank unavailable. Possible fix: retry again after a few minutes'
),
'208'
:
_
(
dedent
(
"""
Inactive card or card not authorized for card-not-present transactions.
Possible fix: retry with another form of payment
"""
)),
'210'
:
_
(
'The card has reached the credit limit. Possible fix: retry with another form of payment'
),
'211'
:
_
(
'Invalid card verification number. Possible fix: retry with another form of payment'
),
# 221 was The customer matched an entry on the processor's negative file.
# Might as well not show this message to the person using such a card.
'221'
:
_
(
'Unknown reason'
),
'231'
:
_
(
'Invalid account number. Possible fix: retry with another form of payment'
),
'232'
:
_
(
dedent
(
"""
The card type is not accepted by the payment processor.
Possible fix: retry with another form of payment
"""
)),
'233'
:
_
(
'General decline by the processor. Possible fix: retry with another form of payment'
),
'234'
:
_
(
dedent
(
"""
There is a problem with our CyberSource merchant configuration. Please let us know at {0}
"""
.
format
(
payment_support_email
))),
# reason code 235 only applies if we are processing a capture through the API. so we should never see it
'235'
:
_
(
'The requested amount exceeds the originally authorized amount.'
),
'236'
:
_
(
'Processor Failure. Possible fix: retry the payment'
),
# reason code 238 only applies if we are processing a capture through the API. so we should never see it
'238'
:
_
(
'The authorization has already been captured'
),
# reason code 239 only applies if we are processing a capture or credit through the API,
# so we should never see it
'239'
:
_
(
'The requested transaction amount must match the previous transaction amount.'
),
'240'
:
_
(
dedent
(
"""
The card type sent is invalid or does not correlate with the credit card number.
Possible fix: retry with the same card or another form of payment
"""
)),
# reason code 241 only applies when we are processing a capture or credit through the API,
# so we should never see it
'241'
:
_
(
'The request ID is invalid.'
),
# reason code 242 occurs if there was not a previously successful authorization request or
# if the previously successful authorization has already been used by another capture request.
# This reason code only applies when we are processing a capture through the API
# so we should never see it
'242'
:
_
(
dedent
(
"""
You requested a capture through the API, but there is no corresponding, unused authorization record.
"""
)),
# we should never see 243
'243'
:
_
(
'The transaction has already been settled or reversed.'
),
# reason code 246 applies only if we are processing a void through the API. so we should never see it
'246'
:
_
(
dedent
(
"""
The capture or credit is not voidable because the capture or credit information has already been
submitted to your processor. Or, you requested a void for a type of transaction that cannot be voided.
"""
)),
# reason code 247 applies only if we are processing a void through the API. so we should never see it
'247'
:
_
(
'You requested a credit for a capture that was previously voided'
),
'250'
:
_
(
dedent
(
"""
Error: The request was received, but there was a timeout at the payment processor.
Possible fix: retry the payment.
"""
)),
'520'
:
_
(
dedent
(
"""
The authorization request was approved by the issuing bank but declined by CyberSource.'
Possible fix: retry with a different form of payment.
"""
)),
}
)
\ No newline at end of file
lms/djangoapps/shoppingcart/processors/exceptions.py
View file @
ca3651fa
...
...
@@ -3,6 +3,9 @@ from shoppingcart.exceptions import PaymentException
class
CCProcessorException
(
PaymentException
):
pass
class
CCProcessorSignatureException
(
CCProcessorException
):
pass
class
CCProcessorDataException
(
CCProcessorException
):
pass
...
...
lms/djangoapps/shoppingcart/views.py
View file @
ca3651fa
...
...
@@ -86,8 +86,8 @@ def postpay_callback(request):
if
result
[
'success'
]:
return
HttpResponseRedirect
(
reverse
(
'shoppingcart.views.show_receipt'
,
args
=
[
result
[
'order'
]
.
id
]))
else
:
return
render_to_response
(
'shoppingcart
.processor_
error.html'
,
{
'order'
:
result
[
'order'
],
'error_html'
:
result
[
'error_html'
]})
return
render_to_response
(
'shoppingcart
/
error.html'
,
{
'order'
:
result
[
'order'
],
'error_html'
:
result
[
'error_html'
]})
@login_required
def
show_receipt
(
request
,
ordernum
):
...
...
lms/templates/shoppingcart/error.html
0 → 100644
View file @
ca3651fa
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%!
from
django
.
core
.
urlresolvers
import
reverse
%
>
<
%
inherit
file=
"../main.html"
/>
<
%
block
name=
"title"
><title>
${_("Payment Error")}
</title></
%
block>
<section
class=
"container"
>
<p><h1>
${_("There was an error processing your order!")}
</h1></p>
${error_html}
<p><a
href=
"${reverse('shoppingcart.views.show_cart')}"
>
${_("Return to cart to retry payment")}
</a></p>
</section>
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