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 Implementation the CyberSource credit card processor.
### Implementes interface as specified by __init__.py
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 time
import hmac import hmac
import binascii import binascii
...@@ -16,26 +32,11 @@ from django.utils.translation import ugettext as _ ...@@ -16,26 +32,11 @@ from django.utils.translation import ugettext as _
from edxmako.shortcuts import render_to_string from edxmako.shortcuts import render_to_string
from shoppingcart.models import Order from shoppingcart.models import Order
from shoppingcart.processors.exceptions import * from shoppingcart.processors.exceptions import *
from shoppingcart.processors.helpers import get_processor_config
from microsite_configuration import microsite from microsite_configuration import microsite
def get_cybersource_config(): def process_postpay_callback(params, **kwargs):
"""
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):
""" """
The top level call to this module, basically 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" 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): ...@@ -70,7 +71,7 @@ def processor_hash(value):
""" """
Performs the base64(HMAC_SHA1(key, value)) used by CyberSource Hosted Order Page 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) 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 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 ...@@ -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. params needs to be an ordered dict, b/c cybersource documentation states that order is important.
Reverse engineered from PHP version provided by cybersource Reverse engineered from PHP version provided by cybersource
""" """
merchant_id = get_cybersource_config().get('MERCHANT_ID', '') merchant_id = get_processor_config().get('MERCHANT_ID', '')
order_page_version = get_cybersource_config().get('ORDERPAGE_VERSION', '7') order_page_version = get_processor_config().get('ORDERPAGE_VERSION', '7')
serial_number = get_cybersource_config().get('SERIAL_NUMBER', '') serial_number = get_processor_config().get('SERIAL_NUMBER', '')
params['merchantID'] = merchant_id params['merchantID'] = merchant_id
params['orderPage_timestamp'] = int(time.time() * 1000) params['orderPage_timestamp'] = int(time.time() * 1000)
...@@ -115,7 +116,7 @@ def verify_signatures(params, signed_fields_key='signedFields', full_sig_key='si ...@@ -115,7 +116,7 @@ def verify_signatures(params, signed_fields_key='signedFields', full_sig_key='si
raise CCProcessorSignatureException() 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 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): ...@@ -124,9 +125,11 @@ def render_purchase_form_html(cart):
'params': get_signed_purchase_params(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)) return sign(get_purchase_params(cart))
def get_purchase_params(cart): def get_purchase_params(cart):
total_cost = cart.total_cost total_cost = cart.total_cost
amount = "{0:0.2f}".format(total_cost) amount = "{0:0.2f}".format(total_cost)
...@@ -139,8 +142,10 @@ def get_purchase_params(cart): ...@@ -139,8 +142,10 @@ def get_purchase_params(cart):
return params return params
def get_purchase_endpoint(): def get_purchase_endpoint():
return get_cybersource_config().get('PURCHASE_ENDPOINT', '') return get_processor_config().get('PURCHASE_ENDPOINT', '')
def payment_accepted(params): 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 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. return PROCESSOR_MODULE.render_purchase_form_html(cart, **kwargs)
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 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. return PROCESSOR_MODULE.process_postpay_callback(params, **kwargs)
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. def get_purchase_endpoint():
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 Return the URL of the current payment processor's endpoint.
purchased_callbacks of the cart items.
If unsuccessful this function should not have those side effects but should try to figure out why and Returns:
return a helpful-enough error message in error_html. 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 ...@@ -7,13 +7,29 @@ from django.test.utils import override_settings
from django.conf import settings from django.conf import settings
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from shoppingcart.models import Order, OrderItem from shoppingcart.models import Order, OrderItem
from shoppingcart.processors.CyberSource import * from shoppingcart.processors.helpers import get_processor_config
from shoppingcart.processors.exceptions import * 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 mock import patch, Mock
from microsite_configuration import microsite
import mock
TEST_CC_PROCESSOR_NAME = "CyberSource"
TEST_CC_PROCESSOR = { TEST_CC_PROCESSOR = {
'CyberSource': { 'CyberSource': {
'SHARED_SECRET': 'secret', 'SHARED_SECRET': 'secret',
...@@ -43,24 +59,25 @@ def fakemicrosite(name, default=None): ...@@ -43,24 +59,25 @@ def fakemicrosite(name, default=None):
else: else:
return None return None
@override_settings(CC_PROCESSOR=TEST_CC_PROCESSOR)
class CyberSourceTests(TestCase):
def setUp(self): @override_settings(
pass CC_PROCESSOR_NAME=TEST_CC_PROCESSOR_NAME,
CC_PROCESSOR=TEST_CC_PROCESSOR
)
class CyberSourceTests(TestCase):
def test_override_settings(self): def test_override_settings(self):
self.assertEqual(settings.CC_PROCESSOR['CyberSource']['MERCHANT_ID'], 'edx_test') self.assertEqual(settings.CC_PROCESSOR['CyberSource']['MERCHANT_ID'], 'edx_test')
self.assertEqual(settings.CC_PROCESSOR['CyberSource']['SHARED_SECRET'], 'secret') self.assertEqual(settings.CC_PROCESSOR['CyberSource']['SHARED_SECRET'], 'secret')
def test_microsite_no_override_settings(self): def test_microsite_no_override_settings(self):
self.assertEqual(get_cybersource_config()['MERCHANT_ID'], 'edx_test') self.assertEqual(get_processor_config()['MERCHANT_ID'], 'edx_test')
self.assertEqual(get_cybersource_config()['SHARED_SECRET'], 'secret') 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): def test_microsite_override_settings(self):
self.assertEqual(get_cybersource_config()['MERCHANT_ID'], 'edx_test_override') self.assertEqual(get_processor_config()['MERCHANT_ID'], 'edx_test_override')
self.assertEqual(get_cybersource_config()['SHARED_SECRET'], 'secret_override') self.assertEqual(get_processor_config()['SHARED_SECRET'], 'secret_override')
def test_hash(self): def test_hash(self):
""" """
...@@ -258,7 +275,7 @@ class CyberSourceTests(TestCase): ...@@ -258,7 +275,7 @@ class CyberSourceTests(TestCase):
order1 = Order.get_cart_for_user(student1) order1 = Order.get_cart_for_user(student1)
item1 = OrderItem(order=order1, user=student1, unit_cost=1.0, line_cost=1.0) item1 = OrderItem(order=order1, user=student1, unit_cost=1.0, line_cost=1.0)
item1.save() item1.save()
html = render_purchase_form_html(order1) render_purchase_form_html(order1)
((template, context), render_kwargs) = render.call_args ((template, context), render_kwargs) = render.call_args
self.assertEqual(template, 'shoppingcart/cybersource_form.html') self.assertEqual(template, 'shoppingcart/cybersource_form.html')
......
...@@ -3,8 +3,8 @@ Tests for the fake payment page used in acceptance tests. ...@@ -3,8 +3,8 @@ Tests for the fake payment page used in acceptance tests.
""" """
from django.test import TestCase from django.test import TestCase
from shoppingcart.processors.CyberSource import sign, verify_signatures, \ from shoppingcart.processors.CyberSource2 import sign, verify_signatures
CCProcessorSignatureException from shoppingcart.processors.exceptions import CCProcessorSignatureException
from shoppingcart.tests.payment_fake import PaymentFakeView from shoppingcart.tests.payment_fake import PaymentFakeView
from collections import OrderedDict from collections import OrderedDict
...@@ -16,16 +16,19 @@ class PaymentFakeViewTest(TestCase): ...@@ -16,16 +16,19 @@ class PaymentFakeViewTest(TestCase):
""" """
CLIENT_POST_PARAMS = OrderedDict([ CLIENT_POST_PARAMS = OrderedDict([
('match', 'on'),
('course_id', 'edx/999/2013_Spring'),
('amount', '25.00'), ('amount', '25.00'),
('currency', 'usd'), ('currency', 'usd'),
('orderPage_transactionType', 'sale'), ('transaction_type', 'sale'),
('orderNumber', '33'), ('orderNumber', '33'),
('access_key', '123456789'),
('merchantID', 'edx'), ('merchantID', 'edx'),
('djch', '012345678912'), ('djch', '012345678912'),
('orderPage_version', 2), ('orderPage_version', 2),
('orderPage_serialNumber', '1234567890'), ('orderPage_serialNumber', '1234567890'),
('profile_id', "00000001"),
('reference_number', 10),
('locale', 'en'),
('signed_date_time', '2014-08-18T13:59:31Z'),
]) ])
def setUp(self): def setUp(self):
...@@ -58,7 +61,7 @@ class PaymentFakeViewTest(TestCase): ...@@ -58,7 +61,7 @@ class PaymentFakeViewTest(TestCase):
post_params = sign(self.CLIENT_POST_PARAMS) post_params = sign(self.CLIENT_POST_PARAMS)
# Tamper with the signature # Tamper with the signature
post_params['orderPage_signaturePublic'] = "invalid" post_params['signature'] = "invalid"
# Simulate a POST request from the payment workflow # Simulate a POST request from the payment workflow
# page to the fake payment page. # page to the fake payment page.
......
...@@ -72,21 +72,16 @@ def show_cart(request): ...@@ -72,21 +72,16 @@ def show_cart(request):
total_cost = cart.total_cost total_cost = cart.total_cost
cart_items = cart.orderitem_set.all() cart_items = cart.orderitem_set.all()
# add the request protocol, domain, and port to the cart object so that any specific callback_url = request.build_absolute_uri(
# CC_PROCESSOR implementation can construct callback URLs, if necessary reverse("shoppingcart.views.postpay_callback")
cart.context = { )
'request_domain': '{0}://{1}'.format( form_html = render_purchase_form_html(cart, callback_url=callback_url)
'https' if request.is_secure() else 'http', context = {
request.get_host() 'shoppingcart_items': cart_items,
) 'amount': total_cost,
'form_html': form_html,
} }
return render_to_response("shoppingcart/list.html", context)
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,
})
@login_required @login_required
......
...@@ -24,17 +24,23 @@ from django.conf import settings ...@@ -24,17 +24,23 @@ from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import ObjectDoesNotExist 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 xmodule.modulestore.tests.factories import CourseFactory
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from student.models import CourseEnrollment from student.models import CourseEnrollment
from course_modes.tests.factories import CourseModeFactory
from course_modes.models import CourseMode from course_modes.models import CourseMode
from verify_student.views import render_to_response from verify_student.views import render_to_response
from verify_student.models import SoftwareSecurePhotoVerification from verify_student.models import SoftwareSecurePhotoVerification
from reverification.tests.factories import MidcourseReverificationWindowFactory 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): def mock_render_to_response(*args, **kwargs):
return render_to_response(*args, **kwargs) return render_to_response(*args, **kwargs)
...@@ -58,8 +64,8 @@ class StartView(TestCase): ...@@ -58,8 +64,8 @@ class StartView(TestCase):
self.assertHttpForbidden(self.client.get(self.start_url())) self.assertHttpForbidden(self.client.get(self.start_url()))
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) @override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestVerifyView(TestCase): class TestVerifyView(ModuleStoreTestCase):
def setUp(self): def setUp(self):
self.user = UserFactory.create(username="rusty", password="test") self.user = UserFactory.create(username="rusty", password="test")
self.client.login(username="rusty", password="test") self.client.login(username="rusty", password="test")
...@@ -93,8 +99,8 @@ class TestVerifyView(TestCase): ...@@ -93,8 +99,8 @@ class TestVerifyView(TestCase):
self.assertIn("You are upgrading your registration for", response.content) self.assertIn("You are upgrading your registration for", response.content)
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) @override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestVerifiedView(TestCase): class TestVerifiedView(ModuleStoreTestCase):
""" """
Tests for VerifiedView. Tests for VerifiedView.
""" """
...@@ -121,8 +127,8 @@ class TestVerifiedView(TestCase): ...@@ -121,8 +127,8 @@ class TestVerifiedView(TestCase):
self.assertIn('dashboard', response._headers.get('location')[1]) self.assertIn('dashboard', response._headers.get('location')[1])
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) @override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestReverifyView(TestCase): class TestReverifyView(ModuleStoreTestCase):
""" """
Tests for the reverification views Tests for the reverification views
...@@ -167,8 +173,8 @@ class TestReverifyView(TestCase): ...@@ -167,8 +173,8 @@ class TestReverifyView(TestCase):
self.assertTrue(context['error']) self.assertTrue(context['error'])
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) @override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestPhotoVerificationResultsCallback(TestCase): class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
""" """
Tests for the results_callback view. Tests for the results_callback view.
""" """
...@@ -379,8 +385,8 @@ class TestPhotoVerificationResultsCallback(TestCase): ...@@ -379,8 +385,8 @@ class TestPhotoVerificationResultsCallback(TestCase):
self.assertIsNotNone(CourseEnrollment.objects.get(course_id=self.course_id)) self.assertIsNotNone(CourseEnrollment.objects.get(course_id=self.course_id))
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) @override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestMidCourseReverifyView(TestCase): class TestMidCourseReverifyView(ModuleStoreTestCase):
""" Tests for the midcourse reverification views """ """ Tests for the midcourse reverification views """
def setUp(self): def setUp(self):
self.user = UserFactory.create(username="rusty", password="test") self.user = UserFactory.create(username="rusty", password="test")
...@@ -490,8 +496,8 @@ class TestMidCourseReverifyView(TestCase): ...@@ -490,8 +496,8 @@ class TestMidCourseReverifyView(TestCase):
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) @override_settings(MODULESTORE=MODULESTORE_CONFIG)
class TestReverificationBanner(TestCase): class TestReverificationBanner(ModuleStoreTestCase):
""" Tests for the midcourse reverification failed toggle banner off """ """ Tests for the midcourse reverification failed toggle banner off """
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True}) @patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
...@@ -511,3 +517,40 @@ class TestReverificationBanner(TestCase): ...@@ -511,3 +517,40 @@ class TestReverificationBanner(TestCase):
self.client.post(reverse('verify_student_toggle_failed_banner_off')) self.client.post(reverse('verify_student_toggle_failed_banner_off'))
photo_verification = SoftwareSecurePhotoVerification.objects.get(user=self.user, window=self.window) photo_verification = SoftwareSecurePhotoVerification.objects.get(user=self.user, window=self.window)
self.assertFalse(photo_verification.display) 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 ...@@ -25,7 +25,7 @@ from course_modes.models import CourseMode
from student.models import CourseEnrollment from student.models import CourseEnrollment
from student.views import reverification_info from student.views import reverification_info
from shoppingcart.models import Order, CertificateItem from shoppingcart.models import Order, CertificateItem
from shoppingcart.processors.CyberSource import ( from shoppingcart.processors import (
get_signed_purchase_params, get_purchase_endpoint get_signed_purchase_params, get_purchase_endpoint
) )
from verify_student.models import ( from verify_student.models import (
...@@ -219,7 +219,12 @@ def create_order(request): ...@@ -219,7 +219,12 @@ def create_order(request):
enrollment_mode = current_mode.slug enrollment_mode = current_mode.slug
CertificateItem.add_to_order(cart, course_id, amount, enrollment_mode) 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") return HttpResponse(json.dumps(params), content_type="text/json")
......
...@@ -117,20 +117,6 @@ FEATURES['REQUIRE_COURSE_EMAIL_AUTH'] = False ...@@ -117,20 +117,6 @@ FEATURES['REQUIRE_COURSE_EMAIL_AUTH'] = False
# verification. # verification.
FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True 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 # HACK
# Setting this flag to false causes imports to not load correctly in the lettuce python files # 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 # 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') ...@@ -301,6 +301,7 @@ SEGMENT_IO_LMS_KEY = AUTH_TOKENS.get('SEGMENT_IO_LMS_KEY')
if SEGMENT_IO_LMS_KEY: if SEGMENT_IO_LMS_KEY:
FEATURES['SEGMENT_IO_LMS'] = ENV_TOKENS.get('SEGMENT_IO_LMS', False) 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) CC_PROCESSOR = AUTH_TOKENS.get('CC_PROCESSOR', CC_PROCESSOR)
SECRET_KEY = AUTH_TOKENS['SECRET_KEY'] SECRET_KEY = AUTH_TOKENS['SECRET_KEY']
......
...@@ -757,7 +757,10 @@ EMBARGO_SITE_REDIRECT_URL = None ...@@ -757,7 +757,10 @@ EMBARGO_SITE_REDIRECT_URL = None
##### shoppingcart Payment ##### ##### shoppingcart Payment #####
PAYMENT_SUPPORT_EMAIL = 'payment@example.com' PAYMENT_SUPPORT_EMAIL = 'payment@example.com'
##### Using cybersource by default ##### ##### Using cybersource by default #####
CC_PROCESSOR_NAME = 'CyberSource'
CC_PROCESSOR = { CC_PROCESSOR = {
'CyberSource': { 'CyberSource': {
'SHARED_SECRET': '', 'SHARED_SECRET': '',
...@@ -765,8 +768,15 @@ CC_PROCESSOR = { ...@@ -765,8 +768,15 @@ CC_PROCESSOR = {
'SERIAL_NUMBER': '', 'SERIAL_NUMBER': '',
'ORDERPAGE_VERSION': '7', 'ORDERPAGE_VERSION': '7',
'PURCHASE_ENDPOINT': '', 'PURCHASE_ENDPOINT': '',
},
'CyberSource2': {
"PURCHASE_ENDPOINT": '',
"SECRET_KEY": '',
"ACCESS_KEY": '',
"PROFILE_ID": '',
} }
} }
# Setting for PAID_COURSE_REGISTRATION, DOES NOT AFFECT VERIFIED STUDENTS # Setting for PAID_COURSE_REGISTRATION, DOES NOT AFFECT VERIFIED STUDENTS
PAID_COURSE_REGISTRATION_CURRENCY = ['usd', '$'] PAID_COURSE_REGISTRATION_CURRENCY = ['usd', '$']
......
...@@ -281,9 +281,13 @@ if SEGMENT_IO_LMS_KEY: ...@@ -281,9 +281,13 @@ if SEGMENT_IO_LMS_KEY:
CC_PROCESSOR['CyberSource']['SHARED_SECRET'] = os.environ.get('CYBERSOURCE_SHARED_SECRET', '') 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']['MERCHANT_ID'] = os.environ.get('CYBERSOURCE_MERCHANT_ID', '')
CC_PROCESSOR['CyberSource']['SERIAL_NUMBER'] = os.environ.get('CYBERSOURCE_SERIAL_NUMBER', '') 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['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 ########################## ########################## USER API ##########################
EDX_API_KEY = None EDX_API_KEY = None
......
...@@ -83,7 +83,9 @@ PIPELINE_SASS_ARGUMENTS = '--debug-info --require {proj_dir}/static/sass/bourbon ...@@ -83,7 +83,9 @@ PIPELINE_SASS_ARGUMENTS = '--debug-info --require {proj_dir}/static/sass/bourbon
FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True
FEATURES['ENABLE_PAYMENT_FAKE'] = 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. # See if the developer has any local overrides.
......
...@@ -220,6 +220,7 @@ OPENID_PROVIDER_TRUSTED_ROOTS = ['*'] ...@@ -220,6 +220,7 @@ OPENID_PROVIDER_TRUSTED_ROOTS = ['*']
###################### Payment ##############################3 ###################### Payment ##############################3
# Enable fake payment processing page # Enable fake payment processing page
FEATURES['ENABLE_PAYMENT_FAKE'] = True FEATURES['ENABLE_PAYMENT_FAKE'] = True
# Configure the payment processor to use the fake processing page # Configure the payment processor to use the fake processing page
# Since both the fake payment page and the shoppingcart app are using # Since both the fake payment page and the shoppingcart app are using
# the same settings, we can generate this randomly and guarantee # the same settings, we can generate this randomly and guarantee
...@@ -231,10 +232,13 @@ RANDOM_SHARED_SECRET = ''.join( ...@@ -231,10 +232,13 @@ RANDOM_SHARED_SECRET = ''.join(
for x in range(250) for x in range(250)
) )
CC_PROCESSOR['CyberSource']['SHARED_SECRET'] = RANDOM_SHARED_SECRET CC_PROCESSOR_NAME = 'CyberSource2'
CC_PROCESSOR['CyberSource']['MERCHANT_ID'] = "edx" CC_PROCESSOR['CyberSource2']['SECRET_KEY'] = RANDOM_SHARED_SECRET
CC_PROCESSOR['CyberSource']['SERIAL_NUMBER'] = "0123456789012345678901" CC_PROCESSOR['CyberSource2']['ACCESS_KEY'] = "0123456789012345678901"
CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = "/shoppingcart/payment_fake" CC_PROCESSOR['CyberSource2']['PROFILE_ID'] = "edx"
CC_PROCESSOR['CyberSource2']['PURCHASE_ENDPOINT'] = "/shoppingcart/payment_fake"
FEATURES['STORE_BILLING_INFO'] = True
########################### SYSADMIN DASHBOARD ################################ ########################### SYSADMIN DASHBOARD ################################
FEATURES['ENABLE_SYSADMIN_DASHBOARD'] = True 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