Commit be1523f2 by Will Daly

Merge pull request #4880 from edx/will/use-new-cybersource-api

CyberSource API Update
parents c2e804ec 1ad8d4f2
### Implementation of support for the Cybersource Credit card processor
### The name of this file should be used as the key of the dict in the CC_PROCESSOR setting
### Implementes interface as specified by __init__.py
"""
Implementation the CyberSource credit card processor.
IMPORTANT: CyberSource will deprecate this version of the API ("Hosted Order Page") in September 2014.
We are keeping this implementation in the code-base for now, but we should
eventually replace this module with the newer implementation (in `CyberSource2.py`)
To enable this implementation, add the following to Django settings:
CC_PROCESSOR_NAME = "CyberSource"
CC_PROCESSOR = {
"CyberSource": {
"SHARED_SECRET": "<shared secret>",
"MERCHANT_ID": "<merchant ID>",
"SERIAL_NUMBER": "<serial number>",
"PURCHASE_ENDPOINT": "<purchase endpoint>"
}
}
"""
import time
import hmac
import binascii
......@@ -16,26 +32,11 @@ from django.utils.translation import ugettext as _
from edxmako.shortcuts import render_to_string
from shoppingcart.models import Order
from shoppingcart.processors.exceptions import *
from shoppingcart.processors.helpers import get_processor_config
from microsite_configuration import microsite
def get_cybersource_config():
"""
This method will return any microsite specific cybersource configuration, otherwise
we return the default configuration
"""
config_key = microsite.get_value('cybersource_config_key')
config = {}
if config_key:
# The microsite CyberSource configuration will be subkeys inside of the normal default
# CyberSource configuration
config = settings.CC_PROCESSOR['CyberSource']['microsites'][config_key]
else:
config = settings.CC_PROCESSOR['CyberSource']
return config
def process_postpay_callback(params):
def process_postpay_callback(params, **kwargs):
"""
The top level call to this module, basically
This function is handed the callback request after the customer has entered the CC info and clicked "buy"
......@@ -70,7 +71,7 @@ def processor_hash(value):
"""
Performs the base64(HMAC_SHA1(key, value)) used by CyberSource Hosted Order Page
"""
shared_secret = get_cybersource_config().get('SHARED_SECRET', '')
shared_secret = get_processor_config().get('SHARED_SECRET', '')
hash_obj = hmac.new(shared_secret.encode('utf-8'), value.encode('utf-8'), sha1)
return binascii.b2a_base64(hash_obj.digest())[:-1] # last character is a '\n', which we don't want
......@@ -80,9 +81,9 @@ def sign(params, signed_fields_key='orderPage_signedFields', full_sig_key='order
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 = get_cybersource_config().get('MERCHANT_ID', '')
order_page_version = get_cybersource_config().get('ORDERPAGE_VERSION', '7')
serial_number = get_cybersource_config().get('SERIAL_NUMBER', '')
merchant_id = get_processor_config().get('MERCHANT_ID', '')
order_page_version = get_processor_config().get('ORDERPAGE_VERSION', '7')
serial_number = get_processor_config().get('SERIAL_NUMBER', '')
params['merchantID'] = merchant_id
params['orderPage_timestamp'] = int(time.time() * 1000)
......@@ -115,7 +116,7 @@ def verify_signatures(params, signed_fields_key='signedFields', full_sig_key='si
raise CCProcessorSignatureException()
def render_purchase_form_html(cart):
def render_purchase_form_html(cart, **kwargs):
"""
Renders the HTML of the hidden POST form that must be used to initiate a purchase with CyberSource
"""
......@@ -124,9 +125,11 @@ def render_purchase_form_html(cart):
'params': get_signed_purchase_params(cart),
})
def get_signed_purchase_params(cart):
def get_signed_purchase_params(cart, **kwargs):
return sign(get_purchase_params(cart))
def get_purchase_params(cart):
total_cost = cart.total_cost
amount = "{0:0.2f}".format(total_cost)
......@@ -139,8 +142,10 @@ def get_purchase_params(cart):
return params
def get_purchase_endpoint():
return get_cybersource_config().get('PURCHASE_ENDPOINT', '')
return get_processor_config().get('PURCHASE_ENDPOINT', '')
def payment_accepted(params):
"""
......
"""
Public API for payment processor implementations.
The specific implementation is determined at runtime using Django settings:
CC_PROCESSOR_NAME: The name of the Python module (in `shoppingcart.processors`) to use.
CC_PROCESSOR: Dictionary of configuration options for specific processor implementations,
keyed to processor names.
"""
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=['render_purchase_form_html'
'process_postpay_callback',
])
# Import the processor implementation, using `CC_PROCESSOR_NAME`
# as the name of the Python module in `shoppingcart.processors`
PROCESSOR_MODULE = __import__(
'shoppingcart.processors.' + settings.CC_PROCESSOR_NAME,
fromlist=[
'render_purchase_form_html',
'process_postpay_callback',
'get_purchase_endpoint',
'get_signed_purchase_params',
]
)
def render_purchase_form_html(cart, **kwargs):
"""
Render an HTML form with POSTs to the hosted payment processor.
Args:
cart (Order): The order model representing items in the user's cart.
Returns:
unicode: the rendered HTML form
def render_purchase_form_html(*args, **kwargs):
"""
The top level call to this module to begin the purchase.
Given a shopping cart,
Renders the HTML form for display on user's browser, which POSTS to Hosted Processors
Returns the HTML as a string
return PROCESSOR_MODULE.render_purchase_form_html(cart, **kwargs)
def process_postpay_callback(params, **kwargs):
"""
return module.render_purchase_form_html(*args, **kwargs)
Handle a response from the payment processor.
Concrete implementations should:
1) Verify the parameters and determine if the payment was successful.
2) If successful, mark the order as purchased and call `purchased_callbacks` of the cart items.
3) If unsuccessful, try to figure out why and generate a helpful error message.
4) Return a dictionary of the form:
{'success': bool, 'order': Order, 'error_html': str}
Args:
params (dict): Dictionary of parameters received from the payment processor.
Keyword Args:
Can be used to provide additional information to concrete implementations.
Returns:
dict
def process_postpay_callback(*args, **kwargs):
"""
The top level call to this module after the purchase.
This function is handed the callback request after the customer has entered the CC info and clicked "buy"
on the external payment page.
It is expected to verify the callback and determine if the payment was successful.
It returns {'success':bool, 'order':Order, 'error_html':str}
If successful this function must have the side effect of marking the order purchased and calling the
purchased_callbacks of the cart items.
If unsuccessful this function should not have those side effects but should try to figure out why and
return a helpful-enough error message in error_html.
return PROCESSOR_MODULE.process_postpay_callback(params, **kwargs)
def get_purchase_endpoint():
"""
Return the URL of the current payment processor's endpoint.
Returns:
unicode
"""
return PROCESSOR_MODULE.get_purchase_endpoint()
def get_signed_purchase_params(cart, **kwargs):
"""
Return the parameters to send to the current payment processor.
Args:
cart (Order): The order model representing items in the user's cart.
Keyword Args:
Can be used to provide additional information to concrete implementations.
Returns:
dict
"""
return module.process_postpay_callback(*args, **kwargs)
return PROCESSOR_MODULE.get_signed_purchase_params(cart, **kwargs)
"""
Helper methods for credit card processing modules.
These methods should be shared among all processor implementations,
but should NOT be imported by modules outside this package.
"""
from django.conf import settings
from microsite_configuration import microsite
def get_processor_config():
"""
Return a dictionary of configuration settings for the active credit card processor.
If we're in a microsite and overrides are available, return those instead.
Returns:
dict
"""
# Retrieve the configuration settings for the active credit card processor
config = settings.CC_PROCESSOR.get(
settings.CC_PROCESSOR_NAME, {}
)
# Check whether we're in a microsite that overrides our configuration
# If so, find the microsite-specific configuration in the 'microsites'
# sub-key of the normal processor configuration.
config_key = microsite.get_value('cybersource_config_key')
if config_key:
config = config['microsites'][config_key]
return config
......@@ -7,13 +7,29 @@ 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 *
from shoppingcart.processors.helpers import get_processor_config
from shoppingcart.processors.exceptions import (
CCProcessorException,
CCProcessorSignatureException,
CCProcessorDataException,
CCProcessorWrongAmountException
)
from shoppingcart.processors.CyberSource import (
render_purchase_form_html,
process_postpay_callback,
processor_hash,
verify_signatures,
sign,
REASONCODE_MAP,
record_purchase,
get_processor_decline_html,
get_processor_exception_html,
payment_accepted,
)
from mock import patch, Mock
from microsite_configuration import microsite
import mock
TEST_CC_PROCESSOR_NAME = "CyberSource"
TEST_CC_PROCESSOR = {
'CyberSource': {
'SHARED_SECRET': 'secret',
......@@ -43,24 +59,25 @@ def fakemicrosite(name, default=None):
else:
return None
@override_settings(CC_PROCESSOR=TEST_CC_PROCESSOR)
class CyberSourceTests(TestCase):
def setUp(self):
pass
@override_settings(
CC_PROCESSOR_NAME=TEST_CC_PROCESSOR_NAME,
CC_PROCESSOR=TEST_CC_PROCESSOR
)
class CyberSourceTests(TestCase):
def test_override_settings(self):
self.assertEqual(settings.CC_PROCESSOR['CyberSource']['MERCHANT_ID'], 'edx_test')
self.assertEqual(settings.CC_PROCESSOR['CyberSource']['SHARED_SECRET'], 'secret')
def test_microsite_no_override_settings(self):
self.assertEqual(get_cybersource_config()['MERCHANT_ID'], 'edx_test')
self.assertEqual(get_cybersource_config()['SHARED_SECRET'], 'secret')
self.assertEqual(get_processor_config()['MERCHANT_ID'], 'edx_test')
self.assertEqual(get_processor_config()['SHARED_SECRET'], 'secret')
@mock.patch("microsite_configuration.microsite.get_value", fakemicrosite)
@patch("microsite_configuration.microsite.get_value", fakemicrosite)
def test_microsite_override_settings(self):
self.assertEqual(get_cybersource_config()['MERCHANT_ID'], 'edx_test_override')
self.assertEqual(get_cybersource_config()['SHARED_SECRET'], 'secret_override')
self.assertEqual(get_processor_config()['MERCHANT_ID'], 'edx_test_override')
self.assertEqual(get_processor_config()['SHARED_SECRET'], 'secret_override')
def test_hash(self):
"""
......@@ -258,7 +275,7 @@ class CyberSourceTests(TestCase):
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)
render_purchase_form_html(order1)
((template, context), render_kwargs) = render.call_args
self.assertEqual(template, 'shoppingcart/cybersource_form.html')
......
......@@ -3,8 +3,8 @@ Tests for the fake payment page used in acceptance tests.
"""
from django.test import TestCase
from shoppingcart.processors.CyberSource import sign, verify_signatures, \
CCProcessorSignatureException
from shoppingcart.processors.CyberSource2 import sign, verify_signatures
from shoppingcart.processors.exceptions import CCProcessorSignatureException
from shoppingcart.tests.payment_fake import PaymentFakeView
from collections import OrderedDict
......@@ -16,16 +16,19 @@ class PaymentFakeViewTest(TestCase):
"""
CLIENT_POST_PARAMS = OrderedDict([
('match', 'on'),
('course_id', 'edx/999/2013_Spring'),
('amount', '25.00'),
('currency', 'usd'),
('orderPage_transactionType', 'sale'),
('transaction_type', 'sale'),
('orderNumber', '33'),
('access_key', '123456789'),
('merchantID', 'edx'),
('djch', '012345678912'),
('orderPage_version', 2),
('orderPage_serialNumber', '1234567890'),
('profile_id', "00000001"),
('reference_number', 10),
('locale', 'en'),
('signed_date_time', '2014-08-18T13:59:31Z'),
])
def setUp(self):
......@@ -58,7 +61,7 @@ class PaymentFakeViewTest(TestCase):
post_params = sign(self.CLIENT_POST_PARAMS)
# Tamper with the signature
post_params['orderPage_signaturePublic'] = "invalid"
post_params['signature'] = "invalid"
# Simulate a POST request from the payment workflow
# page to the fake payment page.
......
......@@ -72,21 +72,16 @@ def show_cart(request):
total_cost = cart.total_cost
cart_items = cart.orderitem_set.all()
# add the request protocol, domain, and port to the cart object so that any specific
# CC_PROCESSOR implementation can construct callback URLs, if necessary
cart.context = {
'request_domain': '{0}://{1}'.format(
'https' if request.is_secure() else 'http',
request.get_host()
)
callback_url = request.build_absolute_uri(
reverse("shoppingcart.views.postpay_callback")
)
form_html = render_purchase_form_html(cart, callback_url=callback_url)
context = {
'shoppingcart_items': cart_items,
'amount': total_cost,
'form_html': form_html,
}
form_html = render_purchase_form_html(cart)
return render_to_response("shoppingcart/list.html",
{'shoppingcart_items': cart_items,
'amount': total_cost,
'form_html': form_html,
})
return render_to_response("shoppingcart/list.html", context)
@login_required
......
......@@ -24,17 +24,23 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from django.core.exceptions import ObjectDoesNotExist
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, mixed_store_config
from xmodule.modulestore.tests.factories import CourseFactory
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
from student.tests.factories import UserFactory
from student.models import CourseEnrollment
from course_modes.tests.factories import CourseModeFactory
from course_modes.models import CourseMode
from verify_student.views import render_to_response
from verify_student.models import SoftwareSecurePhotoVerification
from reverification.tests.factories import MidcourseReverificationWindowFactory
# Since we don't need any XML course fixtures, use a modulestore configuration
# that disables the XML modulestore.
MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, include_xml=False)
def mock_render_to_response(*args, **kwargs):
return render_to_response(*args, **kwargs)
......@@ -58,8 +64,8 @@ class StartView(TestCase):
self.assertHttpForbidden(self.client.get(self.start_url()))
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
class TestVerifyView(TestCase):
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestVerifyView(ModuleStoreTestCase):
def setUp(self):
self.user = UserFactory.create(username="rusty", password="test")
self.client.login(username="rusty", password="test")
......@@ -93,8 +99,8 @@ class TestVerifyView(TestCase):
self.assertIn("You are upgrading your registration for", response.content)
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
class TestVerifiedView(TestCase):
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestVerifiedView(ModuleStoreTestCase):
"""
Tests for VerifiedView.
"""
......@@ -121,8 +127,8 @@ class TestVerifiedView(TestCase):
self.assertIn('dashboard', response._headers.get('location')[1])
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
class TestReverifyView(TestCase):
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestReverifyView(ModuleStoreTestCase):
"""
Tests for the reverification views
......@@ -167,8 +173,8 @@ class TestReverifyView(TestCase):
self.assertTrue(context['error'])
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
class TestPhotoVerificationResultsCallback(TestCase):
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
"""
Tests for the results_callback view.
"""
......@@ -379,8 +385,8 @@ class TestPhotoVerificationResultsCallback(TestCase):
self.assertIsNotNone(CourseEnrollment.objects.get(course_id=self.course_id))
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
class TestMidCourseReverifyView(TestCase):
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestMidCourseReverifyView(ModuleStoreTestCase):
""" Tests for the midcourse reverification views """
def setUp(self):
self.user = UserFactory.create(username="rusty", password="test")
......@@ -490,8 +496,8 @@ class TestMidCourseReverifyView(TestCase):
self.assertEquals(response.status_code, 200)
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
class TestReverificationBanner(TestCase):
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestReverificationBanner(ModuleStoreTestCase):
""" Tests for the midcourse reverification failed toggle banner off """
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
......@@ -511,3 +517,40 @@ class TestReverificationBanner(TestCase):
self.client.post(reverse('verify_student_toggle_failed_banner_off'))
photo_verification = SoftwareSecurePhotoVerification.objects.get(user=self.user, window=self.window)
self.assertFalse(photo_verification.display)
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestCreateOrder(ModuleStoreTestCase):
""" Tests for the create order view. """
def setUp(self):
""" Create a user and course. """
self.user = UserFactory.create(username="test", password="test")
self.course = CourseFactory.create()
for mode in ('audit', 'honor', 'verified'):
CourseModeFactory(mode_slug=mode, course_id=self.course.id)
self.client.login(username="test", password="test")
def test_create_order_already_verified(self):
# Verify the student so we don't need to submit photos
self._verify_student()
# Create an order
url = reverse('verify_student_create_order')
params = {
'course_id': unicode(self.course.id),
}
response = self.client.post(url, params)
self.assertEqual(response.status_code, 200)
# Verify that the information will be sent to the correct callback URL
# (configured by test settings)
data = json.loads(response.content)
self.assertEqual(data['override_custom_receipt_page'], "http://testserver/shoppingcart/postpay_callback/")
def _verify_student(self):
""" Simulate that the student's identity has already been verified. """
attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
attempt.mark_ready()
attempt.submit()
attempt.approve()
......@@ -25,7 +25,7 @@ from course_modes.models import CourseMode
from student.models import CourseEnrollment
from student.views import reverification_info
from shoppingcart.models import Order, CertificateItem
from shoppingcart.processors.CyberSource import (
from shoppingcart.processors import (
get_signed_purchase_params, get_purchase_endpoint
)
from verify_student.models import (
......@@ -219,7 +219,12 @@ def create_order(request):
enrollment_mode = current_mode.slug
CertificateItem.add_to_order(cart, course_id, amount, enrollment_mode)
params = get_signed_purchase_params(cart)
callback_url = request.build_absolute_uri(
reverse("shoppingcart.views.postpay_callback")
)
params = get_signed_purchase_params(
cart, callback_url=callback_url
)
return HttpResponse(json.dumps(params), content_type="text/json")
......
......@@ -117,20 +117,6 @@ FEATURES['REQUIRE_COURSE_EMAIL_AUTH'] = False
# verification.
FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True
# Configure the payment processor to use the fake processing page
# Since both the fake payment page and the shoppingcart app are using
# the same settings, we can generate this randomly and guarantee
# that they are using the same secret.
RANDOM_SHARED_SECRET = ''.join(
choice(string.letters + string.digits + string.punctuation)
for x in range(250)
)
CC_PROCESSOR['CyberSource']['SHARED_SECRET'] = RANDOM_SHARED_SECRET
CC_PROCESSOR['CyberSource']['MERCHANT_ID'] = "edx"
CC_PROCESSOR['CyberSource']['SERIAL_NUMBER'] = "0123456789012345678901"
CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = "/shoppingcart/payment_fake"
# HACK
# Setting this flag to false causes imports to not load correctly in the lettuce python files
# We do not yet understand why this occurs. Setting this to true is a stopgap measure
......
......@@ -301,6 +301,7 @@ SEGMENT_IO_LMS_KEY = AUTH_TOKENS.get('SEGMENT_IO_LMS_KEY')
if SEGMENT_IO_LMS_KEY:
FEATURES['SEGMENT_IO_LMS'] = ENV_TOKENS.get('SEGMENT_IO_LMS', False)
CC_PROCESSOR_NAME = AUTH_TOKENS.get('CC_PROCESSOR_NAME', CC_PROCESSOR_NAME)
CC_PROCESSOR = AUTH_TOKENS.get('CC_PROCESSOR', CC_PROCESSOR)
SECRET_KEY = AUTH_TOKENS['SECRET_KEY']
......
......@@ -757,7 +757,10 @@ EMBARGO_SITE_REDIRECT_URL = None
##### shoppingcart Payment #####
PAYMENT_SUPPORT_EMAIL = 'payment@example.com'
##### Using cybersource by default #####
CC_PROCESSOR_NAME = 'CyberSource'
CC_PROCESSOR = {
'CyberSource': {
'SHARED_SECRET': '',
......@@ -765,8 +768,15 @@ CC_PROCESSOR = {
'SERIAL_NUMBER': '',
'ORDERPAGE_VERSION': '7',
'PURCHASE_ENDPOINT': '',
},
'CyberSource2': {
"PURCHASE_ENDPOINT": '',
"SECRET_KEY": '',
"ACCESS_KEY": '',
"PROFILE_ID": '',
}
}
# Setting for PAID_COURSE_REGISTRATION, DOES NOT AFFECT VERIFIED STUDENTS
PAID_COURSE_REGISTRATION_CURRENCY = ['usd', '$']
......
......@@ -281,9 +281,13 @@ if SEGMENT_IO_LMS_KEY:
CC_PROCESSOR['CyberSource']['SHARED_SECRET'] = os.environ.get('CYBERSOURCE_SHARED_SECRET', '')
CC_PROCESSOR['CyberSource']['MERCHANT_ID'] = os.environ.get('CYBERSOURCE_MERCHANT_ID', '')
CC_PROCESSOR['CyberSource']['SERIAL_NUMBER'] = os.environ.get('CYBERSOURCE_SERIAL_NUMBER', '')
#CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = os.environ.get('CYBERSOURCE_PURCHASE_ENDPOINT', '')
CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = '/shoppingcart/payment_fake/'
CC_PROCESSOR['CyberSource2']['SECRET_KEY'] = os.environ.get('CYBERSOURCE_SECRET_KEY', '')
CC_PROCESSOR['CyberSource2']['ACCESS_KEY'] = os.environ.get('CYBERSOURCE_ACCESS_KEY', '')
CC_PROCESSOR['CyberSource2']['PROFILE_ID'] = os.environ.get('CYBERSOURCE_PROFILE_ID', '')
CC_PROCESSOR['CyberSource2']['PURCHASE_ENDPOINT'] = '/shoppingcart/payment_fake/'
########################## USER API ##########################
EDX_API_KEY = None
......
......@@ -83,7 +83,9 @@ PIPELINE_SASS_ARGUMENTS = '--debug-info --require {proj_dir}/static/sass/bourbon
FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True
FEATURES['ENABLE_PAYMENT_FAKE'] = True
CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = '/shoppingcart/payment_fake/'
for processor in CC_PROCESSOR.values():
processor['PURCHASE_ENDPOINT'] = '/shoppingcart/payment_fake/'
#####################################################################
# See if the developer has any local overrides.
......
......@@ -220,6 +220,7 @@ OPENID_PROVIDER_TRUSTED_ROOTS = ['*']
###################### Payment ##############################3
# Enable fake payment processing page
FEATURES['ENABLE_PAYMENT_FAKE'] = True
# Configure the payment processor to use the fake processing page
# Since both the fake payment page and the shoppingcart app are using
# the same settings, we can generate this randomly and guarantee
......@@ -231,10 +232,13 @@ RANDOM_SHARED_SECRET = ''.join(
for x in range(250)
)
CC_PROCESSOR['CyberSource']['SHARED_SECRET'] = RANDOM_SHARED_SECRET
CC_PROCESSOR['CyberSource']['MERCHANT_ID'] = "edx"
CC_PROCESSOR['CyberSource']['SERIAL_NUMBER'] = "0123456789012345678901"
CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = "/shoppingcart/payment_fake"
CC_PROCESSOR_NAME = 'CyberSource2'
CC_PROCESSOR['CyberSource2']['SECRET_KEY'] = RANDOM_SHARED_SECRET
CC_PROCESSOR['CyberSource2']['ACCESS_KEY'] = "0123456789012345678901"
CC_PROCESSOR['CyberSource2']['PROFILE_ID'] = "edx"
CC_PROCESSOR['CyberSource2']['PURCHASE_ENDPOINT'] = "/shoppingcart/payment_fake"
FEATURES['STORE_BILLING_INFO'] = True
########################### SYSADMIN DASHBOARD ################################
FEATURES['ENABLE_SYSADMIN_DASHBOARD'] = True
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment