From 86a4710ee8c2756fe3c44bc423426058d3c1cf71 Mon Sep 17 00:00:00 2001
From: vkaracic <karacicvedran@gmail.com>
Date: Wed, 17 Feb 2016 18:12:34 +0100
Subject: [PATCH] Otto checkout flow

---
 common/djangoapps/course_modes/views.py       | 52 ++++++++----
 common/djangoapps/student/helpers.py          |  2 +-
 common/djangoapps/student/views.py            | 10 ++-
 lms/djangoapps/commerce/admin.py              |  8 ++
 .../migrations/0002_commerceconfiguration.py  | 32 +++++++
 lms/djangoapps/commerce/models.py             | 24 +++++-
 lms/djangoapps/commerce/tests/test_utils.py   | 47 +++++++++-
 lms/djangoapps/commerce/utils.py              | 31 +++++++
 lms/djangoapps/courseware/tests/test_views.py | 85 ++++++++++++++-----
 lms/djangoapps/courseware/views.py            | 50 ++++++++---
 .../student_account/views/FinishAuthView.js   |  2 +-
 lms/templates/course_modes/choose.html        |  8 +-
 lms/templates/courseware/course_about.html    | 18 +++-
 .../dashboard/_dashboard_course_listing.html  |  6 +-
 .../lms/templates/course_modes/choose.html    |  8 +-
 15 files changed, 322 insertions(+), 61 deletions(-)
 create mode 100644 lms/djangoapps/commerce/admin.py
 create mode 100644 lms/djangoapps/commerce/migrations/0002_commerceconfiguration.py

diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py
index ddbf5715878..eb23262f215 100644
--- a/common/djangoapps/course_modes/views.py
+++ b/common/djangoapps/course_modes/views.py
@@ -3,28 +3,27 @@ Views for the course_mode module
 """
 
 import decimal
-from ipware.ip import get_ip
 
+from django.contrib.auth.decorators import login_required
 from django.core.urlresolvers import reverse
 from django.db import transaction
 from django.http import HttpResponse, HttpResponseBadRequest
 from django.shortcuts import redirect
-from django.views.generic.base import View
-from django.utils.translation import ugettext as _
-from django.contrib.auth.decorators import login_required
 from django.utils.decorators import method_decorator
+from django.utils.translation import ugettext as _
+from django.views.generic.base import View
+from ipware.ip import get_ip
+from opaque_keys.edx.keys import CourseKey
+from opaque_keys.edx.locations import SlashSeparatedCourseKey
+from xmodule.modulestore.django import modulestore
 
-from edxmako.shortcuts import render_to_response
-
+from lms.djangoapps.commerce.utils import EcommerceService
 from course_modes.models import CourseMode
 from courseware.access import has_access
+from edxmako.shortcuts import render_to_response
+from embargo import api as embargo_api
 from student.models import CourseEnrollment
-from opaque_keys.edx.locations import SlashSeparatedCourseKey
-from opaque_keys.edx.keys import CourseKey
 from util.db import outer_atomic
-from xmodule.modulestore.django import modulestore
-
-from embargo import api as embargo_api
 
 
 class ChooseModeView(View):
@@ -39,7 +38,14 @@ class ChooseModeView(View):
     """
 
     @method_decorator(transaction.non_atomic_requests)
-    def dispatch(self, *args, **kwargs):        # pylint: disable=missing-docstring
+    def dispatch(self, *args, **kwargs):
+        """Disable atomicity for the view.
+
+        Otherwise, we'd be unable to commit to the database until the
+        request had concluded; Django will refuse to commit when an
+        atomic() block is active, since that would break atomicity.
+
+        """
         return super(ChooseModeView, self).dispatch(*args, **kwargs)
 
     @method_decorator(login_required)
@@ -117,7 +123,10 @@ class ChooseModeView(View):
         )
 
         context = {
-            "course_modes_choose_url": reverse("course_modes_choose", kwargs={'course_id': course_key.to_deprecated_string()}),
+            "course_modes_choose_url": reverse(
+                "course_modes_choose",
+                kwargs={'course_id': course_key.to_deprecated_string()}
+            ),
             "modes": modes,
             "has_credit_upsell": has_credit_upsell,
             "course_name": course.display_name_with_default_escaped,
@@ -129,15 +138,22 @@ class ChooseModeView(View):
             "nav_hidden": True,
         }
         if "verified" in modes:
+            verified_mode = modes["verified"]
             context["suggested_prices"] = [
                 decimal.Decimal(x.strip())
-                for x in modes["verified"].suggested_prices.split(",")
+                for x in verified_mode.suggested_prices.split(",")
                 if x.strip()
             ]
