Commit cf9d4277 by Jason Bau Committed by Diana Huang

factor out cybersource processor from cart

parent ab1452cb
......@@ -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
......
### 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,
})
\ No newline at end of file
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)
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 #####################
......
<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
......
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