factor out cybersource processor from cart
This commit is contained in:
@@ -4,6 +4,7 @@ from datetime import datetime
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.contrib.auth.models import User
|
||||
from courseware.courses import course_image_url, get_course_about_section
|
||||
from student.views import course_from_id
|
||||
from student.models import CourseEnrollmentAllowed, CourseEnrollment
|
||||
from statsd import statsd
|
||||
@@ -152,7 +153,7 @@ class PaidCourseRegistration(OrderItem):
|
||||
item.qty = 1
|
||||
item.unit_cost = cost
|
||||
item.line_cost = cost
|
||||
item.line_desc = "Registration for Course {0}".format(course_id)
|
||||
item.line_desc = 'Registration for Course: {0}'.format(get_course_about_section(course, "title"))
|
||||
item.currency = currency
|
||||
item.save()
|
||||
return item
|
||||
|
||||
81
lms/djangoapps/shoppingcart/processors/CyberSource.py
Normal file
81
lms/djangoapps/shoppingcart/processors/CyberSource.py
Normal file
@@ -0,0 +1,81 @@
|
||||
### 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
|
||||
|
||||
import time
|
||||
import hmac
|
||||
import binascii
|
||||
from collections import OrderedDict
|
||||
from hashlib import sha1
|
||||
from django.conf import settings
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
|
||||
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','')
|
||||
|
||||
def hash(value):
|
||||
"""
|
||||
Performs the base64(HMAC_SHA1(key, value)) used by CyberSource Hosted Order Page
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
params['merchantID'] = merchant_id
|
||||
params['orderPage_timestamp'] = int(time.time()*1000)
|
||||
params['orderPage_version'] = orderPage_version
|
||||
params['orderPage_serialNumber'] = serial_number
|
||||
fields = ",".join(params.keys())
|
||||
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
|
||||
|
||||
return params
|
||||
|
||||
def verify(params):
|
||||
"""
|
||||
Verify the signatures accompanying the POST back from Cybersource Hosted Order Page
|
||||
"""
|
||||
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
|
||||
|
||||
def render_purchase_form_html(cart, user):
|
||||
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'
|
||||
params['orderNumber'] = "{0:d}".format(cart.id)
|
||||
params['billTo_email'] = user.email
|
||||
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', {
|
||||
'action': purchase_endpoint,
|
||||
'params': signed_param_dict,
|
||||
})
|
||||
34
lms/djangoapps/shoppingcart/processors/__init__.py
Normal file
34
lms/djangoapps/shoppingcart/processors/__init__.py
Normal file
@@ -0,0 +1,34 @@
|
||||
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'])
|
||||
|
||||
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 render_purchase_form_html(*args, **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
|
||||
"""
|
||||
return module.render_purchase_form_html(*args, **kwargs)
|
||||
@@ -1,26 +1,14 @@
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
import hmac
|
||||
import binascii
|
||||
from hashlib import sha1
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
from .models import *
|
||||
from .processors import verify, render_purchase_form_html
|
||||
|
||||
log = logging.getLogger("shoppingcart")
|
||||
|
||||
shared_secret = settings.CYBERSOURCE.get('SHARED_SECRET','')
|
||||
merchant_id = settings.CYBERSOURCE.get('MERCHANT_ID','')
|
||||
serial_number = settings.CYBERSOURCE.get('SERIAL_NUMBER','')
|
||||
orderPage_version = settings.CYBERSOURCE.get('ORDERPAGE_VERSION','7')
|
||||
|
||||
|
||||
def test(request, course_id):
|
||||
item1 = PaidCourseRegistration(course_id, 200)
|
||||
item1.purchased_callback(request.user.id)
|
||||
@@ -47,26 +35,11 @@ def show_cart(request):
|
||||
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'
|
||||
params['orderNumber'] = "{0:d}".format(cart.id)
|
||||
params['billTo_email'] = request.user.email
|
||||
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 = cybersource_sign(params)
|
||||
form_html = render_purchase_form_html(cart, request.user)
|
||||
return render_to_response("shoppingcart/list.html",
|
||||
{'shoppingcart_items': cart_items,
|
||||
'amount': amount,
|
||||
'params': signed_param_dict,
|
||||
'form_html': form_html,
|
||||
})
|
||||
|
||||
@login_required
|
||||
@@ -89,47 +62,11 @@ def remove_item(request):
|
||||
@csrf_exempt
|
||||
def receipt(request):
|
||||
"""
|
||||
Receives the POST-back from Cybersource and performs the validation and displays a receipt
|
||||
Receives the POST-back from processor and performs the validation and displays a receipt
|
||||
and does some other stuff
|
||||
"""
|
||||
if cybersource_verify(request.POST):
|
||||
if verify(request.POST.dict()):
|
||||
return HttpResponse("Validated")
|
||||
else:
|
||||
return HttpResponse("Not Validated")
|
||||
|
||||
def cybersource_hash(value):
|
||||
"""
|
||||
Performs the base64(HMAC_SHA1(key, value)) used by CyberSource Hosted Order Page
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
def cybersource_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
|
||||
"""
|
||||
params['merchantID'] = merchant_id
|
||||
params['orderPage_timestamp'] = int(time.time()*1000)
|
||||
params['orderPage_version'] = orderPage_version
|
||||
params['orderPage_serialNumber'] = serial_number
|
||||
fields = ",".join(params.keys())
|
||||
values = ",".join(["{0}={1}".format(i,params[i]) for i in params.keys()])
|
||||
fields_sig = cybersource_hash(fields)
|
||||
values += ",signedFieldsPublicSignature=" + fields_sig
|
||||
params['orderPage_signaturePublic'] = cybersource_hash(values)
|
||||
params['orderPage_signedFields'] = fields
|
||||
|
||||
return params
|
||||
|
||||
def cybersource_verify(params):
|
||||
signed_fields = params.get('signedFields', '').split(',')
|
||||
data = ",".join(["{0}={1}".format(k, params.get(k, '')) for k in signed_fields])
|
||||
signed_fields_sig = cybersource_hash(params.get('signedFields', ''))
|
||||
data += ",signedFieldsPublicSignature=" + signed_fields_sig
|
||||
returned_sig = params.get('signedDataPublicSignature','')
|
||||
if not returned_sig:
|
||||
return False
|
||||
return cybersource_hash(data) == returned_sig
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ if SEGMENT_IO_LMS_KEY:
|
||||
MITX_FEATURES['SEGMENT_IO_LMS'] = ENV_TOKENS.get('SEGMENT_IO_LMS', False)
|
||||
|
||||
|
||||
CYBERSOURCE = AUTH_TOKENS.get('CYBERSOURCE', CYBERSOURCE)
|
||||
CC_PROCESSOR = AUTH_TOKENS.get('CC_PROCESSOR', CC_PROCESSOR)
|
||||
|
||||
SECRET_KEY = AUTH_TOKENS['SECRET_KEY']
|
||||
|
||||
|
||||
@@ -431,12 +431,16 @@ ZENDESK_URL = None
|
||||
ZENDESK_USER = None
|
||||
ZENDESK_API_KEY = None
|
||||
|
||||
##### CyberSource Payment parameters #####
|
||||
CYBERSOURCE = {
|
||||
'SHARED_SECRET': '',
|
||||
'MERCHANT_ID' : '',
|
||||
'SERIAL_NUMBER' : '',
|
||||
'ORDERPAGE_VERSION': '7',
|
||||
##### shoppingcart Payment #####
|
||||
##### Using cybersource by default #####
|
||||
CC_PROCESSOR = {
|
||||
'CyberSource' : {
|
||||
'SHARED_SECRET': '',
|
||||
'MERCHANT_ID' : '',
|
||||
'SERIAL_NUMBER' : '',
|
||||
'ORDERPAGE_VERSION': '7',
|
||||
'PURCHASE_ENDPOINT': '',
|
||||
}
|
||||
}
|
||||
|
||||
################################# open ended grading config #####################
|
||||
|
||||
6
lms/templates/shoppingcart/cybersource_form.html
Normal file
6
lms/templates/shoppingcart/cybersource_form.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<form action="${action}" method="post">
|
||||
% for pk, pv in params.iteritems():
|
||||
<input type="hidden" name="${pk}" value="${pv}" />
|
||||
% endfor
|
||||
<input type="submit" value="Check Out" />
|
||||
</form>
|
||||
@@ -25,12 +25,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<form action="https://orderpagetest.ic3.com/hop/orderform.jsp" method="post">
|
||||
% for pk, pv in params.iteritems():
|
||||
<input type="hidden" name="${pk}" value="${pv}" />
|
||||
% endfor
|
||||
<input type="submit" value="Check Out" />
|
||||
</form>
|
||||
${form_html}
|
||||
% else:
|
||||
<p>You have selected no items for purchase.</p>
|
||||
% endif
|
||||
|
||||
Reference in New Issue
Block a user