diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py
index cc25031935ff458d818a1760fa2b8ee9a2ae2d5b..fa1a687e47c627725208025b2a90945e8f148938 100644
--- a/common/djangoapps/course_modes/models.py
+++ b/common/djangoapps/course_modes/models.py
@@ -37,7 +37,7 @@ class CourseMode(models.Model):
     currency = models.CharField(default="usd", max_length=8)
 
     # turn this mode off after the given expiration date
-    expiration_date = models.DateField(default=None, null=True)
+    expiration_date = models.DateField(default=None, null=True, blank=True)
 
     DEFAULT_MODE = Mode('honor', _('Honor Code Certificate'), 0, '', 'usd')
     DEFAULT_MODE_SLUG = 'honor'
@@ -86,6 +86,15 @@ class CourseMode(models.Model):
         else:
             return None
 
+    @classmethod
+    def min_course_price_for_currency(cls, course_id, currency):
+        """
+        Returns the minimum price of the course in the appropriate currency over all the course's modes.
+        If there is no mode found, will return the price of DEFAULT_MODE, which is 0
+        """
+        modes = cls.modes_for_course(course_id)
+        return min(mode.min_price for mode in modes if mode.currency == currency)
+
     def __unicode__(self):
         return u"{} : {}, min={}, prices={}".format(
             self.course_id, self.mode_slug, self.min_price, self.suggested_prices
diff --git a/common/djangoapps/course_modes/tests/test_models.py b/common/djangoapps/course_modes/tests/test_models.py
index 72aa3f2464c88c84160456b93e29cf86f6b1ee22..deeed6ea9ba04e43a9a26c936bcd0a3f457fcfff 100644
--- a/common/djangoapps/course_modes/tests/test_models.py
+++ b/common/djangoapps/course_modes/tests/test_models.py
@@ -73,6 +73,24 @@ class CourseModeModelTest(TestCase):
         self.assertEqual(mode2, CourseMode.mode_for_course(self.course_id, u'verified'))
         self.assertIsNone(CourseMode.mode_for_course(self.course_id, 'DNE'))
 
+    def test_min_course_price_for_currency(self):
+        """
+        Get the min course price for a course according to currency
+        """
+        # no modes, should get 0
+        self.assertEqual(0, CourseMode.min_course_price_for_currency(self.course_id, 'usd'))
+
+        # create some modes
+        mode1 = Mode(u'honor', u'Honor Code Certificate', 10, '', 'usd')
+        mode2 = Mode(u'verified', u'Verified Certificate', 20, '', 'usd')
+        mode3 = Mode(u'honor', u'Honor Code Certificate', 80, '', 'cny')
+        set_modes = [mode1, mode2, mode3]
+        for mode in set_modes:
+            self.create_mode(mode.slug, mode.name, mode.min_price, mode.suggested_prices, mode.currency)
+
+        self.assertEqual(10, CourseMode.min_course_price_for_currency(self.course_id, 'usd'))
+        self.assertEqual(80, CourseMode.min_course_price_for_currency(self.course_id, 'cny'))
+
     def test_modes_for_course_expired(self):
         expired_mode, _status = self.create_mode('verified', 'Verified Certificate')
         expired_mode.expiration_date = datetime.now(pytz.UTC) + timedelta(days=-1)
diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py
index a6466ee9f96ca13052e75e91f3e39223862404bb..c35ad664274df56e09f3ca0a5c9bf436cc3e5e82 100644
--- a/common/djangoapps/student/tests/tests.py
+++ b/common/djangoapps/student/tests/tests.py
@@ -11,20 +11,29 @@ import unittest
 
 from django.conf import settings
 from django.test import TestCase
+from django.test.utils import override_settings
 from django.test.client import RequestFactory
 from django.contrib.auth.models import User
 from django.contrib.auth.hashers import UNUSABLE_PASSWORD
 from django.contrib.auth.tokens import default_token_generator
 from django.utils.http import int_to_base36
+from django.core.urlresolvers import reverse
 
+from xmodule.modulestore.tests.factories import CourseFactory
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
+from courseware.tests.tests import TEST_DATA_MIXED_MODULESTORE
 
 from mock import Mock, patch
 from textwrap import dedent
 
 from student.models import unique_id_for_user, CourseEnrollment
-from student.views import process_survey_link, _cert_info, password_reset, password_reset_confirm_wrapper
+from student.views import (process_survey_link, _cert_info, password_reset, password_reset_confirm_wrapper,
+                           change_enrollment)
 from student.tests.factories import UserFactory
 from student.tests.test_email import mock_render_to_string
+
+import shoppingcart
+
 COURSE_1 = 'edX/toy/2012_Fall'
 COURSE_2 = 'edx/full/6.002_Spring_2012'
 
@@ -343,3 +352,32 @@ class EnrollInCourseTest(TestCase):
         # for that user/course_id combination
         CourseEnrollment.enroll(user, course_id)
         self.assertTrue(CourseEnrollment.is_enrolled(user, course_id))
+
+
+@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
+class PaidRegistrationTest(ModuleStoreTestCase):
+    """
+    Tests for paid registration functionality (not verified student), involves shoppingcart
+    """
+    # arbitrary constant
+    COURSE_SLUG = "100"
+    COURSE_NAME = "test_course"
+    COURSE_ORG = "EDX"
+
+    def setUp(self):
+        # Create course
+        self.req_factory = RequestFactory()
+        self.course = CourseFactory.create(org=self.COURSE_ORG, display_name=self.COURSE_NAME, number=self.COURSE_SLUG)
+        self.assertIsNotNone(self.course)
+        self.user = User.objects.create(username="jack", email="jack@fake.edx.org")
+
+    @unittest.skipUnless(settings.MITX_FEATURES.get('ENABLE_SHOPPING_CART'), "Shopping Cart not enabled in settings")
+    def test_change_enrollment_add_to_cart(self):
+        request = self.req_factory.post(reverse('change_enrollment'), {'course_id': self.course.id,
+                                                                       'enrollment_action': 'add_to_cart'})
+        request.user = self.user
+        response = change_enrollment(request)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.content, reverse('shoppingcart.views.show_cart'))
+        self.assertTrue(shoppingcart.models.PaidCourseRegistration.contained_in_order(
+            shoppingcart.models.Order.get_cart_for_user(self.user), self.course.id))
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index 486e32c465fce77f2001a666b682abded9d5c124..db2ce5b4a452dea2e8108f0ee8c8ea73809f4829 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -58,6 +58,7 @@ from external_auth.models import ExternalAuthMap
 import external_auth.views
 
 from bulk_email.models import Optout
+import shoppingcart
 
 import track.views
 
@@ -405,6 +406,19 @@ def change_enrollment(request):
 
         return HttpResponse()
 
+    elif action == "add_to_cart":
+        # Pass the request handling to shoppingcart.views
+        # The view in shoppingcart.views performs error handling and logs different errors.  But this elif clause
+        # is only used in the "auto-add after user reg/login" case, i.e. it's always wrapped in try_change_enrollment.
+        # This means there's no good way to display error messages to the user.  So we log the errors and send
+        # the user to the shopping cart page always, where they can reasonably discern the status of their cart,
+        # whether things got added, etc
+
+        shoppingcart.views.add_course_to_cart(request, course_id)
+        return HttpResponse(
+            reverse("shoppingcart.views.show_cart")
+        )
+
     elif action == "unenroll":
         try:
             CourseEnrollment.unenroll(user, course_id)
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index 1c87abd3bc6edd9dac3ae4b8be0749711e96c5ba..dbf4bf1994adf1d9a771910cd4eb5f298d92fc5f 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -1,10 +1,14 @@
-from mock import MagicMock
+"""
+Tests courseware views.py
+"""
+from mock import MagicMock, patch
 import datetime