-            context["currency"] = modes["verified"].currency.upper()
-            context["min_price"] = modes["verified"].min_price
-            context["verified_name"] = modes["verified"].name
-            context["verified_description"] = modes["verified"].description
+            context["currency"] = verified_mode.currency.upper()
+            context["min_price"] = verified_mode.min_price
+            context["verified_name"] = verified_mode.name
+            context["verified_description"] = verified_mode.description
+
+            if verified_mode.sku:
+                ecommerce_service = EcommerceService()
+                context["use_ecommerce_payment_flow"] = ecommerce_service.is_enabled()
+                context["ecommerce_payment_page"] = ecommerce_service.payment_page_url()
+                context["sku"] = verified_mode.sku
 
         return render_to_response("course_modes/choose.html", context)
 
diff --git a/common/djangoapps/student/helpers.py b/common/djangoapps/student/helpers.py
index 4e882e5dcca..a8c4e500fff 100644
--- a/common/djangoapps/student/helpers.py
+++ b/common/djangoapps/student/helpers.py
@@ -207,7 +207,7 @@ def get_next_url_for_login_page(request):
     """
     Determine the URL to redirect to following login/registration/third_party_auth
 
-    The user is currently on a login or reigration page.
+    The user is currently on a login or registration page.
     If 'course_id' is set, or other POST_AUTH_PARAMS, we will need to send the user to the
     /account/finish_auth/ view following login, which will take care of auto-enrollment in
     the specified course.
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index f6a4b5f2c28..86c8d8d5564 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -39,7 +39,6 @@ from django.template.response import TemplateResponse
 
 from ratelimitbackend.exceptions import RateLimitException
 
-
 from social.apps.django_app import utils as social_utils
 from social.backends import oauth as social_oauth
 from social.exceptions import AuthException, AuthAlreadyAssociated
@@ -55,6 +54,7 @@ from student.models import (
     create_comments_service_user, PasswordHistory, UserSignupSource,
     DashboardConfiguration, LinkedInAddToProfileConfiguration, ManualEnrollmentAudit, ALLOWEDTOENROLL_TO_ENROLLED)
 from student.forms import AccountCreationForm, PasswordResetFormNoActive, get_registration_extension_form
+from lms.djangoapps.commerce.utils import EcommerceService  # pylint: disable=import-error
 from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification  # pylint: disable=import-error
 from certificates.models import CertificateStatuses, certificate_status_for_student
 from certificates.api import (  # pylint: disable=import-error
@@ -502,6 +502,7 @@ def complete_course_mode_info(course_id, enrollment, modes=None):
     # if verified is an option.
     if CourseMode.VERIFIED in modes and enrollment.mode in CourseMode.UPSELL_TO_VERIFIED_MODES:
         mode_info['show_upsell'] = True
+        mode_info['verified_sku'] = modes['verified'].sku
         # if there is an expiration date, find out how long from now it is
         if modes['verified'].expiration_datetime:
             today = datetime.datetime.now(UTC).date()
@@ -737,6 +738,13 @@ def dashboard(request):
         'xseries_credentials': xseries_credentials,
     }
 
+    ecommerce_service = EcommerceService()
+    if ecommerce_service.is_enabled():
+        context.update({
+            'use_ecommerce_payment_flow': True,
+            'ecommerce_payment_page': ecommerce_service.payment_page_url(),
+        })
+
     return render_to_response('dashboard.html', context)
 
 
diff --git a/lms/djangoapps/commerce/admin.py b/lms/djangoapps/commerce/admin.py
new file mode 100644
index 00000000000..ad49323a139
--- /dev/null
+++ b/lms/djangoapps/commerce/admin.py
@@ -0,0 +1,8 @@
+""" Admin site bindings for commerce app. """
+
+from django.contrib import admin
+
+from commerce.models import CommerceConfiguration
+from config_models.admin import ConfigurationModelAdmin
+
+admin.site.register(CommerceConfiguration, ConfigurationModelAdmin)
diff --git a/lms/djangoapps/commerce/migrations/0002_commerceconfiguration.py b/lms/djangoapps/commerce/migrations/0002_commerceconfiguration.py
new file mode 100644
index 00000000000..f5bc5ef9098
--- /dev/null
+++ b/lms/djangoapps/commerce/migrations/0002_commerceconfiguration.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('commerce', '0001_data__add_ecommerce_service_user'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='CommerceConfiguration',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
+                ('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
+                ('checkout_on_ecommerce_service', models.BooleanField(default=False, help_text='Use the checkout page hosted by the E-Commerce service.')),
+                ('single_course_checkout_page', models.CharField(default=b'/basket/single-item/', help_text='Path to single course checkout page hosted by the E-Commerce service.', max_length=255)),
+                ('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')),
+            ],
+            options={
+                'ordering': ('-change_date',),
+                'abstract': False,
+            },
+        ),
+    ]
diff --git a/lms/djangoapps/commerce/models.py b/lms/djangoapps/commerce/models.py
index 32ec058432a..3a78bc08416 100644
--- a/lms/djangoapps/commerce/models.py
+++ b/lms/djangoapps/commerce/models.py
@@ -1,3 +1,25 @@
 """
