about page changes, refactor processor reply handling
This commit is contained in:
@@ -338,6 +338,7 @@ class CourseFields(object):
|
|||||||
show_timezone = Boolean(help="True if timezones should be shown on dates in the courseware", scope=Scope.settings, default=True)
|
show_timezone = Boolean(help="True if timezones should be shown on dates in the courseware", scope=Scope.settings, default=True)
|
||||||
enrollment_domain = String(help="External login method associated with user accounts allowed to register in course",
|
enrollment_domain = String(help="External login method associated with user accounts allowed to register in course",
|
||||||
scope=Scope.settings)
|
scope=Scope.settings)
|
||||||
|
enrollment_cost = Dict(scope=Scope.settings, default={'currency':'usd', 'cost':0})
|
||||||
|
|
||||||
# An extra property is used rather than the wiki_slug/number because
|
# An extra property is used rather than the wiki_slug/number because
|
||||||
# there are courses that change the number for different runs. This allows
|
# there are courses that change the number for different runs. This allows
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
xdescribe("A jsinput has:", function () {
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
$('#fixture').remove();
|
|
||||||
$.ajax({
|
|
||||||
async: false,
|
|
||||||
url: 'mainfixture.html',
|
|
||||||
success: function(data) {
|
|
||||||
$('body').append($(data));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe("The jsinput constructor", function(){
|
|
||||||
|
|
||||||
var iframe1 = $(document).find('iframe')[0];
|
|
||||||
|
|
||||||
var testJsElem = jsinputConstructor({
|
|
||||||
id: 1,
|
|
||||||
elem: iframe1,
|
|
||||||
passive: false
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Returns an object", function(){
|
|
||||||
expect(typeof(testJsElem)).toEqual('object');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Adds the object to the jsinput array", function() {
|
|
||||||
expect(jsinput.exists(1)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("The returned object", function() {
|
|
||||||
|
|
||||||
it("Has a public 'update' method", function(){
|
|
||||||
expect(testJsElem.update).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Returns an 'update' that is idempotent", function(){
|
|
||||||
var orig = testJsElem.update();
|
|
||||||
for (var i = 0; i++; i < 5) {
|
|
||||||
expect(testJsElem.update()).toEqual(orig);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Changes the parent's inputfield", function() {
|
|
||||||
testJsElem.update();
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe("The walkDOM functions", function() {
|
|
||||||
|
|
||||||
walkDOM();
|
|
||||||
|
|
||||||
it("Creates (at least) one object per iframe", function() {
|
|
||||||
jsinput.arr.length >= 2;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Does not create multiple objects with the same id", function() {
|
|
||||||
while (jsinput.arr.length > 0) {
|
|
||||||
var elem = jsinput.arr.pop();
|
|
||||||
expect(jsinput.exists(elem.id)).toBe(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
@@ -123,11 +123,13 @@ class OrderItem(models.Model):
|
|||||||
|
|
||||||
Unfortunately the QuerySet used determines the class to be OrderItem, and not its most specific
|
Unfortunately the QuerySet used determines the class to be OrderItem, and not its most specific
|
||||||
subclasses. That means this parent class implementation of purchased_callback needs to act as
|
subclasses. That means this parent class implementation of purchased_callback needs to act as
|
||||||
a dispatcher to call the callback the proper subclasses, and as such it needs to know about all subclasses.
|
a dispatcher to call the callback the proper subclasses, and as such it needs to know about all
|
||||||
So please add
|
possible subclasses.
|
||||||
|
So keep ORDER_ITEM_SUBTYPES up-to-date
|
||||||
"""
|
"""
|
||||||
for classname, lc_classname in ORDER_ITEM_SUBTYPES:
|
for cls, lc_classname in ORDER_ITEM_SUBTYPES.iteritems():
|
||||||
try:
|
try:
|
||||||
|
#Uses https://docs.djangoproject.com/en/1.4/topics/db/models/#multi-table-inheritance to test subclass
|
||||||
sub_instance = getattr(self,lc_classname)
|
sub_instance = getattr(self,lc_classname)
|
||||||
sub_instance.purchased_callback()
|
sub_instance.purchased_callback()
|
||||||
except (ObjectDoesNotExist, AttributeError):
|
except (ObjectDoesNotExist, AttributeError):
|
||||||
@@ -135,13 +137,18 @@ class OrderItem(models.Model):
|
|||||||
.format(lc_classname))
|
.format(lc_classname))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Each entry is a tuple of ('ModelName', 'lower_case_model_name')
|
def is_of_subtype(self, cls):
|
||||||
# See https://docs.djangoproject.com/en/1.4/topics/db/models/#multi-table-inheritance for
|
"""
|
||||||
# PLEASE KEEP THIS LIST UP_TO_DATE WITH THE SUBCLASSES OF OrderItem
|
Checks if self is also a type of cls, in addition to being an OrderItem
|
||||||
ORDER_ITEM_SUBTYPES = [
|
Uses https://docs.djangoproject.com/en/1.4/topics/db/models/#multi-table-inheritance to test for subclass
|
||||||
('PaidCourseRegistration', 'paidcourseregistration')
|
"""
|
||||||
]
|
if cls not in ORDER_ITEM_SUBTYPES:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
getattr(self, ORDER_ITEM_SUBTYPES[cls])
|
||||||
|
return True
|
||||||
|
except (ObjectDoesNotExist, AttributeError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class PaidCourseRegistration(OrderItem):
|
class PaidCourseRegistration(OrderItem):
|
||||||
@@ -151,7 +158,16 @@ class PaidCourseRegistration(OrderItem):
|
|||||||
course_id = models.CharField(max_length=128, db_index=True)
|
course_id = models.CharField(max_length=128, db_index=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_to_order(cls, order, course_id, cost, currency='usd'):
|
def part_of_order(cls, order, course_id):
|
||||||
|
"""
|
||||||
|
Is the course defined by course_id in the order?
|
||||||
|
"""
|
||||||
|
return course_id in [item.paidcourseregistration.course_id
|
||||||
|
for item in order.orderitem_set.all()
|
||||||
|
if item.is_of_subtype(PaidCourseRegistration)]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_to_order(cls, order, course_id, cost=None, currency=None):
|
||||||
"""
|
"""
|
||||||
A standardized way to create these objects, with sensible defaults filled in.
|
A standardized way to create these objects, with sensible defaults filled in.
|
||||||
Will update the cost if called on an order that already carries the course.
|
Will update the cost if called on an order that already carries the course.
|
||||||
@@ -164,6 +180,10 @@ class PaidCourseRegistration(OrderItem):
|
|||||||
item, created = cls.objects.get_or_create(order=order, user=order.user, course_id=course_id)
|
item, created = cls.objects.get_or_create(order=order, user=order.user, course_id=course_id)
|
||||||
item.status = order.status
|
item.status = order.status
|
||||||
item.qty = 1
|
item.qty = 1
|
||||||
|
if cost is None:
|
||||||
|
cost = course.enrollment_cost['cost']
|
||||||
|
if currency is None:
|
||||||
|
currency = course.enrollment_cost['currency']
|
||||||
item.unit_cost = cost
|
item.unit_cost = cost
|
||||||
item.line_cost = cost
|
item.line_cost = cost
|
||||||
item.line_desc = 'Registration for Course: {0}'.format(get_course_about_section(course, "title"))
|
item.line_desc = 'Registration for Course: {0}'.format(get_course_about_section(course, "title"))
|
||||||
@@ -182,9 +202,6 @@ class PaidCourseRegistration(OrderItem):
|
|||||||
# throw errors if it doesn't
|
# throw errors if it doesn't
|
||||||
# use get_or_create here to gracefully handle case where the user is already enrolled in the course, for
|
# use get_or_create here to gracefully handle case where the user is already enrolled in the course, for
|
||||||
# whatever reason.
|
# whatever reason.
|
||||||
# Don't really need to create CourseEnrollmentAllowed object, but doing it for bookkeeping and consistency
|
|
||||||
# with rest of codebase.
|
|
||||||
CourseEnrollmentAllowed.objects.get_or_create(email=self.user.email, course_id=self.course_id, auto_enroll=True)
|
|
||||||
CourseEnrollment.objects.get_or_create(user=self.user, course_id=self.course_id)
|
CourseEnrollment.objects.get_or_create(user=self.user, course_id=self.course_id)
|
||||||
|
|
||||||
log.info("Enrolled {0} in paid course {1}, paid ${2}".format(self.user.email, self.course_id, self.line_cost))
|
log.info("Enrolled {0} in paid course {1}, paid ${2}".format(self.user.email, self.course_id, self.line_cost))
|
||||||
@@ -193,3 +210,11 @@ class PaidCourseRegistration(OrderItem):
|
|||||||
tags=["org:{0}".format(org),
|
tags=["org:{0}".format(org),
|
||||||
"course:{0}".format(course_num),
|
"course:{0}".format(course_num),
|
||||||
"run:{0}".format(run)])
|
"run:{0}".format(run)])
|
||||||
|
|
||||||
|
|
||||||
|
# Each entry is a dictionary of ModelName: 'lower_case_model_name'
|
||||||
|
# See https://docs.djangoproject.com/en/1.4/topics/db/models/#multi-table-inheritance for
|
||||||
|
# PLEASE KEEP THIS LIST UP_TO_DATE WITH THE SUBCLASSES OF OrderItem
|
||||||
|
ORDER_ITEM_SUBTYPES = {
|
||||||
|
PaidCourseRegistration: 'paidcourseregistration',
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ from django.conf import settings
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from mitxmako.shortcuts import render_to_string
|
from mitxmako.shortcuts import render_to_string
|
||||||
from shoppingcart.models import Order
|
from shoppingcart.models import Order
|
||||||
from .exceptions import CCProcessorDataException, CCProcessorWrongAmountException
|
from .exceptions import CCProcessorException, CCProcessorDataException, CCProcessorWrongAmountException
|
||||||
|
|
||||||
shared_secret = settings.CC_PROCESSOR['CyberSource'].get('SHARED_SECRET','')
|
shared_secret = settings.CC_PROCESSOR['CyberSource'].get('SHARED_SECRET','')
|
||||||
merchant_id = settings.CC_PROCESSOR['CyberSource'].get('MERCHANT_ID','')
|
merchant_id = settings.CC_PROCESSOR['CyberSource'].get('MERCHANT_ID','')
|
||||||
@@ -21,6 +21,42 @@ serial_number = settings.CC_PROCESSOR['CyberSource'].get('SERIAL_NUMBER','')
|
|||||||
orderPage_version = settings.CC_PROCESSOR['CyberSource'].get('ORDERPAGE_VERSION','7')
|
orderPage_version = settings.CC_PROCESSOR['CyberSource'].get('ORDERPAGE_VERSION','7')
|
||||||
purchase_endpoint = settings.CC_PROCESSOR['CyberSource'].get('PURCHASE_ENDPOINT','')
|
purchase_endpoint = settings.CC_PROCESSOR['CyberSource'].get('PURCHASE_ENDPOINT','')
|
||||||
|
|
||||||
|
def process_postpay_callback(request):
|
||||||
|
"""
|
||||||
|
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"
|
||||||
|
on the external Hosted Order 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.
|
||||||
|
"""
|
||||||
|
params = request.POST.dict()
|
||||||
|
if verify_signatures(params):
|
||||||
|
try:
|
||||||
|
result = payment_accepted(params)
|
||||||
|
if result['accepted']:
|
||||||
|
# SUCCESS CASE first, rest are some sort of oddity
|
||||||
|
record_purchase(params, result['order'])
|
||||||
|
return {'success': True,
|
||||||
|
'order': result['order'],
|
||||||
|
'error_html': ''}
|
||||||
|
else:
|
||||||
|
return {'success': False,
|
||||||
|
'order': result['order'],
|
||||||
|
'error_html': get_processor_error_html(params)}
|
||||||
|
except CCProcessorException as e:
|
||||||
|
return {'success': False,
|
||||||
|
'order': None, #due to exception we may not have the order
|
||||||
|
'error_html': get_exception_html(params, e)}
|
||||||
|
else:
|
||||||
|
return {'success': False,
|
||||||
|
'order': None,
|
||||||
|
'error_html': get_signature_error_html(params)}
|
||||||
|
|
||||||
|
|
||||||
def hash(value):
|
def 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
|
||||||
@@ -48,7 +84,7 @@ def sign(params):
|
|||||||
return params
|
return params
|
||||||
|
|
||||||
|
|
||||||
def verify(params):
|
def verify_signatures(params):
|
||||||
"""
|
"""
|
||||||
Verify the signatures accompanying the POST back from Cybersource Hosted Order Page
|
Verify the signatures accompanying the POST back from Cybersource Hosted Order Page
|
||||||
"""
|
"""
|
||||||
@@ -161,6 +197,18 @@ def record_purchase(params, order):
|
|||||||
processor_reply_dump=json.dumps(params)
|
processor_reply_dump=json.dumps(params)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_processor_error_html(params):
|
||||||
|
"""Have to parse through the error codes for all the other cases"""
|
||||||
|
return "<p>ERROR!</p>"
|
||||||
|
|
||||||
|
def get_exception_html(params, exp):
|
||||||
|
"""Return error HTML associated with exception"""
|
||||||
|
return "<p>EXCEPTION!</p>"
|
||||||
|
|
||||||
|
def get_signature_error_html(params):
|
||||||
|
"""Return error HTML associated with signature failure"""
|
||||||
|
return "<p>EXCEPTION!</p>"
|
||||||
|
|
||||||
|
|
||||||
CARDTYPE_MAP = defaultdict(lambda:"UNKNOWN")
|
CARDTYPE_MAP = defaultdict(lambda:"UNKNOWN")
|
||||||
CARDTYPE_MAP.update(
|
CARDTYPE_MAP.update(
|
||||||
|
|||||||
@@ -8,8 +8,32 @@ module = __import__('shoppingcart.processors.' + processor_name,
|
|||||||
'render_purchase_form_html'
|
'render_purchase_form_html'
|
||||||
'payment_accepted',
|
'payment_accepted',
|
||||||
'record_purchase',
|
'record_purchase',
|
||||||
|
'process_postpay_callback',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
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 module.render_purchase_form_html(*args, **kwargs)
|
||||||
|
|
||||||
|
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 module.process_postpay_callback(*args, **kwargs)
|
||||||
|
|
||||||
def sign(*args, **kwargs):
|
def sign(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Given a dict (or OrderedDict) of parameters to send to the
|
Given a dict (or OrderedDict) of parameters to send to the
|
||||||
@@ -30,14 +54,6 @@ def verify(*args, **kwargs):
|
|||||||
"""
|
"""
|
||||||
return module.sign(*args, **kwargs)
|
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)
|
|
||||||
|
|
||||||
def payment_accepted(*args, **kwargs):
|
def payment_accepted(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Given params returned by the CC processor, check that processor has accepted the payment
|
Given params returned by the CC processor, check that processor has accepted the payment
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ class CCProcessorException(PaymentException):
|
|||||||
class CCProcessorDataException(CCProcessorException):
|
class CCProcessorDataException(CCProcessorException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class CCProcessorWrongAmountException(PaymentException):
|
class CCProcessorWrongAmountException(CCProcessorException):
|
||||||
pass
|
pass
|
||||||
@@ -6,7 +6,6 @@ urlpatterns = patterns('shoppingcart.views', # nopep8
|
|||||||
url(r'^add/course/(?P<course_id>[^/]+/[^/]+/[^/]+)/$','add_course_to_cart'),
|
url(r'^add/course/(?P<course_id>[^/]+/[^/]+/[^/]+)/$','add_course_to_cart'),
|
||||||
url(r'^clear/$','clear_cart'),
|
url(r'^clear/$','clear_cart'),
|
||||||
url(r'^remove_item/$', 'remove_item'),
|
url(r'^remove_item/$', 'remove_item'),
|
||||||
url(r'^purchased/$', 'purchased'),
|
url(r'^postpay_callback/$', 'postpay_callback'), #Both the ~accept and ~reject callback pages are handled here
|
||||||
url(r'^postpay_accept_callback/$', 'postpay_accept_callback'),
|
|
||||||
url(r'^receipt/(?P<ordernum>[0-9]*)/$', 'show_receipt'),
|
url(r'^receipt/(?P<ordernum>[0-9]*)/$', 'show_receipt'),
|
||||||
)
|
)
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound, HttpResponseForbidden, Http404
|
||||||
from django.http import HttpResponse, HttpResponseRedirect, Http404
|
from django.utils.translation import ugettext as _
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from student.models import CourseEnrollment
|
||||||
|
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||||
from mitxmako.shortcuts import render_to_response
|
from mitxmako.shortcuts import render_to_response
|
||||||
from .models import *
|
from .models import *
|
||||||
from .processors import verify, payment_accepted, render_purchase_form_html, record_purchase
|
from .processors import process_postpay_callback, render_purchase_form_html
|
||||||
from .processors.exceptions import CCProcessorDataException, CCProcessorWrongAmountException
|
|
||||||
|
|
||||||
log = logging.getLogger("shoppingcart")
|
log = logging.getLogger("shoppingcart")
|
||||||
|
|
||||||
@@ -16,20 +17,22 @@ def test(request, course_id):
|
|||||||
item1.purchased_callback(request.user.id)
|
item1.purchased_callback(request.user.id)
|
||||||
return HttpResponse('OK')
|
return HttpResponse('OK')
|
||||||
|
|
||||||
@login_required
|
|
||||||
def purchased(request):
|
|
||||||
#verify() -- signatures, total cost match up, etc. Need error handling code (
|
|
||||||
# If verify fails probaly need to display a contact email/number)
|
|
||||||
cart = Order.get_cart_for_user(request.user)
|
|
||||||
cart.purchase()
|
|
||||||
return HttpResponseRedirect('/')
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def add_course_to_cart(request, course_id):
|
def add_course_to_cart(request, course_id):
|
||||||
|
if not request.user.is_authenticated():
|
||||||
|
return HttpResponseForbidden(_('You must be logged-in to add to a shopping cart'))
|
||||||
cart = Order.get_cart_for_user(request.user)
|
cart = Order.get_cart_for_user(request.user)
|
||||||
# TODO: Catch 500 here for course that does not exist, period
|
if PaidCourseRegistration.part_of_order(cart, course_id):
|
||||||
PaidCourseRegistration.add_to_order(cart, course_id, 200)
|
return HttpResponseNotFound(_('The course {0} is already in your cart.'.format(course_id)))
|
||||||
return HttpResponse("Added")
|
if CourseEnrollment.objects.filter(user=request.user, course_id=course_id).exists():
|
||||||
|
return HttpResponseNotFound(_('You are already registered in course {0}.'.format(course_id)))
|
||||||
|
try:
|
||||||
|
PaidCourseRegistration.add_to_order(cart, course_id)
|
||||||
|
except ItemNotFoundError:
|
||||||
|
return HttpResponseNotFound(_('The course you requested does not exist.'))
|
||||||
|
if request.method == 'GET':
|
||||||
|
return HttpResponseRedirect(reverse('shoppingcart.views.show_cart'))
|
||||||
|
return HttpResponse(_("Course added to cart."))
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def show_cart(request):
|
def show_cart(request):
|
||||||
@@ -62,31 +65,23 @@ def remove_item(request):
|
|||||||
return HttpResponse('OK')
|
return HttpResponse('OK')
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def postpay_accept_callback(request):
|
def postpay_callback(request):
|
||||||
"""
|
"""
|
||||||
Receives the POST-back from processor and performs the validation and displays a receipt
|
Receives the POST-back from processor.
|
||||||
and does some other stuff
|
Mainly this calls the processor-specific code to check if the payment was accepted, and to record the order
|
||||||
|
if it was, and to generate an error page.
|
||||||
HANDLES THE ACCEPT AND REVIEW CASES
|
If successful this function should have the side effect of changing the "cart" into a full "order" in the DB.
|
||||||
|
The cart can then render a success page which links to receipt pages.
|
||||||
|
If unsuccessful the order will be left untouched and HTML messages giving more detailed error info will be
|
||||||
|
returned.
|
||||||
"""
|
"""
|
||||||
# TODO: Templates and logic for all error cases and the REVIEW CASE
|
result = process_postpay_callback(request)
|
||||||
params = request.POST.dict()
|
if result['success']:
|
||||||
if verify(params):
|
return HttpResponseRedirect(reverse('shoppingcart.views.show_receipt', args=[result['order'].id]))
|
||||||
try:
|
|
||||||
result = payment_accepted(params)
|
|
||||||
if result['accepted']:
|
|
||||||
# ACCEPTED CASE first
|
|
||||||
record_purchase(params, result['order'])
|
|
||||||
#render_receipt
|
|
||||||
return HttpResponseRedirect(reverse('shoppingcart.views.show_receipt', args=[result['order'].id]))
|
|
||||||
else:
|
|
||||||
return HttpResponse("CC Processor has not accepted the payment.")
|
|
||||||
except CCProcessorWrongAmountException:
|
|
||||||
return HttpResponse("Charged the wrong amount, contact our user support")
|
|
||||||
except CCProcessorDataException:
|
|
||||||
return HttpResponse("Exception: the processor returned invalid data")
|
|
||||||
else:
|
else:
|
||||||
return HttpResponse("There has been a communication problem blah blah. Not Validated")
|
return render_to_response('shoppingcart.processor_error.html', {'order':result['order'],
|
||||||
|
'error_html': result['error_html']})
|
||||||
|
|
||||||
|
|
||||||
def show_receipt(request, ordernum):
|
def show_receipt(request, ordernum):
|
||||||
"""
|
"""
|
||||||
@@ -107,7 +102,7 @@ def show_receipt(request, ordernum):
|
|||||||
'order_items': order_items,
|
'order_items': order_items,
|
||||||
'any_refunds': any_refunds})
|
'any_refunds': any_refunds})
|
||||||
|
|
||||||
def show_orders(request):
|
#def show_orders(request):
|
||||||
"""
|
"""
|
||||||
Displays all orders of a user
|
Displays all orders of a user
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -59,7 +59,6 @@
|
|||||||
|
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
|
|
||||||
})(this)
|
})(this)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -93,6 +92,7 @@
|
|||||||
<strong>${_("View Courseware")}</strong>
|
<strong>${_("View Courseware")}</strong>
|
||||||
</a>
|
</a>
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%else:
|
%else:
|
||||||
<a href="#" class="register">${_("Register for {course.display_number_with_default}").format(course=course) | h}</a>
|
<a href="#" class="register">${_("Register for {course.display_number_with_default}").format(course=course) | h}</a>
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<!-- <input id="back_input" type="submit" value="Return" /> -->
|
||||||
${form_html}
|
${form_html}
|
||||||
% else:
|
% else:
|
||||||
<p>${_("You have selected no items for purchase.")}</p>
|
<p>${_("You have selected no items for purchase.")}</p>
|
||||||
@@ -44,6 +44,10 @@
|
|||||||
location.reload(true);
|
location.reload(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#back_input').click(function(){
|
||||||
|
history.back();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,11 @@
|
|||||||
|
|
||||||
<%block name="title"><title>${_("Receipt for Order")} ${order.id}</title></%block>
|
<%block name="title"><title>${_("Receipt for Order")} ${order.id}</title></%block>
|
||||||
|
|
||||||
|
% if notification is not UNDEFINED:
|
||||||
|
<section class="notification">
|
||||||
|
${notification}
|
||||||
|
</section>
|
||||||
|
% endif
|
||||||
|
|
||||||
<section class="container cart-list">
|
<section class="container cart-list">
|
||||||
<p><h1>${_(settings.PLATFORM_NAME + " (" + settings.SITE_NAME + ")" + " Electronic Receipt")}</h1></p>
|
<p><h1>${_(settings.PLATFORM_NAME + " (" + settings.SITE_NAME + ")" + " Electronic Receipt")}</h1></p>
|
||||||
|
|||||||
Reference in New Issue
Block a user