+import unittest
 
 from django.test import TestCase
 from django.http import Http404
 from django.test.utils import override_settings
-from django.contrib.auth.models import User
+from django.contrib.auth.models import User, AnonymousUser
 from django.test.client import RequestFactory
 
 from django.conf import settings
@@ -15,12 +19,14 @@ from student.tests.factories import AdminFactory
 from mitxmako.middleware import MakoMiddleware
 
 from xmodule.modulestore.django import modulestore, clear_existing_modulestores
+from xmodule.modulestore.tests.factories import CourseFactory
 
 import courseware.views as views
 from xmodule.modulestore import Location
 from pytz import UTC
 from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
 from course_modes.models import CourseMode
+import shoppingcart
 
 
 @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
@@ -78,6 +84,32 @@ class ViewsTestCase(TestCase):
         chapter = 'Overview'
         self.chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter)
 
+    @unittest.skipUnless(settings.MITX_FEATURES.get('ENABLE_SHOPPING_CART'), "Shopping Cart not enabled in settings")
+    @patch.dict(settings.MITX_FEATURES, {'ENABLE_PAID_COURSE_REGISTRATION': True})
+    def test_course_about_in_cart(self):
+        in_cart_span = '<span class="add-to-cart">'
+        # don't mock this course due to shopping cart existence checking
+        course = CourseFactory.create(org="new", number="unenrolled", display_name="course")
+        request = self.request_factory.get(reverse('about_course', args=[course.id]))
+        request.user = AnonymousUser()
+        response = views.course_about(request, course.id)
+        self.assertEqual(response.status_code, 200)
+        self.assertNotIn(in_cart_span, response.content)
+
+        # authenticated user with nothing in cart
+        request.user = self.user
+        response = views.course_about(request, course.id)
+        self.assertEqual(response.status_code, 200)
+        self.assertNotIn(in_cart_span, response.content)
+
+        # now add the course to the cart
+        cart = shoppingcart.models.Order.get_cart_for_user(self.user)
+        shoppingcart.models.PaidCourseRegistration.add_to_order(cart, course.id)
+        response = views.course_about(request, course.id)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn(in_cart_span, response.content)
+
+
     def test_user_groups(self):
         # depreciated function
         mock_user = MagicMock()
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index 66c91f1b9bea57f4e8ded391198a29c92de70d9c..4488b510d05c66e2a78341a11ec29ddbee057d64 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -36,6 +36,7 @@ from xmodule.modulestore.django import modulestore
 from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem
 from xmodule.modulestore.search import path_to_location
 from xmodule.course_module import CourseDescriptor
+import shoppingcart
 
 import comment_client
 