-This file is intentionally empty. Django 1.6 and below require a models.py file for all apps.
+Commerce-related models.
 """
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from config_models.models import ConfigurationModel
+
+
+class CommerceConfiguration(ConfigurationModel):
+    """ Commerce configuration """
+
+    checkout_on_ecommerce_service = models.BooleanField(
+        default=False,
+        help_text=_('Use the checkout page hosted by the E-Commerce service.')
+    )
+
+    single_course_checkout_page = models.CharField(
+        max_length=255,
+        default='/basket/single-item/',
+        help_text=_('Path to single course checkout page hosted by the E-Commerce service.')
+    )
+
+    def __unicode__(self):
+        return "Commerce configuration"
diff --git a/lms/djangoapps/commerce/tests/test_utils.py b/lms/djangoapps/commerce/tests/test_utils.py
index df25b7e7b9e..90c446982e1 100644
--- a/lms/djangoapps/commerce/tests/test_utils.py
+++ b/lms/djangoapps/commerce/tests/test_utils.py
@@ -1,8 +1,10 @@
 """Tests of commerce utilities."""
 from django.test import TestCase
+from django.test.utils import override_settings
 from mock import patch
 
-from commerce.utils import audit_log
+from commerce.utils import audit_log, EcommerceService
+from commerce.models import CommerceConfiguration
 
 
 class AuditLogTests(TestCase):
@@ -16,3 +18,46 @@ class AuditLogTests(TestCase):
         # key-value pairs ordered alphabetically by key.
         message = 'foo: bar="baz", qux="quux"'
         self.assertTrue(mock_log.info.called_with(message))
+
+
+class EcommerceServiceTests(TestCase):
+    """Tests for the EcommerceService helper class."""
+    SKU = 'TESTSKU'
+
+    def setUp(self):
+        CommerceConfiguration.objects.create(
+            checkout_on_ecommerce_service=True,
+            single_course_checkout_page='/test_basket/'
+        )
+        super(EcommerceServiceTests, self).setUp()
+
+    def test_is_enabled(self):
+        """Verify that is_enabled() returns True when ecomm checkout is enabled. """
+        is_enabled = EcommerceService().is_enabled()
+        self.assertTrue(is_enabled)
+
+        config = CommerceConfiguration.current()
+        config.checkout_on_ecommerce_service = False
+        config.save()
+        is_not_enabled = EcommerceService().is_enabled()
+        self.assertFalse(is_not_enabled)
+
+    @patch('openedx.core.djangoapps.theming.helpers.is_request_in_themed_site')
+    def test_is_enabled_for_microsites(self, is_microsite):
+        """Verify that is_enabled() returns False if used for a microsite."""
+        is_microsite.return_value = True
+        is_not_enabled = EcommerceService().is_enabled()
+        self.assertFalse(is_not_enabled)
+
+    @override_settings(ECOMMERCE_PUBLIC_URL_ROOT='http://ecommerce_url')
+    def test_payment_page_url(self):
+        """Verify that the proper URL is returned."""
+        url = EcommerceService().payment_page_url()
+        self.assertEqual(url, 'http://ecommerce_url/test_basket/')
+
+    @override_settings(ECOMMERCE_PUBLIC_URL_ROOT='http://ecommerce_url')
+    def test_checkout_page_url(self):
+        """ Verify the checkout page URL is properly constructed and returned. """
+        url = EcommerceService().checkout_page_url(self.SKU)
+        expected_url = 'http://ecommerce_url/test_basket/?sku={}'.format(self.SKU)
+        self.assertEqual(url, expected_url)
diff --git a/lms/djangoapps/commerce/utils.py b/lms/djangoapps/commerce/utils.py
index 4d25a58a41d..00ff3bb99f4 100644
--- a/lms/djangoapps/commerce/utils.py
+++ b/lms/djangoapps/commerce/utils.py
@@ -1,6 +1,11 @@
 """Utilities to assist with commerce tasks."""
 import logging
+from urlparse import urljoin
 
+from django.conf import settings
+
+from commerce.models import CommerceConfiguration
+from openedx.core.djangoapps.theming import helpers
 
 log = logging.getLogger(__name__)
 
@@ -32,3 +37,29 @@ def audit_log(name, **kwargs):
     message = u'{name}: {payload}'.format(name=name, payload=payload)
 
     log.info(message)
+
+
+class EcommerceService(object):
+    """ Helper class for ecommerce service integration. """
+    def __init__(self):
+        self.config = CommerceConfiguration.current()
+
+    def is_enabled(self):
+        """ Check if the service is enabled and that the site is not a microsite. """
+        return self.config.checkout_on_ecommerce_service and not helpers.is_request_in_themed_site()
+
+    def payment_page_url(self):
+        """ Return the URL for the checkout page.
+
+        Example:
+            http://localhost:8002/basket/single_item/
+        """
+        return urljoin(settings.ECOMMERCE_PUBLIC_URL_ROOT, self.config.single_course_checkout_page)
+
+    def checkout_page_url(self, sku):
+        """ Construct the URL to the ecommerce checkout page and include a product.
+
+        Example:
+            http://localhost:8002/basket/single_item/?sku=5H3HG5
+        """
+        return "{}?sku={}".format(self.payment_page_url(), sku)
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index 5914b836712..889fafa4f66 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -31,6 +31,7 @@ import shoppingcart
 from certificates import api as certs_api
 from certificates.models import CertificateStatuses, CertificateGenerationConfiguration
 from certificates.tests.factories import GeneratedCertificateFactory
+from commerce.models import CommerceConfiguration
 from course_modes.models import CourseMode
 from course_modes.tests.factories import CourseModeFactory
 from courseware.model_data import set_score
@@ -69,21 +70,21 @@ class TestJumpTo(ModuleStoreTestCase):
         location = self.course_key.make_usage_key(None, 'NoSuchPlace')
         # This is fragile, but unfortunately the problem is that within the LMS we
         # can't use the reverse calls from the CMS
-        jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string())
+        jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', unicode(self.course_key), unicode(location))
         response = self.client.get(jumpto_url)
         self.assertEqual(response.status_code, 404)
 
     @unittest.skip
     def test_jumpto_from_chapter(self):
         location = self.course_key.make_usage_key('chapter', 'Overview')
-        jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string())
+        jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', unicode(self.course_key), unicode(location))
         expected = 'courses/edX/toy/2012_Fall/courseware/Overview/'
         response = self.client.get(jumpto_url)
         self.assertRedirects(response, expected, status_code=302, target_status_code=302)
 
     @unittest.skip
     def test_jumpto_id(self):
-        jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), 'Overview')
+        jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', unicode(self.course_key), 'Overview')
         expected = 'courses/edX/toy/2012_Fall/courseware/Overview/'
         response = self.client.get(jumpto_url)
         self.assertRedirects(response, expected, status_code=302, target_status_code=302)
@@ -173,7 +174,7 @@ class TestJumpTo(ModuleStoreTestCase):
 
     def test_jumpto_id_invalid_location(self):
         location = Location('edX', 'toy', 'NoSuchPlace', None, None, None)
-        jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string())
+        jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', unicode(self.course_key), unicode(location))
         response = self.client.get(jumpto_url)
         self.assertEqual(response.status_code, 404)
 
@@ -212,26 +213,72 @@ class ViewsTestCase(ModuleStoreTestCase):
         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.to_deprecated_string()]))
+        request = self.request_factory.get(reverse('about_course', args=[unicode(course.id)]))
         request.user = AnonymousUser()
         mako_middleware_process_request(request)
-        response = views.course_about(request, course.id.to_deprecated_string())
+        response = views.course_about(request, unicode(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.to_deprecated_string())
+        response = views.course_about(request, unicode(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.to_deprecated_string())
+        response = views.course_about(request, unicode(course.id))
         self.assertEqual(response.status_code, 200)
         self.assertIn(in_cart_span, response.content)
 
+    def assert_enrollment_link_present(self, is_anonymous, _id=False):
+        """
+        Prepare ecommerce checkout data and assert if the ecommerce link is contained in the response.
+
+        Arguments:
+            is_anonymous(bool): Tell the method to use an anonymous user or the logged in one.
+            _id(bool): Tell the method to either expect an id in the href or not.
+
+        """
+        checkout_page = '/test_basket/'
+        sku = 'TEST123'
+        CommerceConfiguration.objects.create(
+            checkout_on_ecommerce_service=True,
+            single_course_checkout_page=checkout_page
+        )
+        course = CourseFactory.create()
+        CourseModeFactory(mode_slug=CourseMode.PROFESSIONAL, course_id=course.id, sku=sku, min_price=1)
+
+        request = self.request_factory.get(reverse('about_course', args=[unicode(course.id)]))
+        request.user = AnonymousUser() if is_anonymous else self.user
+        mako_middleware_process_request(request)
+
+        # Construct the link for each of the four possibilities:
+        #      (1) shopping cart is disabled and the user is not logged in
+        #      (2) shopping cart is disabled and the user is logged in
+        #      (3) shopping cart is enabled and the user is not logged in
+        #      (4) shopping cart is enabled and the user is logged in
+        href = '<a href="{}?{}" class="add-to-cart"{}'.format(
+            checkout_page,
+            'sku=TEST123',
+            ' id="">' if _id else ">"
+        )
+        response = views.course_about(request, unicode(course.id))
+        self.assertEqual(response.status_code, 200)
+        self.assertIn(href, response.content)
+
+    @ddt.data(True, False)
+    def test_ecommerce_checkout(self, is_anonymous):
+        self.assert_enrollment_link_present(is_anonymous=is_anonymous)
+
+    @ddt.data(True, False)
+    @unittest.skipUnless(settings.FEATURES.get('ENABLE_SHOPPING_CART'), 'Shopping Cart not enabled in settings')
+    @patch.dict(settings.FEATURES, {'ENABLE_PAID_COURSE_REGISTRATION': True})
+    def test_ecommerce_checkout_shopping_cart_enabled(self, is_anonymous):
+        self.assert_enrollment_link_present(is_anonymous=is_anonymous, _id=True)
+
     def test_user_groups(self):
         # depreciated function
         mock_user = MagicMock()
@@ -268,7 +315,7 @@ class ViewsTestCase(ModuleStoreTestCase):
     def test_index_invalid_position(self):
         request_url = '/'.join([
             '/courses',
-            self.course.id.to_deprecated_string(),
+            unicode(self.course.id),
             'courseware',
             self.chapter.location.name,
             self.section.location.name,
@@ -281,7 +328,7 @@ class ViewsTestCase(ModuleStoreTestCase):
     def test_unicode_handling_in_url(self):
         url_parts = [
             '/courses',
-            self.course.id.to_deprecated_string(),
+            unicode(self.course.id),
             'courseware',
             self.chapter.location.name,
             self.section.location.name,
@@ -373,9 +420,9 @@ class ViewsTestCase(ModuleStoreTestCase):
         self.client.login(username=admin.username, password='test')
 
         url = reverse('submission_history', kwargs={
-            'course_id': self.course_key.to_deprecated_string(),
+            'course_id': unicode(self.course_key),
             'student_username': 'dummy',
-            'location': self.component.location.to_deprecated_string(),
+            'location': unicode(self.component.location),
         })
         response = self.client.get(url)
         # Tests that we do not get an "Invalid x" response when passing correct arguments to view
@@ -389,7 +436,7 @@ class ViewsTestCase(ModuleStoreTestCase):
 
         # try it with an existing user and a malicious location
         url = reverse('submission_history', kwargs={
-            'course_id': self.course_key.to_deprecated_string(),
+            'course_id': unicode(self.course_key),
             'student_username': 'dummy',
             'location': '<script>alert("hello");</script>'
         })
@@ -398,7 +445,7 @@ class ViewsTestCase(ModuleStoreTestCase):
 
         # try it with a malicious user and a non-existent location
         url = reverse('submission_history', kwargs={
-            'course_id': self.course_key.to_deprecated_string(),
+            'course_id': unicode(self.course_key),
             'student_username': '<script>alert("hello");</script>',
             'location': 'dummy'
         })
@@ -697,7 +744,7 @@ class TestProgressDueDate(BaseDueDateTests):
         """ Returns the HTML for the progress page """
 
         mako_middleware_process_request(self.request)
-        return views.progress(self.request, course_id=course.id.to_deprecated_string(), student_id=self.user.id).content
+        return views.progress(self.request, course_id=unicode(course.id), student_id=self.user.id).content
 
 
 class TestAccordionDueDate(BaseDueDateTests):
@@ -742,7 +789,7 @@ class StartDateTests(ModuleStoreTestCase):
         """
         Get the text of the /about page for the course.
         """
-        text = views.course_about(self.request, course_key.to_deprecated_string()).content
+        text = views.course_about(self.request, unicode(course_key)).content
         return text
 
     @patch('util.date_utils.pgettext', fake_pgettext(translations={
@@ -816,7 +863,7 @@ class ProgressPageTests(ModuleStoreTestCase):
     def test_pure_ungraded_xblock(self):
         ItemFactory.create(category='acid', parent_location=self.vertical.location)
 
-        resp = views.progress(self.request, course_id=self.course.id.to_deprecated_string())
+        resp = views.progress(self.request, course_id=unicode(self.course.id))
         self.assertEqual(resp.status_code, 200)
 
     @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
@@ -845,12 +892,12 @@ class ProgressPageTests(ModuleStoreTestCase):
 
         # Enroll student into course
         CourseEnrollment.enroll(self.user, self.course.id)
-        resp = views.progress(self.request, course_id=self.course.id.to_deprecated_string(), student_id=self.user.id)
+        resp = views.progress(self.request, course_id=unicode(self.course.id), student_id=self.user.id)
         # Assert that valid 'student_id' returns 200 status
         self.assertEqual(resp.status_code, 200)
 
     def test_non_asci_grade_cutoffs(self):
-        resp = views.progress(self.request, course_id=self.course.id.to_deprecated_string())
+        resp = views.progress(self.request, course_id=unicode(self.course.id))
 
         self.assertEqual(resp.status_code, 200)
 
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index 11d69b9176c..60e16b873e5 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -39,6 +39,7 @@ import survey.utils
 import survey.views
 from certificates import api as certs_api
 from openedx.core.lib.gating import api as gating_api
+from commerce.utils import EcommerceService
 from course_modes.models import CourseMode
 from courseware import grades
 from courseware.access import has_access, has_ccx_coach_role, _adjust_start_date_for_beta_testers
@@ -63,13 +64,13 @@ from courseware.url_helpers import get_redirect_url
 from courseware.user_state_client import DjangoXBlockUserStateClient
 from edxmako.shortcuts import render_to_response, render_to_string, marketing_link
 from instructor.enrollment import uses_shib
-from microsite_configuration import microsite
 from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
 from openedx.core.djangoapps.credit.api import (
     get_credit_requirement_status,
     is_user_eligible_for_credit,
     is_credit_course
 )
+from openedx.core.djangoapps.theming import helpers as theming_helpers
 from shoppingcart.models import CourseRegistrationCode
 from shoppingcart.utils import is_shopping_cart_enabled
 from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
@@ -143,8 +144,10 @@ def courses(request):
     if not settings.FEATURES.get('ENABLE_COURSE_DISCOVERY'):
         courses_list = get_courses(request.user)
 
-        if microsite.get_value("ENABLE_COURSE_SORTING_BY_START_DATE",
-                               settings.FEATURES["ENABLE_COURSE_SORTING_BY_START_DATE"]):
+        if theming_helpers.get_value(
+                "ENABLE_COURSE_SORTING_BY_START_DATE",
+                settings.FEATURES["ENABLE_COURSE_SORTING_BY_START_DATE"]
+        ):
             courses_list = sort_by_start_date(courses_list)
         else:
             courses_list = sort_by_announcement(courses_list)
@@ -508,7 +511,7 @@ def _index_bulk_op(request, course_key, chapter, section, position):
                     return redirect(reverse('courseware', args=[course.id.to_deprecated_string()]))
                 raise Http404
 
-            ## Allow chromeless operation
+            # Allow chromeless operation
             if section_descriptor.chrome:
                 chrome = [s.strip() for s in section_descriptor.chrome.lower().split(",")]
                 if 'accordion' not in chrome:
@@ -855,8 +858,9 @@ def course_about(request, course_id):
     with modulestore().bulk_operations(course_key):
         permission = get_permission_for_course_about()
         course = get_course_with_access(request.user, permission, course_key)
+        modes = CourseMode.modes_for_course_dict(course_key)
 
-        if microsite.get_value('ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False)):
+        if theming_helpers.get_value('ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False)):
             return redirect(reverse('info', args=[course.id.to_deprecated_string()]))
 
         registered = registered_for_course(course, request.user)
@@ -871,10 +875,9 @@ def course_about(request, course_id):
 
         show_courseware_link = bool(
             (
-                has_access(request.user, 'load', course)
-                and has_access(request.user, 'view_courseware_with_prerequisites', course)
-            )
-            or settings.FEATURES.get('ENABLE_LMS_MIGRATION')
+                has_access(request.user, 'load', course) and
+                has_access(request.user, 'view_courseware_with_prerequisites', course)
+            ) or settings.FEATURES.get('ENABLE_LMS_MIGRATION')
         )
 
         # Note: this is a flow for payment for course registration, not the Verified Certificate flow.
@@ -884,15 +887,31 @@ def course_about(request, course_id):
 
         _is_shopping_cart_enabled = is_shopping_cart_enabled()
         if _is_shopping_cart_enabled:
-            registration_price = CourseMode.min_course_price_for_currency(course_key,
-                                                                          settings.PAID_COURSE_REGISTRATION_CURRENCY[0])
+            registration_price = CourseMode.min_course_price_for_currency(
+                course_key,
+                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_key) or \
                     shoppingcart.models.CourseRegCodeItem.contained_in_order(cart, course_key)
 
             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=urllib.quote(str(course_id)))
+                reg_url=reverse('register_user'), course_id=urllib.quote(str(course_id))
+            )
+
+        # If the ecommerce checkout flow is enabled and the mode of the course is
+        # professional or no id professional, we construct links for the enrollment
+        # button to add the course to the ecommerce basket.
+        ecommerce_checkout_link = ''
+        professional_mode = ''
+        ecomm_service = EcommerceService()
+        if ecomm_service.is_enabled() and (
+                CourseMode.PROFESSIONAL in modes or CourseMode.NO_ID_PROFESSIONAL_MODE in modes
+        ):
+            professional_mode = modes.get(CourseMode.PROFESSIONAL, '') or \
+                modes.get(CourseMode.NO_ID_PROFESSIONAL_MODE, '')
+            ecommerce_checkout_link = ecomm_service.checkout_page_url(professional_mode.sku)
 
         course_price = get_cosmetic_display_price(course, registration_price)
         can_add_course_to_cart = _is_shopping_cart_enabled and registration_price
@@ -925,6 +944,9 @@ def course_about(request, course_id):
             'is_cosmetic_price_enabled': settings.FEATURES.get('ENABLE_COSMETIC_DISPLAY_PRICE'),
             'course_price': course_price,
             'in_cart': in_cart,
+            'ecommerce_checkout': ecomm_service.is_enabled(),
+            'ecommerce_checkout_link': ecommerce_checkout_link,
+            'professional_mode': professional_mode,
             'reg_then_add_to_cart_link': reg_then_add_to_cart_link,
             'show_courseware_link': show_courseware_link,
             'is_course_full': is_course_full,
@@ -1577,12 +1599,12 @@ def financial_assistance_form(request):
     enrolled_courses = [
         {'name': enrollment.course_overview.display_name, 'value': unicode(enrollment.course_id)}
         for enrollment in CourseEnrollment.enrollments_for_user(user).order_by('-created')
-        if CourseMode.objects.filter(
+
+        if enrollment.mode != CourseMode.VERIFIED and CourseMode.objects.filter(
             Q(_expiration_datetime__isnull=True) | Q(_expiration_datetime__gt=datetime.now(UTC())),
             course_id=enrollment.course_id,
             mode_slug=CourseMode.VERIFIED
         ).exists()
-        and enrollment.mode != CourseMode.VERIFIED
     ]
     return render_to_response('financial-assistance/apply.html', {
         'header_text': FINANCIAL_ASSISTANCE_HEADER,
diff --git a/lms/static/js/student_account/views/FinishAuthView.js b/lms/static/js/student_account/views/FinishAuthView.js
index 72d66bf2985..8ec1da6ab70 100644
--- a/lms/static/js/student_account/views/FinishAuthView.js
+++ b/lms/static/js/student_account/views/FinishAuthView.js
@@ -51,7 +51,7 @@
                     enrollmentAction: $.url( '?enrollment_action' ),
                     courseId: $.url( '?course_id' ),
                     courseMode: $.url( '?course_mode' ),
-                    emailOptIn: $.url( '?email_opt_in')
+                    emailOptIn: $.url( '?email_opt_in' )
                 };
                 for (var key in queryParams) {
                     if (queryParams[key]) {
diff --git a/lms/templates/course_modes/choose.html b/lms/templates/course_modes/choose.html
index b15250a7335..f79fae05dc1 100644
--- a/lms/templates/course_modes/choose.html
+++ b/lms/templates/course_modes/choose.html
@@ -21,7 +21,7 @@ from django.core.urlresolvers import reverse
             } else {
                 title.attr("aria-expanded", "false");
             }
-        }
+        };
 
         $(document).ready(function() {
             $('.expandable-area').slideUp();
@@ -38,6 +38,12 @@ from django.core.urlresolvers import reverse
                 $('#contribution-other').attr('checked',true);
             });
 
+            % if use_ecommerce_payment_flow:
+            $('input[name=verified_mode]').click(function(e){
+                e.preventDefault();
+                window.location.href = '${ecommerce_payment_page}?sku=${sku}';
+            });
+            % endif
         });
     </script>
 </%block>
diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html
index 26fc56afd27..b24c1510b7b 100644
--- a/lms/templates/courseware/course_about.html
+++ b/lms/templates/courseware/course_about.html
@@ -39,6 +39,7 @@ from openedx.core.lib.courses import course_image_url
             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.to_deprecated_string()])}",
@@ -152,14 +153,27 @@ from openedx.core.lib.courses import course_image_url
             reg_href = reg_then_add_to_cart_link
             reg_element_id = "reg_then_add_to_cart"
           %>
+          <% if ecommerce_checkout:
+              reg_href = ecommerce_checkout_link
+              reg_element_id = ""
+          %>
           <a href="${reg_href}" class="add-to-cart" id="${reg_element_id}">
             ${_("Add {course_name} to Cart <span>({price} USD)</span>")\
               .format(course_name=course.display_number_with_default, price=course_price)}
-
           </a>
           <div id="register_error"></div>
         %else:
-          <a href="#" class="register">
+          <% 
+            if ecommerce_checkout:
+              reg_href = ecommerce_checkout_link
+            else:
+              reg_href="#"
+            if professional_mode:
+              href_class = "add-to-cart"
+            else:
+              href_class = "register"
+          %>
+          <a href="${reg_href}" class="${href_class}">
             ${_("Enroll in {course_name}").format(course_name=course.display_number_with_default) | h}
           </a>
           <div id="register_error"></div>
diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html
index 1852a21db25..b862f3556ac 100644
--- a/lms/templates/dashboard/_dashboard_course_listing.html
+++ b/lms/templates/dashboard/_dashboard_course_listing.html
@@ -330,7 +330,11 @@ from student.helpers import (
                     ${_("It's official. It's easily shareable. It's a proven motivator to complete the course. <br>{link_start}Learn more about the verified {cert_name_long}{link_end}.").format(link_start='<a href="{}" class="verified-info" data-course-key="{}">'.format(marketing_link('WHAT_IS_VERIFIED_CERT'), enrollment.course_id), link_end="</a>", cert_name_long=cert_name_long)}
                 </p>
                 <div class="action-upgrade-container">
-                  <a class="action action-upgrade" href="${reverse('verify_student_upgrade_and_verify', kwargs={'course_id': unicode(course_overview.id)})}" data-course-id="${course_overview.id | h}" data-user="${user.username | h}">
+                  % if use_ecommerce_payment_flow and course_mode_info['verified_sku']:
+                    <a class="action action-upgrade" href="${ecommerce_payment_page}?sku=${course_mode_info['verified_sku']}">
+                  % else:
+                    <a class="action action-upgrade" href="${reverse('verify_student_upgrade_and_verify', kwargs={'course_id': unicode(course_overview.id)})}" data-course-id="${course_overview.id | h}" data-user="${user.username | h}">
+                  % endif
                       <i class="action-upgrade-icon"></i>
                     <span class="wrapper-copy">
                       <span class="copy" id="upgrade-to-verified">${_("Upgrade to Verified")}</span>
diff --git a/themes/edx.org/lms/templates/course_modes/choose.html b/themes/edx.org/lms/templates/course_modes/choose.html
index 41ad75eec87..d023d02d9c0 100644
--- a/themes/edx.org/lms/templates/course_modes/choose.html
+++ b/themes/edx.org/lms/templates/course_modes/choose.html
@@ -21,7 +21,7 @@ from django.core.urlresolvers import reverse
             } else {
                 title.attr("aria-expanded", "false");
             }
-        }
+        };
 
         $(document).ready(function() {
             $('.expandable-area').slideUp();
@@ -38,6 +38,12 @@ from django.core.urlresolvers import reverse
                 $('#contribution-other').attr('checked',true);
             });
 
+            % if use_ecommerce_payment_flow:
+            $('input[name=verified_mode]').click(function(e){
+                e.preventDefault();
+                window.location.href = '${ecommerce_payment_page}?sku=${sku}';
+            });
+            % endif
         });
     </script>
 </%block>
-- 
GitLab