Start of tests for CyberSource processor
This commit is contained in:
@@ -17,13 +17,6 @@ from mitxmako.shortcuts import render_to_string
|
||||
from shoppingcart.models import Order
|
||||
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(params):
|
||||
"""
|
||||
The top level call to this module, basically
|
||||
@@ -59,6 +52,7 @@ def hash(value):
|
||||
"""
|
||||
Performs the base64(HMAC_SHA1(key, value)) used by CyberSource Hosted Order Page
|
||||
"""
|
||||
shared_secret = settings.CC_PROCESSOR['CyberSource'].get('SHARED_SECRET','')
|
||||
hash_obj = hmac.new(shared_secret, value, sha1)
|
||||
return binascii.b2a_base64(hash_obj.digest())[:-1] # last character is a '\n', which we don't want
|
||||
|
||||
@@ -68,6 +62,10 @@ def sign(params):
|
||||
params needs to be an ordered dict, b/c cybersource documentation states that order is important.
|
||||
Reverse engineered from PHP version provided by cybersource
|
||||
"""
|
||||
merchant_id = settings.CC_PROCESSOR['CyberSource'].get('MERCHANT_ID','')
|
||||
orderPage_version = settings.CC_PROCESSOR['CyberSource'].get('ORDERPAGE_VERSION','7')
|
||||
serial_number = settings.CC_PROCESSOR['CyberSource'].get('SERIAL_NUMBER','')
|
||||
|
||||
params['merchantID'] = merchant_id
|
||||
params['orderPage_timestamp'] = int(time.time()*1000)
|
||||
params['orderPage_version'] = orderPage_version
|
||||
@@ -82,7 +80,7 @@ def sign(params):
|
||||
return params
|
||||
|
||||
|
||||
def verify_signatures(params):
|
||||
def verify_signatures(params, signed_fields_key='signedFields', full_sig_key='signedDataPublicSignature'):
|
||||
"""
|
||||
Verify the signatures accompanying the POST back from Cybersource Hosted Order Page
|
||||
|
||||
@@ -90,11 +88,11 @@ def verify_signatures(params):
|
||||
|
||||
raises CCProcessorSignatureException if not verified
|
||||
"""
|
||||
signed_fields = params.get('signedFields', '').split(',')
|
||||
signed_fields = params.get(signed_fields_key, '').split(',')
|
||||
data = ",".join(["{0}={1}".format(k, params.get(k, '')) for k in signed_fields])
|
||||
signed_fields_sig = hash(params.get('signedFields', ''))
|
||||
signed_fields_sig = hash(params.get(signed_fields_key, ''))
|
||||
data += ",signedFieldsPublicSignature=" + signed_fields_sig
|
||||
returned_sig = params.get('signedDataPublicSignature','')
|
||||
returned_sig = params.get(full_sig_key, '')
|
||||
if hash(data) != returned_sig:
|
||||
raise CCProcessorSignatureException()
|
||||
|
||||
@@ -103,11 +101,12 @@ def render_purchase_form_html(cart, user):
|
||||
"""
|
||||
Renders the HTML of the hidden POST form that must be used to initiate a purchase with CyberSource
|
||||
"""
|
||||
purchase_endpoint = settings.CC_PROCESSOR['CyberSource'].get('PURCHASE_ENDPOINT','')
|
||||
|
||||
total_cost = cart.total_cost
|
||||
amount = "{0:0.2f}".format(total_cost)
|
||||
cart_items = cart.orderitem_set.all()
|
||||
params = OrderedDict()
|
||||
params['comment'] = 'Stanford OpenEdX Purchase'
|
||||
params['amount'] = amount
|
||||
params['currency'] = cart.currency
|
||||
params['orderPage_transactionType'] = 'sale'
|
||||
@@ -217,6 +216,8 @@ def record_purchase(params, order):
|
||||
|
||||
def get_processor_decline_html(params):
|
||||
"""Have to parse through the error codes to return a helpful message"""
|
||||
payment_support_email = settings.PAYMENT_SUPPORT_EMAIL
|
||||
|
||||
msg = _(dedent(
|
||||
"""
|
||||
<p class="error_msg">
|
||||
@@ -238,6 +239,7 @@ def get_processor_decline_html(params):
|
||||
def get_processor_exception_html(params, exception):
|
||||
"""Return error HTML associated with exception"""
|
||||
|
||||
payment_support_email = settings.PAYMENT_SUPPORT_EMAIL
|
||||
if isinstance(exception, CCProcessorDataException):
|
||||
msg = _(dedent(
|
||||
"""
|
||||
@@ -359,7 +361,7 @@ REASONCODE_MAP.update(
|
||||
'234' : _(dedent(
|
||||
"""
|
||||
There is a problem with our CyberSource merchant configuration. Please let us know at {0}
|
||||
""".format(payment_support_email))),
|
||||
""".format(settings.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'),
|
||||
|
||||
@@ -3,11 +3,7 @@ from django.conf import settings
|
||||
### Now code that determines, using settings, which actual processor implementation we're using.
|
||||
processor_name = settings.CC_PROCESSOR.keys()[0]
|
||||
module = __import__('shoppingcart.processors.' + processor_name,
|
||||
fromlist=['sign',
|
||||
'verify',
|
||||
'render_purchase_form_html'
|
||||
'payment_accepted',
|
||||
'record_purchase',
|
||||
fromlist=['render_purchase_form_html'
|
||||
'process_postpay_callback',
|
||||
])
|
||||
|
||||
@@ -34,37 +30,3 @@ def process_postpay_callback(*args, **kwargs):
|
||||
"""
|
||||
return module.process_postpay_callback(*args, **kwargs)
|
||||
|
||||
def sign(*args, **kwargs):
|
||||
"""
|
||||
Given a dict (or OrderedDict) of parameters to send to the
|
||||
credit card processor, signs them in the manner expected by
|
||||
the processor
|
||||
|
||||
Returns a dict containing the signature
|
||||
"""
|
||||
return module.sign(*args, **kwargs)
|
||||
|
||||
def verify(*args, **kwargs):
|
||||
"""
|
||||
Given a dict (or OrderedDict) of parameters to returned by the
|
||||
credit card processor, verifies them in the manner specified by
|
||||
the processor
|
||||
|
||||
Returns a boolean
|
||||
"""
|
||||
return module.sign(*args, **kwargs)
|
||||
|
||||
def payment_accepted(*args, **kwargs):
|
||||
"""
|
||||
Given params returned by the CC processor, check that processor has accepted the payment
|
||||
Returns a dict of {accepted:bool, amt_charged:float, currency:str, order:Order}
|
||||
"""
|
||||
return module.payment_accepted(*args, **kwargs)
|
||||
|
||||
def record_purchase(*args, **kwargs):
|
||||
"""
|
||||
Given params returned by the CC processor, record that the purchase has occurred in
|
||||
the database and also run callbacks
|
||||
"""
|
||||
return module.record_purchase(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Tests for the CyberSource processor handler
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.conf import settings
|
||||
from shoppingcart.processors.CyberSource import *
|
||||
from shoppingcart.processors.exceptions import CCProcessorSignatureException
|
||||
|
||||
TEST_CC_PROCESSOR = {
|
||||
'CyberSource' : {
|
||||
'SHARED_SECRET': 'secret',
|
||||
'MERCHANT_ID' : 'edx_test',
|
||||
'SERIAL_NUMBER' : '12345',
|
||||
'ORDERPAGE_VERSION': '7',
|
||||
'PURCHASE_ENDPOINT': '',
|
||||
}
|
||||
}
|
||||
|
||||
@override_settings(CC_PROCESSOR=TEST_CC_PROCESSOR)
|
||||
class CyberSourceTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_override_settings(self):
|
||||
self.assertEquals(settings.CC_PROCESSOR['CyberSource']['MERCHANT_ID'], 'edx_test')
|
||||
self.assertEquals(settings.CC_PROCESSOR['CyberSource']['SHARED_SECRET'], 'secret')
|
||||
|
||||
def test_hash(self):
|
||||
"""
|
||||
Tests the hash function. Basically just hardcodes the answer.
|
||||
"""
|
||||
self.assertEqual(hash('test'), 'GqNJWF7X7L07nEhqMAZ+OVyks1Y=')
|
||||
self.assertEqual(hash('edx '), '/KowheysqM2PFYuxVKg0P8Flfk4=')
|
||||
|
||||
def test_sign_then_verify(self):
|
||||
"""
|
||||
"loopback" test:
|
||||
Tests the that the verify function verifies parameters signed by the sign function
|
||||
"""
|
||||
params = OrderedDict()
|
||||
params['amount'] = "12.34"
|
||||
params['currency'] = 'usd'
|
||||
params['orderPage_transactionType'] = 'sale'
|
||||
params['orderNumber'] = "567"
|
||||
|
||||
verify_signatures(sign(params), signed_fields_key='orderPage_signedFields',
|
||||
full_sig_key='orderPage_signaturePublic')
|
||||
|
||||
# if the above verify_signature fails it will throw an exception, so basically we're just
|
||||
# testing for the absence of that exception. the trivial assert below does that
|
||||
self.assertEqual(1, 1)
|
||||
|
||||
def test_verify_exception(self):
|
||||
"""
|
||||
Tests that failure to verify raises the proper CCProcessorSignatureException
|
||||
"""
|
||||
params = OrderedDict()
|
||||
params['a'] = 'A'
|
||||
params['b'] = 'B'
|
||||
params['signedFields'] = 'A,B'
|
||||
params['signedDataPublicSignature'] = 'WONTVERIFY'
|
||||
|
||||
with self.assertRaises(CCProcessorSignatureException):
|
||||
verify_signatures(params)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user