@@ -604,10 +605,27 @@ def course_about(request, course_id):
     show_courseware_link = (has_access(request.user, course, 'load') or
                             settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'))
 
+    # Note: this is a flow for payment for course registration, not the Verified Certificate flow.
+    registration_price = 0
+    in_cart = False
+    reg_then_add_to_cart_link = ""
+    if settings.MITX_FEATURES.get('ENABLE_PAID_COURSE_REGISTRATION'):
+        registration_price = CourseMode.min_course_price_for_currency(course_id,
+                                                                      settings.PAID_COURSE_REGISTRATION_CURRENCY[0])
+        if request.user.is_authenticated():
+            cart = shoppingcart.models.Order.get_cart_for_user(request.user)
+            in_cart = shoppingcart.models.PaidCourseRegistration.contained_in_order(cart, course_id)
+
+        reg_then_add_to_cart_link = "{reg_url}?course_id={course_id}&enrollment_action=add_to_cart".format(
+            reg_url=reverse('register_user'), course_id=course.id)
+
     return render_to_response('courseware/course_about.html',
                               {'course': course,
                                'registered': registered,
                                'course_target': course_target,
+                               'registration_price': registration_price,
+                               'in_cart': in_cart,
+                               'reg_then_add_to_cart_link': reg_then_add_to_cart_link,
                                'show_courseware_link': show_courseware_link})
 
 
diff --git a/lms/djangoapps/shoppingcart/exceptions.py b/lms/djangoapps/shoppingcart/exceptions.py
index 029dc079bb27f0bb389d567f4adcb49a3a4748a6..a40c2e9feba92bfbf231403d9d565c74fb793c97 100644
--- a/lms/djangoapps/shoppingcart/exceptions.py
+++ b/lms/djangoapps/shoppingcart/exceptions.py
@@ -1,3 +1,9 @@
+"""
+Exceptions for the shoppingcart app
+"""
+# (Exception Class Names are sort of self-explanatory, so skipping docstring requirement)
+# pylint: disable=C0111
+
 class PaymentException(Exception):
     pass
 
@@ -8,3 +14,15 @@ class PurchasedCallbackException(PaymentException):
 
 class InvalidCartItem(PaymentException):
     pass
+
+
+class ItemAlreadyInCartException(InvalidCartItem):
+    pass
+
+
+class AlreadyEnrolledInCourseException(InvalidCartItem):
+    pass
+
+
+class CourseDoesNotExistException(InvalidCartItem):
+    pass
diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py
index 368a327fc903959e816b280446da691eab47e0c3..9ae11f6554aada58bdb26815e61e9ff713f88aa7 100644
--- a/lms/djangoapps/shoppingcart/models.py
+++ b/lms/djangoapps/shoppingcart/models.py
@@ -2,7 +2,10 @@ from datetime import datetime
 import pytz
 import logging
 import smtplib
-import textwrap
+
+from model_utils.managers import InheritanceManager
+from collections import namedtuple
+from boto.exception import BotoServerError  # this is a super-class of SESError and catches connection errors
 
 from django.db import models
 from django.conf import settings
@@ -11,19 +14,20 @@ from django.core.mail import send_mail
 from django.contrib.auth.models import User
 from django.utils.translation import ugettext as _
 from django.db import transaction
-from model_utils.managers import InheritanceManager
+from django.core.urlresolvers import reverse
+
+from xmodule.modulestore.django import modulestore
+from xmodule.course_module import CourseDescriptor
+from xmodule.modulestore.exceptions import ItemNotFoundError
 
 from course_modes.models import CourseMode
-from courseware.courses import get_course_about_section
 from mitxmako.shortcuts import render_to_string
 from student.views import course_from_id
 from student.models import CourseEnrollment
-from dogapi import dog_stats_api
 from verify_student.models import SoftwareSecurePhotoVerification
-from xmodule.modulestore.django import modulestore
-from xmodule.course_module import CourseDescriptor
 
-from .exceptions import InvalidCartItem, PurchasedCallbackException
+from .exceptions import (InvalidCartItem, PurchasedCallbackException, ItemAlreadyInCartException,
+                         AlreadyEnrolledInCourseException, CourseDoesNotExistException)
 
 log = logging.getLogger("shoppingcart")
 
@@ -33,6 +37,9 @@ ORDER_STATUSES = (
     ('refunded', 'refunded'),  # Not used for now
 )
 
+# we need a tuple to represent the primary key of various OrderItem subclasses
+OrderItemSubclassPK = namedtuple('OrderItemSubclassPK', ['cls', 'pk'])  # pylint: disable=C0103
+
 
 class Order(models.Model):
     """
@@ -72,13 +79,30 @@ class Order(models.Model):
             cart_order, _created = cls.objects.get_or_create(user=user, status='cart')
         return cart_order
 
+    @classmethod
+    def user_cart_has_items(cls, user):
+        """
+        Returns true if the user (anonymous user ok) has
+        a cart with items in it.  (Which means it should be displayed.
+        """
+        if not user.is_authenticated():
+            return False
+        cart = cls.get_cart_for_user(user)
+        return cart.has_items()
+
     @property
     def total_cost(self):
         """
         Return the total cost of the cart.  If the order has been purchased, returns total of
         all purchased and not refunded items.
         """
-        return sum(i.line_cost for i in self.orderitem_set.filter(status=self.status))
+        return sum(i.line_cost for i in self.orderitem_set.filter(status=self.status))  # pylint: disable=E1101
+
+    def has_items(self):
+        """
+        Does the cart have any items in it?
+        """
+        return self.orderitem_set.exists()  # pylint: disable=E1101
 
     def clear(self):
         """
@@ -135,13 +159,31 @@ class Order(models.Model):
         subject = _("Order Payment Confirmation")
         message = render_to_string('emails/order_confirmation_email.txt', {
             'order': self,
-            'order_items': orderitems
+            'order_items': orderitems,
+            'has_billing_info': settings.MITX_FEATURES['STORE_BILLING_INFO']
         })
         try:
             send_mail(subject, message,
-                      settings.DEFAULT_FROM_EMAIL, [self.user.email])
-        except smtplib.SMTPException:
-            log.error('Failed sending confirmation e-mail for order %d', self.id)
+                      settings.DEFAULT_FROM_EMAIL, [self.user.email])  # pylint: disable=E1101
+        except (smtplib.SMTPException, BotoServerError):  # sadly need to handle diff. mail backends individually
+            log.error('Failed sending confirmation e-mail for order %d', self.id)  # pylint: disable=E1101
+
+    def generate_receipt_instructions(self):
+        """
+        Call to generate specific instructions for each item in the order.  This gets displayed on the receipt
+        page, typically.  Instructions are something like "visit your dashboard to see your new courses".
+        This will return two things in a pair.  The first will be a dict with keys=OrderItemSubclassPK corresponding
+        to an OrderItem and values=a set of html instructions they generate.  The second will be a set of de-duped
+        html instructions
+        """
+        instruction_set = set([])  # heh. not ia32 or alpha or sparc
+        instruction_dict = {}
+        order_items = OrderItem.objects.filter(order=self).select_subclasses()
+        for item in order_items:
+            item_pk_with_subclass, set_of_html = item.generate_receipt_instructions()
+            instruction_dict[item_pk_with_subclass] = set_of_html
+            instruction_set.update(set_of_html)
+        return instruction_dict, instruction_set
 
 
 class OrderItem(models.Model):
@@ -202,6 +244,22 @@ class OrderItem(models.Model):
         """
         raise NotImplementedError
 
+    def generate_receipt_instructions(self):
+        """
+        This is called on each item in a purchased order to generate receipt instructions.
+        This should return a list of `ReceiptInstruction`s in HTML string
+        Default implementation is to return an empty set
+        """
+        return self.pk_with_subclass, set([])
+
+    @property
+    def pk_with_subclass(self):
+        """
+        Returns a named tuple that annotates the pk of this instance with its class, to fully represent
+        a pk of a subclass (inclusive) of OrderItem
+        """
+        return OrderItemSubclassPK(type(self), self.pk)
+
     @property
     def single_item_receipt_template(self):
         """
@@ -235,9 +293,9 @@ class PaidCourseRegistration(OrderItem):
     mode = models.SlugField(default=CourseMode.DEFAULT_MODE_SLUG)
 
     @classmethod
-    def part_of_order(cls, order, course_id):
+    def contained_in_order(cls, order, course_id):
         """
-        Is the course defined by course_id in the order?
+        Is the course defined by course_id contained in the order?
         """
         return course_id in [item.paidcourseregistration.course_id
                              for item in order.orderitem_set.all().select_subclasses("paidcourseregistration")]
@@ -251,10 +309,26 @@ class PaidCourseRegistration(OrderItem):
 
         Returns the order item
         """
-        # TODO: Possibly add checking for whether student is already enrolled in course
-        course = course_from_id(course_id)  # actually fetch the course to make sure it exists, use this to
-                                            # throw errors if it doesn't
-
+        # First a bunch of sanity checks
+        try:
+            course = course_from_id(course_id)  # actually fetch the course to make sure it exists, use this to
+                                                # throw errors if it doesn't
+        except ItemNotFoundError:
+            log.error("User {} tried to add non-existent course {} to cart id {}"
+                      .format(order.user.email, course_id, order.id))
+            raise CourseDoesNotExistException
+
+        if cls.contained_in_order(order, course_id):
+            log.warning("User {} tried to add PaidCourseRegistration for course {}, already in cart id {}"
+                        .format(order.user.email, course_id, order.id))
+            raise ItemAlreadyInCartException
+
+        if CourseEnrollment.is_enrolled(user=order.user, course_id=course_id):
+            log.warning("User {} trying to add course {} to cart id {}, already registered"
+                        .format(order.user.email, course_id, order.id))
+            raise AlreadyEnrolledInCourseException
+
+        ### Validations done, now proceed
         ### handle default arguments for mode_slug, cost, currency
         course_mode = CourseMode.mode_for_course(course_id, mode_slug)
         if not course_mode:
@@ -273,12 +347,13 @@ class PaidCourseRegistration(OrderItem):
         item.mode = course_mode.slug
         item.qty = 1
         item.unit_cost = cost
-        item.line_desc = 'Registration for Course: {0}. Mode: {1}'.format(get_course_about_section(course, "title"),
-                                                                          course_mode.name)
+        item.line_desc = 'Registration for Course: {0}'.format(course.display_name_with_default)
         item.currency = currency
         order.currency = currency
         order.save()
         item.save()
+        log.info("User {} added course registration {} to cart: order {}"
+                 .format(order.user.email, course_id, order.id))
         return item
 
     def purchased_callback(self):
@@ -301,14 +376,18 @@ class PaidCourseRegistration(OrderItem):
 
         CourseEnrollment.enroll(user=self.user, course_id=self.course_id, mode=self.mode)
 
-        log.info("Enrolled {0} in paid course {1}, paid ${2}".format(self.user.email, self.course_id, self.line_cost))
-        org, course_num, run = self.course_id.split("/")
-        dog_stats_api.increment(
-            "shoppingcart.PaidCourseRegistration.purchased_callback.enrollment",
-            tags=["org:{0}".format(org),
-                  "course:{0}".format(course_num),
-                  "run:{0}".format(run)]
-        )
+        log.info("Enrolled {0} in paid course {1}, paid ${2}"
+                 .format(self.user.email, self.course_id, self.line_cost))  # pylint: disable=E1101
+
+    def generate_receipt_instructions(self):
+        """
+        Generates instructions when the user has purchased a PaidCourseRegistration.
+        Basically tells the user to visit the dashboard to see their new classes
+        """
+        notification = (_('Please visit your <a href="{dashboard_link}">dashboard</a> to see your new enrollments.')
+                        .format(dashboard_link=reverse('dashboard')))
+
+        return self.pk_with_subclass, set([notification])
 
 
 class CertificateItem(OrderItem):
diff --git a/lms/djangoapps/shoppingcart/tests/test_models.py b/lms/djangoapps/shoppingcart/tests/test_models.py
index f14599714593261ffea7368242b2ef23ffb9d971..1224bb5093b33b072c0eff7bb62dccfe27be9ff8 100644
--- a/lms/djangoapps/shoppingcart/tests/test_models.py
+++ b/lms/djangoapps/shoppingcart/tests/test_models.py
@@ -1,19 +1,21 @@
 """
 Tests for the Shopping Cart Models
 """
+import smtplib
+from boto.exception import BotoServerError  # this is a super-class of SESError and catches connection errors
 
-from factory import DjangoModelFactory
-from mock import patch
+from mock import patch, MagicMock
 from django.core import mail
 from django.conf import settings
 from django.db import DatabaseError
 from django.test import TestCase
 from django.test.utils import override_settings
-
+from django.contrib.auth.models import AnonymousUser
 from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory
 from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
-from shoppingcart.models import Order, OrderItem, CertificateItem, InvalidCartItem, PaidCourseRegistration
+from shoppingcart.models import (Order, OrderItem, CertificateItem, InvalidCartItem, PaidCourseRegistration,
+                                 OrderItemSubclassPK)
 from student.tests.factories import UserFactory
 from student.models import CourseEnrollment
 from course_modes.models import CourseMode
@@ -39,13 +41,24 @@ class OrderTest(ModuleStoreTestCase):
         cart2 = Order.get_cart_for_user(user=self.user)
         self.assertEquals(cart2.orderitem_set.count(), 1)
 
+    def test_user_cart_has_items(self):
+        anon = AnonymousUser()
+        self.assertFalse(Order.user_cart_has_items(anon))
+        self.assertFalse(Order.user_cart_has_items(self.user))
+        cart = Order.get_cart_for_user(self.user)
+        item = OrderItem(order=cart, user=self.user)
+        item.save()
+        self.assertTrue(Order.user_cart_has_items(self.user))
+
     def test_cart_clear(self):
         cart = Order.get_cart_for_user(user=self.user)
         CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
         CertificateItem.add_to_order(cart, 'org/test/Test_Course_1', self.cost, 'honor')
         self.assertEquals(cart.orderitem_set.count(), 2)
+        self.assertTrue(cart.has_items())
         cart.clear()
         self.assertEquals(cart.orderitem_set.count(), 0)
+        self.assertFalse(cart.has_items())
 
     def test_add_item_to_cart_currency_match(self):
         cart = Order.get_cart_for_user(user=self.user)
@@ -111,6 +124,22 @@ class OrderTest(ModuleStoreTestCase):
         cart.purchase()
         self.assertEquals(len(mail.outbox), 1)
 
+    @patch('shoppingcart.models.log.error')
+    def test_purchase_item_email_smtp_failure(self, error_logger):
+        cart = Order.get_cart_for_user(user=self.user)
+        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
+        with patch('shoppingcart.models.send_mail', side_effect=smtplib.SMTPException):
+            cart.purchase()
+            self.assertTrue(error_logger.called)
+
+    @patch('shoppingcart.models.log.error')
+    def test_purchase_item_email_boto_failure(self, error_logger):
+        cart = Order.get_cart_for_user(user=self.user)
+        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
+        with patch('shoppingcart.models.send_mail', side_effect=BotoServerError("status", "reason")):
+            cart.purchase()
+            self.assertTrue(error_logger.called)
+
     def purchase_with_data(self, cart):
         """ purchase a cart with billing information """
         CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
@@ -127,8 +156,9 @@ class OrderTest(ModuleStoreTestCase):
             cardtype='001',
         )
 
+    @patch('shoppingcart.models.render_to_string')
     @patch.dict(settings.MITX_FEATURES, {'STORE_BILLING_INFO': True})
-    def test_billing_info_storage_on(self):
+    def test_billing_info_storage_on(self, render):
         cart = Order.get_cart_for_user(self.user)
         self.purchase_with_data(cart)
         self.assertNotEqual(cart.bill_to_first, '')
@@ -141,9 +171,12 @@ class OrderTest(ModuleStoreTestCase):
         self.assertNotEqual(cart.bill_to_city, '')
         self.assertNotEqual(cart.bill_to_state, '')
         self.assertNotEqual(cart.bill_to_country, '')
+        ((_, context), _) = render.call_args
+        self.assertTrue(context['has_billing_info'])
 
+    @patch('shoppingcart.models.render_to_string')
     @patch.dict(settings.MITX_FEATURES, {'STORE_BILLING_INFO': False})
-    def test_billing_info_storage_off(self):
+    def test_billing_info_storage_off(self, render):
         cart = Order.get_cart_for_user(self.user)
         self.purchase_with_data(cart)
         self.assertNotEqual(cart.bill_to_first, '')
@@ -157,13 +190,30 @@ class OrderTest(ModuleStoreTestCase):
         self.assertEqual(cart.bill_to_street2, '')
         self.assertEqual(cart.bill_to_ccnum, '')
         self.assertEqual(cart.bill_to_cardtype, '')
+        ((_, context), _) = render.call_args
+        self.assertFalse(context['has_billing_info'])
+
+    mock_gen_inst = MagicMock(return_value=(OrderItemSubclassPK(OrderItem, 1), set([])))
+
+    def test_generate_receipt_instructions_callchain(self):
+        """
+        This tests the generate_receipt_instructions call chain (ie calling the function on the
+        cart also calls it on items in the cart
+        """
+        cart = Order.get_cart_for_user(self.user)
+        item = OrderItem(user=self.user, order=cart)
+        item.save()
+        self.assertTrue(cart.has_items())
+        with patch.object(OrderItem, 'generate_receipt_instructions', self.mock_gen_inst):
+            cart.generate_receipt_instructions()
+            self.mock_gen_inst.assert_called_with()
 
 
 class OrderItemTest(TestCase):
     def setUp(self):
         self.user = UserFactory.create()
 
-    def test_orderItem_purchased_callback(self):
+    def test_order_item_purchased_callback(self):
         """
         This tests that calling purchased_callback on the base OrderItem class raises NotImplementedError
         """
@@ -171,6 +221,19 @@ class OrderItemTest(TestCase):
         with self.assertRaises(NotImplementedError):
             item.purchased_callback()
 
+    def test_order_item_generate_receipt_instructions(self):
+        """
+        This tests that the generate_receipt_instructions call chain and also
+        that calling it on the base OrderItem class returns an empty list
+        """
+        cart = Order.get_cart_for_user(self.user)
+        item = OrderItem(user=self.user, order=cart)
+        item.save()
+        self.assertTrue(cart.has_items())
+        (inst_dict, inst_set) = cart.generate_receipt_instructions()
+        self.assertDictEqual({item.pk_with_subclass: set([])}, inst_dict)
+        self.assertEquals(set([]), inst_set)
+
 
 @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
 class PaidCourseRegistrationTest(ModuleStoreTestCase):
@@ -195,8 +258,8 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase):
         self.assertEqual(reg1.mode, "honor")
         self.assertEqual(reg1.user, self.user)
         self.assertEqual(reg1.status, "cart")
-        self.assertTrue(PaidCourseRegistration.part_of_order(self.cart, self.course_id))
-        self.assertFalse(PaidCourseRegistration.part_of_order(self.cart, self.course_id + "abcd"))
+        self.assertTrue(PaidCourseRegistration.contained_in_order(self.cart, self.course_id))
+        self.assertFalse(PaidCourseRegistration.contained_in_order(self.cart, self.course_id + "abcd"))
         self.assertEqual(self.cart.total_cost, self.cost)
 
     def test_add_with_default_mode(self):
@@ -212,7 +275,7 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase):
         self.assertEqual(reg1.user, self.user)
         self.assertEqual(reg1.status, "cart")
         self.assertEqual(self.cart.total_cost, 0)
-        self.assertTrue(PaidCourseRegistration.part_of_order(self.cart, self.course_id))
+        self.assertTrue(PaidCourseRegistration.contained_in_order(self.cart, self.course_id))
 
     def test_purchased_callback(self):
         reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_id)
@@ -221,6 +284,26 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase):
         reg1 = PaidCourseRegistration.objects.get(id=reg1.id)  # reload from DB to get side-effect
         self.assertEqual(reg1.status, "purchased")
 
+    def test_generate_receipt_instructions(self):
+        """
+        Add 2 courses to the order and make sure the instruction_set only contains 1 element (no dups)
+        """
+        course2 = CourseFactory.create(org='MITx', number='998', display_name='Robot Duper Course')
+        course_mode2 = CourseMode(course_id=course2.id,
+                                  mode_slug="honor",
+                                  mode_display_name="honor cert",
+                                  min_price=self.cost)
+        course_mode2.save()
+        pr1 = PaidCourseRegistration.add_to_order(self.cart, self.course_id)
+        pr2 = PaidCourseRegistration.add_to_order(self.cart, course2.id)
+        self.cart.purchase()
+        inst_dict, inst_set = self.cart.generate_receipt_instructions()
+        self.assertEqual(2, len(inst_dict))
+        self.assertEqual(1, len(inst_set))
+        self.assertIn("dashboard", inst_set.pop())
+        self.assertIn(pr1.pk_with_subclass, inst_dict)
+        self.assertIn(pr2.pk_with_subclass, inst_dict)
+
     def test_purchased_callback_exception(self):
         reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_id)
         reg1.course_id = "changedforsomereason"
diff --git a/lms/djangoapps/shoppingcart/tests/test_views.py b/lms/djangoapps/shoppingcart/tests/test_views.py
index 8c124901c11400f943fa7c40375489e4bc2c6879..d60cab78d90e3641ac3d1fe26d58b5a36d5dc0f1 100644
--- a/lms/djangoapps/shoppingcart/tests/test_views.py
+++ b/lms/djangoapps/shoppingcart/tests/test_views.py
@@ -85,7 +85,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
         self.login_user()
         resp = self.client.post(reverse('shoppingcart.views.add_course_to_cart', args=[self.course_id]))
         self.assertEqual(resp.status_code, 200)
-        self.assertTrue(PaidCourseRegistration.part_of_order(self.cart, self.course_id))
+        self.assertTrue(PaidCourseRegistration.contained_in_order(self.cart, self.course_id))
 
 
     @patch('shoppingcart.views.render_purchase_form_html', form_mock)
diff --git a/lms/djangoapps/shoppingcart/views.py b/lms/djangoapps/shoppingcart/views.py
index 8930136b802de06b2e096797e888083d2dd1fccf..8c6d61d532e530afaa3d44c897eab2308bd0b0ed 100644
--- a/lms/djangoapps/shoppingcart/views.py
+++ b/lms/djangoapps/shoppingcart/views.py
@@ -6,30 +6,33 @@ from django.views.decorators.http import require_POST
 from django.core.urlresolvers import reverse
 from django.views.decorators.csrf import csrf_exempt
 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 .models import Order, PaidCourseRegistration, CertificateItem, OrderItem
+from .models import Order, PaidCourseRegistration, OrderItem
 from .processors import process_postpay_callback, render_purchase_form_html
+from .exceptions import ItemAlreadyInCartException, AlreadyEnrolledInCourseException, CourseDoesNotExistException
 
 log = logging.getLogger("shoppingcart")
 
 
+@require_POST
 def add_course_to_cart(request, course_id):
+    """
+    Adds course specified by course_id to the cart.  The model function add_to_order does all the
+    heavy lifting (logging, error checking, etc)
+    """
     if not request.user.is_authenticated():
+        log.info("Anon user trying to add course {} to cart".format(course_id))
         return HttpResponseForbidden(_('You must be logged-in to add to a shopping cart'))
     cart = Order.get_cart_for_user(request.user)
-    if PaidCourseRegistration.part_of_order(cart, course_id):
-        return HttpResponseBadRequest(_('The course {0} is already in your cart.'.format(course_id)))
-    if CourseEnrollment.is_enrolled(user=request.user, course_id=course_id):
-        return HttpResponseBadRequest(_('You are already registered in course {0}.'.format(course_id)))
-
+    # All logging from here handled by the model
     try:
         PaidCourseRegistration.add_to_order(cart, course_id)
-    except ItemNotFoundError:
+    except CourseDoesNotExistException:
         return HttpResponseNotFound(_('The course you requested does not exist.'))
-    if request.method == 'GET':  # This is temporary for testing purposes and will go away before we pull
-        return HttpResponseRedirect(reverse('shoppingcart.views.show_cart'))
+    except ItemAlreadyInCartException:
+        return HttpResponseBadRequest(_('The course {0} is already in your cart.'.format(course_id)))
+    except AlreadyEnrolledInCourseException:
+        return HttpResponseBadRequest(_('You are already registered in course {0}.'.format(course_id)))
     return HttpResponse(_("Course added to cart."))
 
 
@@ -103,12 +106,14 @@ def show_receipt(request, ordernum):
     order_items = OrderItem.objects.filter(order=order).select_subclasses()
     any_refunds = any(i.status == "refunded" for i in order_items)
     receipt_template = 'shoppingcart/receipt.html'
+    __, instructions = order.generate_receipt_instructions()
     # we want to have the ability to override the default receipt page when
     # there is only one item in the order
     context = {
         'order': order,
         'order_items': order_items,
         'any_refunds': any_refunds,
+        'instructions': instructions,
     }
 
     if order_items.count() == 1:
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index 1d012e4ca03566568cbb3adb370355a97e12eaae..86d3d539bd5ccc0c80eb336f962dea7bc3d04f34 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -100,6 +100,8 @@ with open(ENV_ROOT / CONFIG_PREFIX + "env.json") as env_file:
     ENV_TOKENS = json.load(env_file)
 
 PLATFORM_NAME = ENV_TOKENS.get('PLATFORM_NAME', PLATFORM_NAME)
+# For displaying on the receipt. At Stanford PLATFORM_NAME != MERCHANT_NAME, but PLATFORM_NAME is a fine default
+CC_MERCHANT_NAME = ENV_TOKENS.get('CC_MERCHANT_NAME', PLATFORM_NAME)
 EMAIL_BACKEND = ENV_TOKENS.get('EMAIL_BACKEND', EMAIL_BACKEND)
 EMAIL_FILE_PATH = ENV_TOKENS.get('EMAIL_FILE_PATH', None)
 EMAIL_HOST = ENV_TOKENS.get('EMAIL_HOST', 'localhost')  # django default is localhost
@@ -136,6 +138,8 @@ TECH_SUPPORT_EMAIL = ENV_TOKENS.get('TECH_SUPPORT_EMAIL', TECH_SUPPORT_EMAIL)
 CONTACT_EMAIL = ENV_TOKENS.get('CONTACT_EMAIL', CONTACT_EMAIL)
 BUGS_EMAIL = ENV_TOKENS.get('BUGS_EMAIL', BUGS_EMAIL)
 PAYMENT_SUPPORT_EMAIL = ENV_TOKENS.get('PAYMENT_SUPPORT_EMAIL', PAYMENT_SUPPORT_EMAIL)
+PAID_COURSE_REGISTRATION_CURRENCY = ENV_TOKENS.get('PAID_COURSE_REGISTRATION_CURRENCY',
+                                                   PAID_COURSE_REGISTRATION_CURRENCY)
 
 #Theme overrides
 THEME_NAME = ENV_TOKENS.get('THEME_NAME', None)
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 47f0083957ae261b36309e67f3ab20d6a0e517ce..941809b82fb121f12b788720f7366a9c7c6304ba 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -36,6 +36,7 @@ from xmodule.modulestore.inheritance import InheritanceMixin
 ################################### FEATURES ###################################
 # The display name of the platform to be used in templates/emails/etc.
 PLATFORM_NAME = "edX"
+CC_MERCHANT_NAME = PLATFORM_NAME
 
 COURSEWARE_ENABLED = True
 ENABLE_JASMINE = False
@@ -171,6 +172,9 @@ MITX_FEATURES = {
 
     # Toggle storing detailed billing information
     'STORE_BILLING_INFO': False,
+
+    # Enable flow for payments for course registration (DIFFERENT from verified student flow)
+    'ENABLE_PAID_COURSE_REGISTRATION': False,
 }
 
 # Used for A/B testing
@@ -500,7 +504,8 @@ CC_PROCESSOR = {
         'PURCHASE_ENDPOINT': '',
     }
 }
-
+# Setting for PAID_COURSE_REGISTRATION, DOES NOT AFFECT VERIFIED STUDENTS
+PAID_COURSE_REGISTRATION_CURRENCY = ['usd', '$']
 ################################# open ended grading config  #####################
 
 #By setting up the default settings with an incorrect user name and password,
diff --git a/lms/envs/test.py b/lms/envs/test.py
index 45e934ec1f21883186a5c22fcae578416d19efa8..30475e26a0e847c6f62c9b8f23a7fb75bda44eb9 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -165,7 +165,6 @@ OPENID_PROVIDER_TRUSTED_ROOTS = ['*']
 ###################### Payment ##############################3
 # Enable fake payment processing page
 MITX_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
diff --git a/lms/static/sass/application.scss.mako b/lms/static/sass/application.scss.mako
index 2753edda0cd3daa6ff417b137a6957aa7222fb16..a5007481218b385a5f1c1e5ab5831a8802a576a5 100644
--- a/lms/static/sass/application.scss.mako
+++ b/lms/static/sass/application.scss.mako
@@ -48,6 +48,7 @@
 
 // base - specific views
 @import 'views/verification';
+@import 'views/shoppingcart';
 
 // shared - course
 @import 'shared/forms';
diff --git a/lms/static/sass/multicourse/_course_about.scss b/lms/static/sass/multicourse/_course_about.scss
index 4f1831cc7ffc925fa560d2b068125599f40d48bf..26bafff87df93ecd5bc91f5c491542432ea66fd7 100644
--- a/lms/static/sass/multicourse/_course_about.scss
+++ b/lms/static/sass/multicourse/_course_about.scss
@@ -98,7 +98,7 @@
           @include transition(all 0.15s linear 0s);
           width: flex-grid(12);
 
-          > a.find-courses, a.register {
+          > a.find-courses, a.register, a.add-to-cart {
             @include button(shiny, $button-color);
             @include box-sizing(border-box);
             border-radius: 3px;
@@ -139,7 +139,7 @@
             }
           }
 
-          span.register {
+          span.register, span.add-to-cart {
             background: $button-archive-color;
             border: 1px solid darken($button-archive-color, 50%);
             @include box-sizing(border-box);
diff --git a/lms/static/sass/shared/_header.scss b/lms/static/sass/shared/_header.scss
index a649cb43d95075cb5e5dcb2537ba4c015b3d8781..b3ca2077f0a9a2cc5a01eed751a0cc5509a9fd06 100644
--- a/lms/static/sass/shared/_header.scss
+++ b/lms/static/sass/shared/_header.scss
@@ -123,6 +123,13 @@ header.global {
             border-radius: 0 4px 4px 0;
             border-left: none;
             padding: 5px 8px 7px 8px;
+            
+            &.shopping-cart {
+              border-radius: 4px;
+              border: 1px solid $border-color-2;
+              margin-right: 10px;
+              padding-bottom: 6px;
+            }
           }
         }
       }
diff --git a/lms/static/sass/views/_shoppingcart.scss b/lms/static/sass/views/_shoppingcart.scss
new file mode 100644
index 0000000000000000000000000000000000000000..d6861fb45630036c1518b2e08c250dbf1a8d7803
--- /dev/null
+++ b/lms/static/sass/views/_shoppingcart.scss
@@ -0,0 +1,103 @@
+// lms - views - shopping cart
+// ====================
+
+.notification {
+  padding: 30px 30px 0 30px;
+}
+
+.cart-list {
+  padding: 30px;
+  margin-top: 40px;
+  border-radius: 3px;
+  border: 1px solid $border-color-1;
+  background-color: $action-primary-fg;
+  
+  > h2 {
+    font-size: 1.5em;
+    color: $base-font-color;
+  }
+  
+  .cart-table {
+    width: 100%;
+    
+    .cart-headings {
+      height: 35px;
+      
+      th {
+        text-align: left;
+        padding-left: 5px;
+        border-bottom: 1px solid $border-color-1;
+        
+        &.qty {
+          width: 100px;
+        }
+        &.u-pr {
+          width: 100px;
+        }
+        &.prc {
+          width: 150px;
+        }
+        &.cur {
+          width: 100px;
+        }
+      }
+    }
+    
+    .cart-items {
+      td {
+        padding: 10px 25px;
+      }
+    }
+    
+    .cart-totals {
+      td {
+        &.cart-total-cost {
+          font-size: 1.25em;
+          font-weight: bold;
+          padding: 10px 25px;
+        }
+      }
+    }
+  }
+  
+  table.order-receipt {
+    width: 100%;
+    
+    .order-number {
+      font-weight: bold;
+    }
+    .order-date {
+      text-align: right;
+    }
+    .items-ordered {
+      padding-top: 50px;
+    }
+    
+    tr {
+      
+    }
+    
+    th {
+      text-align: left;
+      padding: 25px 0 15px 0;
+      
+      &.qty {
+        width: 50px;
+      }
+      &.u-pr {
+        width: 100px;
+      }
+      &.pri {
+        width: 125px;
+      }
+      &.curr {
+        width: 75px;
+      }
+    }
+    tr.order-item {
+      td {
+        padding-bottom: 10px;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html
index 6abcd2998a82ede82cc4b2d40403f478a3223326..e63003b3ee2658070cde06b5a28a0e0434c36cb4 100644
--- a/lms/templates/courseware/course_about.html
+++ b/lms/templates/courseware/course_about.html
@@ -3,6 +3,8 @@
   from django.core.urlresolvers import reverse
   from courseware.courses import course_image_url, get_course_about_section
   from courseware.access import has_access
+
+  cart_link = reverse('shoppingcart.views.show_cart')
 %>
 <%namespace name='static' file='../static_content.html'/>
 
@@ -24,6 +26,29 @@
       $("#class_enroll_form").submit();
       event.preventDefault();
     });
+    add_course_complete_handler = function(jqXHR, textStatus) {
+      if (jqXHR.status == 200) {
+        location.href = "${cart_link}";
+      }
+      if (jqXHR.status == 400) {
+        $("#register_error")
+          .html(jqXHR.responseText ? jqXHR.responseText : "${_('An error occurred. Please try again later.')}")
+          .css("display", "block");
+      }
+      else if (jqXHR.status == 403) {
+          location.href = "${reg_then_add_to_cart_link}";
+      }
+    };
+    $("#add_to_cart_post").click(function(event){
+      $.ajax({
+        url: "${reverse('add_course_to_cart', args=[course.id])}",
+        type: "POST",
+        /* Rant: HAD TO USE COMPLETE B/C PROMISE.DONE FOR SOME REASON DOES NOT WORK ON THIS PAGE. */
+        complete: add_course_complete_handler
+      })
+      event.preventDefault();
+    });
+
 
     ## making the conditional around this entire JS block for sanity
     %if settings.MITX_FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
@@ -88,19 +113,42 @@
 
         <div class="main-cta">
         %if user.is_authenticated() and registered:
-	              %if show_courseware_link:
-                    <a href="${course_target}">
-                %endif
+          %if show_courseware_link:
+            <a href="${course_target}">
+          %endif
 
-                <span class="register disabled">${_("You are registered for this course {course.display_number_with_default}").format(course=course) | h}</span>
-                %if show_courseware_link:
-                    <strong>${_("View Courseware")}</strong>
-                    </a>
-                %endif
+          <span class="register disabled">
+            ${_("You are registered for this course {course.display_number_with_default}").format(course=course) | h}
+          </span>
+          %if show_courseware_link:
+            <strong>${_("View Courseware")}</strong>
+            </a>
+          %endif
 
+        %elif in_cart:
+          <span class="add-to-cart">
+            ${_('This course is in your <a href="{cart_link}">cart</a>.').format(cart_link=cart_link)}
+          </span>
+        %elif settings.MITX_FEATURES.get('ENABLE_PAID_COURSE_REGISTRATION') and registration_price:
+          <%
+          if user.is_authenticated():
+            reg_href = "#"
+            reg_element_id = "add_to_cart_post"
+          else:
+            reg_href = reg_then_add_to_cart_link
+            reg_element_id = "reg_then_add_to_cart"
+          %>
+          <a href="${reg_href}" class="add-to-cart" id="${reg_element_id}">
+            ${_("Add {course.display_number_with_default} to Cart ({currency_symbol}{cost})")\
+              .format(course=course, currency_symbol=settings.PAID_COURSE_REGISTRATION_CURRENCY[1],
+                      cost=registration_price)}
+          </a>
+          <div id="register_error"></div>
         %else:
-            <a href="#" class="register">${_("Register for {course.display_number_with_default}").format(course=course) | h}</a>
-            <div id="register_error"></div>
+          <a href="#" class="register">
+            ${_("Register for {course.display_number_with_default}").format(course=course) | h}
+          </a>
+          <div id="register_error"></div>
         %endif
         </div>
 
diff --git a/lms/templates/emails/order_confirmation_email.txt b/lms/templates/emails/order_confirmation_email.txt
index 74f945b06ad918a49909e296df61471c937daae2..65c0e3a67e71400f71d3942061b5fcf2956d758c 100644
--- a/lms/templates/emails/order_confirmation_email.txt
+++ b/lms/templates/emails/order_confirmation_email.txt
@@ -1,8 +1,13 @@
 <%! from django.utils.translation import ugettext as _ %>
 ${_("Hi {name}").format(name=order.user.profile.name)}
 
-${_("Your payment was successful. You will see the charge below on your next credit or debit card statement. The charge will show up on your statement under the company name {platform_name}. If you have billing questions, please read the FAQ ({faq_url}) or contact {billing_email}.").format(platform_name=settings.PLATFORM_NAME, billing_email=settings.PAYMENT_SUPPORT_EMAIL, faq_url=marketing_link('FAQ'))}
-
+${_("Your payment was successful. You will see the charge below on your next credit or debit card statement.")}
+${_("The charge will show up on your statement under the company name {merchant_name}.").format(merchant_name=settings.CC_MERCHANT_NAME)}
+% if marketing_link('FAQ'):
+${_("If you have billing questions, please read the FAQ ({faq_url}) or contact {billing_email}.").format(billing_email=settings.PAYMENT_SUPPORT_EMAIL, faq_url=marketing_link('FAQ'))}
+% else:
+${_("If you have billing questions, please contact {billing_email}.").format(billing_email=settings.PAYMENT_SUPPORT_EMAIL)}
+% endif
 ${_("-The {platform_name} Team").format(platform_name=settings.PLATFORM_NAME)}
 
 ${_("Your order number is: {order_number}").format(order_number=order.id)}
@@ -13,8 +18,18 @@ ${_("Quantity - Description - Price")}
 %for order_item in order_items:
     ${order_item.qty} - ${order_item.line_desc} - ${"$" if order_item.currency == 'usd' else ""}${order_item.line_cost} 
 %endfor
+
 ${_("Total billed to credit/debit card: {currency_symbol}{total_cost}").format(total_cost=order.total_cost, currency_symbol=("$" if order.currency == 'usd' else ""))}
 
+% if has_billing_info:
+${order.bill_to_cardtype} ${_("#:")} ${order.bill_to_ccnum}
+${order.bill_to_first} ${order.bill_to_last}
+${order.bill_to_street1}
+${order.bill_to_street2}
+${order.bill_to_city}, ${order.bill_to_state} ${order.bill_to_postalcode}
+${order.bill_to_country.upper()}
+% endif
+
 %for order_item in order_items:
     ${order_item.additional_instruction_text}
 %endfor
diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html
index 1dd5aa5229f8ca678f1f7c26ec32df3e411c38cf..8f6da00e68d22d7bb0f4af35c7757aa0dca97c1d 100644
--- a/lms/templates/navigation.html
+++ b/lms/templates/navigation.html
@@ -9,6 +9,8 @@ from django.utils.translation import ugettext as _
 import branding
 # app that handles site status messages
 from status.status import get_site_status_msg
+# shopping cart
+import shoppingcart
 %>
 
 ## Provide a hook for themes to inject branding on top.
@@ -79,7 +81,17 @@ site_status_msg = get_site_status_msg(course_id)
         </ul>
       </li>
     </ol>
-
+      % if settings.MITX_FEATURES.get('ENABLE_PAID_COURSE_REGISTRATION') and \
+           settings.MITX_FEATURES['ENABLE_SHOPPING_CART'] and \
+           shoppingcart.models.Order.user_cart_has_items(user):
+        <ol class="user">
+          <li class="primary">
+            <a class="shopping-cart" href="${reverse('shoppingcart.views.show_cart')}">
+              <i class="icon-shopping-cart"></i> Shopping Cart
+            </a>
+          </li>
+        </ol>
+      % endif
     % else:
     <ol class="left nav-global">
       <%block name="navigation_global_links">
diff --git a/lms/templates/shoppingcart/list.html b/lms/templates/shoppingcart/list.html
index cf452baab0de8c204d1b373e24fb6c5271e0dad7..820e05dc47bc4982b1aedf4616fda6d7e5edbbb3 100644
--- a/lms/templates/shoppingcart/list.html
+++ b/lms/templates/shoppingcart/list.html
@@ -7,47 +7,62 @@
 <%block name="title"><title>${_("Your Shopping Cart")}</title></%block>
 
 <section class="container cart-list">
-    <h2>${_("Your selected items:")}</h2>
-    % if shoppingcart_items:
-        <table>
-            <thead>
-            <tr>${_("<td>Quantity</td><td>Description</td><td>Unit Price</td><td>Price</td><td>Currency</td>")}</tr>
-            </thead>
-            <tbody>
-            % for item in shoppingcart_items:
-            <tr><td>${item.qty}</td><td>${item.line_desc}</td>
-                <td>${"{0:0.2f}".format(item.unit_cost)}</td><td>${"{0:0.2f}".format(item.line_cost)}</td>
-                <td>${item.currency.upper()}</td>
-                <td><a data-item-id="${item.id}" class='remove_line_item' href='#'>[x]</a></td></tr>
-            % endfor
-            <tr><td></td><td></td><td></td><td>${_("Total Amount")}</td></tr>
-            <tr><td></td><td></td><td></td><td>${"{0:0.2f}".format(amount)}</td></tr>
-
-            </tbody>
-        </table>
-        <!-- <input id="back_input" type="submit" value="Return" /> -->
-        ${form_html}
-    % else:
-        <p>${_("You have selected no items for purchase.")}</p>
-    % endif
+  <h2>${_("Your selected items:")}</h2>
+  % if shoppingcart_items:
+    <table class="cart-table">
+      <thead>
+        <tr class="cart-headings">
+          <th class="qty">${_("Quantity")}</th>
+          <th class="dsc">${_("Description")}</th>
+          <th class="u-pr">${_("Unit Price")}</th>
+          <th class="prc">${_("Price")}</th>
+          <th class="cur">${_("Currency")}</th>
+        </tr>
+      </thead>
+      <tbody>
+        % for item in shoppingcart_items:
+        <tr class="cart-items">
+          <td>${item.qty}</td>
+          <td>${item.line_desc}</td>
+          <td>${"{0:0.2f}".format(item.unit_cost)}</td>
+          <td>${"{0:0.2f}".format(item.line_cost)}</td>
+          <td>${item.currency.upper()}</td>
+          <td><a data-item-id="${item.id}" class='remove_line_item' href='#'>[x]</a></td>
+        </tr>
+        % endfor
+        <tr class="cart-headings">
+          <td colspan="4"></td>
+          <th>${_("Total Amount")}</th>
+        </tr>
+        <tr class="cart-totals">
+          <td colspan="4"></td>
+          <td class="cart-total-cost">${"{0:0.2f}".format(amount)}</td>
+        </tr>
+      </tbody>
+    </table>
+    <!-- <input id="back_input" type="submit" value="Return" /> -->
+    ${form_html}
+  % else:
+    <p>${_("You have selected no items for purchase.")}</p>
+  % endif
 
 </section>
 
 
 <script>
-    $(function() {
-        $('a.remove_line_item').click(function(event) {
-            event.preventDefault();
-            var post_url = "${reverse('shoppingcart.views.remove_item')}";
-            $.post(post_url, {id:$(this).data('item-id')})
-                   .always(function(data){
-                        location.reload(true);
-                    });
-        });
-
-        $('#back_input').click(function(){
-           history.back();
-        });
+  $(function() {
+    $('a.remove_line_item').click(function(event) {
+      event.preventDefault();
+      var post_url = "${reverse('shoppingcart.views.remove_item')}";
+      $.post(post_url, {id:$(this).data('item-id')})
+        .always(function(data){
+        location.reload(true);
+      });
     });
+
+    $('#back_input').click(function(){
+      history.back();
+    });
+  });
 </script>
 
diff --git a/lms/templates/shoppingcart/receipt.html b/lms/templates/shoppingcart/receipt.html
index 7802f88dea2c1e77a7d8a15fda51884556c8beab..20c8a4272cdafac8ba5f820e7538653d54fd1a41 100644
--- a/lms/templates/shoppingcart/receipt.html
+++ b/lms/templates/shoppingcart/receipt.html
@@ -3,70 +3,89 @@
 <%! from django.conf import settings %>
 
 <%inherit file="../main.html" />
-<%block name="bodyclass">register verification-process step-requirements</%block>
+<%block name="bodyclass">purchase-receipt</%block>
 
 <%block name="title"><title>${_("Register for [Course Name] | Receipt (Order")} ${order.id})</title></%block>
 
 <%block name="content">
 
-% if notification is not UNDEFINED:
-<section class="notification">
-    ${notification}
-</section>
-% endif
-
 <div class="container">
-  <section class="wrapper cart-list">
+  <section class="notification">
+    <h2>Thank you for your Purchase!</h2>
+    <p>Please print this receipt page for your records.  You should also have received a receipt in your email.</p>
+    % for inst in instructions:
+      <p>${inst}</p>
+    % endfor
+  </section>
 
+  <section class="wrapper cart-list">
     <div class="wrapper-content-main">
       <article class="content-main">
-        <h3 class="title">${_(settings.PLATFORM_NAME + " (" + settings.SITE_NAME + ")" + " Electronic Receipt")}</h3>
-
-
-
-    <h2>${_("Order #")}${order.id}</h2>
-    <h2>${_("Date:")} ${order.purchase_time.date().isoformat()}</h2>
-    <h2>${_("Items ordered:")}</h2>
+        <h1>${_(settings.PLATFORM_NAME + " (" + settings.SITE_NAME + ")" + " Electronic Receipt")}</h1>
+        <hr />
 
-        <table>
-            <thead>
-            <tr>${_("<td>Qty</td><td>Description</td><td>Unit Price</td><td>Price</td><td>Currency</td>")}</tr>
-            </thead>
-            <tbody>
-            % for item in order_items:
-            <tr>
-                % if item.status == "purchased":
-                <td>${item.qty}</td><td>${item.line_desc}</td>
+        <table class="order-receipt">
+          <tbody>
+          <tr>
+            <td colspan="2"><h3 class="order-number">${_("Order #")}${order.id}</h3></td>
+            <td></td>
+            <td colspan="2"><h3 class="order-date">${_("Date:")} ${order.purchase_time.date().isoformat()}</h3></td>
+          </tr>
+          <tr>
+            <td colspan="5"><h2 class="items-ordered">${_("Items ordered:")}</h2></td>
+          </tr>
+          <tr>
+            <th class="qty">${_("Qty")}</th>
+            <th class="desc">${_("Description")}</th>
+            <th class="u-pr">${_("Unit Price")}</th>
+            <th class="pri">${_("Price")}</th>
+            <th class="curr">${_("Currency")}</th>
+          </tr>
+          % for item in order_items:
+            <tr class="order-item">
+              % if item.status == "purchased":
+                <td>${item.qty}</td>
+                <td>${item.line_desc}</td>
                 <td>${"{0:0.2f}".format(item.unit_cost)}</td>
                 <td>${"{0:0.2f}".format(item.line_cost)}</td>
                 <td>${item.currency.upper()}</td></tr>
-                % elif item.status == "refunded":
-                <td><del>${item.qty}</del></td><td><del>${item.line_desc}</del></td>
+              % elif item.status == "refunded":
+                <td><del>${item.qty}</del></td>
+                <td><del>${item.line_desc}</del></td>
                 <td><del>${"{0:0.2f}".format(item.unit_cost)}</del></td>
                 <td><del>${"{0:0.2f}".format(item.line_cost)}</del></td>
                 <td><del>${item.currency.upper()}</del></td></tr>
-                % endif
-            % endfor
-            <tr><td></td><td></td><td></td><td>${_("Total Amount")}</td></tr>
-            <tr><td></td><td></td><td></td><td>${"{0:0.2f}".format(order.total_cost)}</td></tr>
-            </tbody>
+              % endif
+          % endfor
+          <tr>
+            <td colspan="3"></td>
+            <th>${_("Total Amount")}</th>
+            <td></td>
+          </tr>
+          <tr>
+            <td colspan="3"></td>
+            <td>${"{0:0.2f}".format(order.total_cost)}</td>
+            <td></td>
+          </tr>
+          </tbody>
         </table>
-    % if any_refunds:
-    <p>
-        ${_("Note: items with strikethough like ")}<del>this</del>${_(" have been refunded.")}
-    </p>
-    % endif
 
-    <h2>${_("Billed To:")}</h2>
-    <p>
-        ${order.bill_to_cardtype} ${_("#:")} ${order.bill_to_ccnum}<br />
-        ${order.bill_to_first} ${order.bill_to_last}<br />
-        ${order.bill_to_street1}<br />
-        ${order.bill_to_street2}<br />
-        ${order.bill_to_city}, ${order.bill_to_state} ${order.bill_to_postalcode}<br />
-        ${order.bill_to_country.upper()}<br />
-    </p>
+        % if any_refunds:
+          <p>
+            ${_("Note: items with strikethough like ")}<del>this</del>${_(" have been refunded.")}
+          </p>
+        % endif
 
+        <h2>${_("Billed To:")}</h2>
+        <p>
+          ${order.bill_to_cardtype} ${_("#:")} ${order.bill_to_ccnum}<br />
+          ${order.bill_to_first} ${order.bill_to_last}<br />
+          ${order.bill_to_street1}<br />
+          ${order.bill_to_street2}<br />
+          ${order.bill_to_city}, ${order.bill_to_state} ${order.bill_to_postalcode}<br />
+          ${order.bill_to_country.upper()}<br />
+        </p>
+    </div>
   </section>
 </div>
 </%block>