From 2d82aebee9314bca088d66f83339495797af3f41 Mon Sep 17 00:00:00 2001
From: Sagirov Eugeniy
Date: Thu, 5 May 2022 14:03:55 +0300
Subject: [PATCH] feat: ecommerce Basket Page -> micro-frontend
---
.../commands/create_or_update_site.py | 13 +-
ecommerce/core/models.py | 10 +-
ecommerce/coupons/tests/test_views.py | 2 +-
ecommerce/coupons/views.py | 4 +-
ecommerce/extensions/basket/apps.py | 20 -
.../extensions/basket/tests/test_utils.py | 22 -
.../extensions/basket/tests/test_views.py | 795 +-----------------
ecommerce/extensions/basket/utils.py | 37 +-
ecommerce/extensions/basket/views.py | 253 +-----
.../extensions/checkout/tests/test_views.py | 4 +-
ecommerce/extensions/checkout/views.py | 3 +-
ecommerce/extensions/payment/constants.py | 12 -
.../payment/tests/views/test_cybersource.py | 22 +-
.../extensions/payment/views/cybersource.py | 4 +-
ecommerce/templates/oscar/basket/basket.html | 73 --
.../oscar/basket/messages/new_total.html | 30 -
.../basket/partials/add_voucher_form.html | 20 -
.../partials/client_side_checkout_basket.html | 281 -------
.../partials/hosted_checkout_basket.html | 189 -----
.../oscar/basket/partials/seat_type.html | 11 -
ecommerce/templates/payment/cybersource.html | 23 -
ecommerce/tests/factories.py | 3 +-
22 files changed, 27 insertions(+), 1804 deletions(-)
delete mode 100644 ecommerce/templates/oscar/basket/basket.html
delete mode 100644 ecommerce/templates/oscar/basket/messages/new_total.html
delete mode 100644 ecommerce/templates/oscar/basket/partials/add_voucher_form.html
delete mode 100644 ecommerce/templates/oscar/basket/partials/client_side_checkout_basket.html
delete mode 100644 ecommerce/templates/oscar/basket/partials/hosted_checkout_basket.html
delete mode 100644 ecommerce/templates/oscar/basket/partials/seat_type.html
delete mode 100644 ecommerce/templates/payment/cybersource.html
diff --git a/ecommerce/core/management/commands/create_or_update_site.py b/ecommerce/core/management/commands/create_or_update_site.py
index 36d9717c882..95b53a99686 100644
--- a/ecommerce/core/management/commands/create_or_update_site.py
+++ b/ecommerce/core/management/commands/create_or_update_site.py
@@ -144,13 +144,6 @@ def add_arguments(self, parser):
type=str,
required=True,
help='URL for Discovery service API calls.')
- parser.add_argument('--enable-microfrontend-for-basket-page',
- action='store',
- dest='enable_microfrontend_for_basket_page',
- type=bool,
- required=False,
- help='Use the microfrontend implementation of the '
- 'basket page instead of the server-side template')
parser.add_argument('--payment-microfrontend-url',
action='store',
dest='payment_microfrontend_url',
@@ -179,10 +172,7 @@ def handle(self, *args, **options): # pylint: disable=too-many-statements
base_cookie_domain = options.get('base_cookie_domain', '')
discovery_api_url = options.get('discovery_api_url')
- enable_microfrontend_for_basket_page = bool(options.get('enable_microfrontend_for_basket_page', False))
- payment_microfrontend_url = options.get(
- 'payment_microfrontend_url'
- ) if enable_microfrontend_for_basket_page else None
+ payment_microfrontend_url = options.get('payment_microfrontend_url')
try:
site = Site.objects.get(id=site_id)
@@ -240,7 +230,6 @@ def handle(self, *args, **options): # pylint: disable=too-many-statements
'oauth_settings': oauth_settings,
'base_cookie_domain': base_cookie_domain,
'discovery_api_url': discovery_api_url,
- 'enable_microfrontend_for_basket_page': enable_microfrontend_for_basket_page,
'payment_microfrontend_url': payment_microfrontend_url,
}
if payment_support_email:
diff --git a/ecommerce/core/models.py b/ecommerce/core/models.py
index c644da58296..5c2c2855e6d 100644
--- a/ecommerce/core/models.py
+++ b/ecommerce/core/models.py
@@ -191,12 +191,6 @@ class SiteConfiguration(models.Model):
max_length=255,
blank=True
)
- enable_microfrontend_for_basket_page = models.BooleanField(
- verbose_name=_('Enable Microfrontend for Basket Page'),
- help_text=_('Use the microfrontend implementation of the basket page instead of the server-side template'),
- blank=True,
- default=False
- )
payment_microfrontend_url = models.URLField(
verbose_name=_('Payment Microfrontend URL'),
help_text=_('URL for the Payment Microfrontend (used if Enable Microfrontend for Basket Page is set)'),
@@ -376,9 +370,7 @@ def enterprise_grant_data_sharing_url(self):
@property
def payment_domain_name(self):
- if self.enable_microfrontend_for_basket_page:
- return urlsplit(self.payment_microfrontend_url).netloc
- return self.site.domain
+ return urlsplit(self.payment_microfrontend_url).netloc
@property
def access_token(self):
diff --git a/ecommerce/coupons/tests/test_views.py b/ecommerce/coupons/tests/test_views.py
index 1668c75370c..c819d749d11 100644
--- a/ecommerce/coupons/tests/test_views.py
+++ b/ecommerce/coupons/tests/test_views.py
@@ -845,7 +845,7 @@ def test_inactive_user_email_domain_restricted_coupon_redemption(self):
self.assert_redirected_to_email_confirmation(response)
def get_coupon_redeem_success_expected_redirect_url(self):
- return self.get_full_url(path=reverse('basket:summary')) + '?coupon_redeem_redirect=1'
+ return self.site_configuration.payment_microfrontend_url + '?coupon_redeem_redirect=1'
@ddt.ddt
diff --git a/ecommerce/coupons/views.py b/ecommerce/coupons/views.py
index 22363cd0b9a..39e82a083dd 100644
--- a/ecommerce/coupons/views.py
+++ b/ecommerce/coupons/views.py
@@ -33,7 +33,7 @@
parse_consent_params
)
from ecommerce.extensions.api import exceptions
-from ecommerce.extensions.basket.utils import get_payment_microfrontend_or_basket_url, prepare_basket
+from ecommerce.extensions.basket.utils import get_payment_microfrontend, prepare_basket
from ecommerce.extensions.checkout.mixins import EdxOrderPlacementMixin
from ecommerce.extensions.checkout.utils import get_receipt_page_url
from ecommerce.extensions.offer.utils import get_redirect_to_email_confirmation_if_required
@@ -297,7 +297,7 @@ def get(self, request): # pylint: disable=too-many-statements
# The coupon_redeem_redirect query param is used to communicate to the Payment MFE that it may redirect
# and should not display the payment form before making that determination.
# TODO: It would be cleaner if the user could be redirected to their final destination up front.
- redirect_url = get_payment_microfrontend_or_basket_url(self.request) + "?coupon_redeem_redirect=1"
+ redirect_url = get_payment_microfrontend(self.request) + "?coupon_redeem_redirect=1"
return HttpResponseRedirect(redirect_url)
diff --git a/ecommerce/extensions/basket/apps.py b/ecommerce/extensions/basket/apps.py
index 02c1d188465..aa290928c59 100644
--- a/ecommerce/extensions/basket/apps.py
+++ b/ecommerce/extensions/basket/apps.py
@@ -1,27 +1,7 @@
-from django.conf.urls import url
-from django.contrib.auth.decorators import login_required
from oscar.apps.basket import apps
-from oscar.core.loading import get_class
class BasketConfig(apps.BasketConfig):
name = 'ecommerce.extensions.basket'
-
- # pylint: disable=attribute-defined-outside-init
- def ready(self):
- super().ready()
- self.basket_add_items_view = get_class('basket.views', 'BasketAddItemsView')
- self.summary_view = get_class('basket.views', 'BasketSummaryView')
-
- def get_urls(self):
- urls = [
- url(r'^$', login_required(self.summary_view.as_view()), name='summary'),
- url(r'^add/(?P\d+)/$', self.add_view.as_view(), name='add'),
- url(r'^vouchers/add/$', self.add_voucher_view.as_view(), name='vouchers-add'),
- url(r'^vouchers/(?P\d+)/remove/$', self.remove_voucher_view.as_view(), name='vouchers-remove'),
- url(r'^saved/$', login_required(self.saved_view.as_view()), name='saved'),
- url(r'^add/$', self.basket_add_items_view.as_view(), name='basket-add'),
- ]
- return self.post_process_urls(urls)
diff --git a/ecommerce/extensions/basket/tests/test_utils.py b/ecommerce/extensions/basket/tests/test_utils.py
index 754f2a1cac1..a3ea5a7b798 100644
--- a/ecommerce/extensions/basket/tests/test_utils.py
+++ b/ecommerce/extensions/basket/tests/test_utils.py
@@ -26,7 +26,6 @@
apply_voucher_on_basket_and_check_discount,
attribute_cookie_data,
get_basket_switch_data,
- get_payment_microfrontend_url_if_configured,
is_duplicate_seat_attempt,
prepare_basket
)
@@ -35,7 +34,6 @@
from ecommerce.extensions.order.exceptions import AlreadyPlacedOrderException
from ecommerce.extensions.order.utils import UserAlreadyPlacedOrder
from ecommerce.extensions.partner.models import StockRecord
-from ecommerce.extensions.payment.constants import DISABLE_MICROFRONTEND_FOR_BASKET_PAGE_FLAG_NAME
from ecommerce.extensions.test.factories import create_order, prepare_voucher
from ecommerce.referrals.models import Referral
from ecommerce.tests.testcases import TestCase, TransactionTestCase
@@ -688,26 +686,6 @@ def test_apply_voucher_on_basket_and_check_discount_with_multiple_vouchers(self)
self.assertEqual(applied, False)
self.assertEqual(msg, 'Basket does not qualify for coupon code {code}.'.format(code=invalid_voucher.code))
- @ddt.data(
- (True, '/payment', False, '/payment'), # Microfrontend not disabled
- (True, '/payment', True, None), # Microfrontend disabled
- )
- @ddt.unpack
- def test_disable_microfrontend_for_basket_page_flag(
- self,
- microfrontend_enabled,
- payment_microfrontend_url,
- disable_microfrontend_flag_active,
- expected_result
- ):
- """
- Verify that the `disable_microfrontend_for_basket_page_flag` correctly disables the microfrontend url retrieval
- """
- with override_flag(DISABLE_MICROFRONTEND_FOR_BASKET_PAGE_FLAG_NAME, active=disable_microfrontend_flag_active):
- self.site_configuration.enable_microfrontend_for_basket_page = microfrontend_enabled
- self.site_configuration.payment_microfrontend_url = payment_microfrontend_url
- self.assertEqual(get_payment_microfrontend_url_if_configured(self.request), expected_result)
-
def test_prepare_basket_with_duplicate_seat(self):
""" Verify a basket fixes the case where flush doesn't work and we attempt adding duplicate seat. """
with mock.patch('ecommerce.extensions.basket.utils.Basket.flush'):
diff --git a/ecommerce/extensions/basket/tests/test_views.py b/ecommerce/extensions/basket/tests/test_views.py
index 1fcf4f9cd75..9883175f6fc 100644
--- a/ecommerce/extensions/basket/tests/test_views.py
+++ b/ecommerce/extensions/basket/tests/test_views.py
@@ -1,7 +1,6 @@
import datetime
-import itertools
import json
import urllib.error
import urllib.parse
@@ -13,303 +12,43 @@
import mock
import pytz
from django.conf import settings
-from django.contrib.messages import get_messages
-from django.http import HttpResponseRedirect
from django.test import override_settings
from django.urls import reverse
-from edx_django_utils.cache import RequestCache, TieredCache
-from oscar.apps.basket.forms import BasketVoucherForm
+from edx_django_utils.cache import RequestCache
from oscar.core.loading import get_class, get_model
from oscar.test import factories
from requests.exceptions import ConnectionError as ReqConnectionError
-from requests.exceptions import Timeout
-from slumber.exceptions import SlumberBaseException
from testfixtures import LogCapture
-from waffle.testutils import override_flag
-from ecommerce.core.exceptions import SiteConfigurationError
-from ecommerce.core.tests import toggle_switch
-from ecommerce.core.url_utils import absolute_url, get_lms_url
-from ecommerce.core.utils import get_cache_key
-from ecommerce.coupons.tests.mixins import CouponMixin, DiscoveryMockMixin
+from ecommerce.core.url_utils import absolute_url
+from ecommerce.coupons.tests.mixins import DiscoveryMockMixin
from ecommerce.courses.tests.factories import CourseFactory
from ecommerce.enterprise.tests.mixins import EnterpriseServiceMockMixin
from ecommerce.enterprise.utils import construct_enterprise_course_consent_url
from ecommerce.entitlements.utils import create_or_update_course_entitlement
from ecommerce.extensions.analytics.utils import translate_basket_line_for_segment
-from ecommerce.extensions.basket.constants import EMAIL_OPT_IN_ATTRIBUTE
from ecommerce.extensions.basket.tests.mixins import BasketMixin
from ecommerce.extensions.basket.tests.test_utils import TEST_BUNDLE_ID
from ecommerce.extensions.basket.utils import _set_basket_bundle_status, apply_voucher_on_basket_and_check_discount
-from ecommerce.extensions.catalogue.tests.mixins import DiscoveryTestMixin
-from ecommerce.extensions.offer.constants import DYNAMIC_DISCOUNT_FLAG
-from ecommerce.extensions.offer.utils import format_benefit_value
-from ecommerce.extensions.order.utils import UserAlreadyPlacedOrder
-from ecommerce.extensions.payment.constants import CLIENT_SIDE_CHECKOUT_FLAG_NAME
-from ecommerce.extensions.payment.forms import PaymentForm
-from ecommerce.extensions.payment.tests.processors import DummyProcessor
-from ecommerce.extensions.test.factories import create_order, prepare_voucher
-from ecommerce.tests.factories import ProductFactory, SiteConfigurationFactory, StockRecordFactory
-from ecommerce.tests.mixins import ApiMockMixin, LmsApiMockMixin
+from ecommerce.extensions.test.factories import prepare_voucher
+from ecommerce.tests.factories import ProductFactory
+from ecommerce.tests.mixins import LmsApiMockMixin
from ecommerce.tests.testcases import TestCase
Applicator = get_class('offer.applicator', 'Applicator')
-Basket = get_model('basket', 'Basket')
BasketAttribute = get_model('basket', 'BasketAttribute')
BasketAttributeType = get_model('basket', 'BasketAttributeType')
Benefit = get_model('offer', 'Benefit')
-Catalog = get_model('catalogue', 'Catalog')
Condition = get_model('offer', 'Condition')
ConditionalOffer = get_model('offer', 'ConditionalOffer')
-OrderLine = get_model('order', 'Line')
-Product = get_model('catalogue', 'Product')
-ProductAttribute = get_model('catalogue', 'ProductAttribute')
Selector = get_class('partner.strategy', 'Selector')
-StockRecord = get_model('partner', 'StockRecord')
Voucher = get_model('voucher', 'Voucher')
-VoucherAddView = get_class('basket.views', 'VoucherAddView')
VoucherApplication = get_model('voucher', 'VoucherApplication')
COUPON_CODE = 'COUPONTEST'
BUNDLE = 'bundle_identifier'
-@ddt.ddt
-class BasketAddItemsViewTests(CouponMixin, DiscoveryTestMixin, DiscoveryMockMixin, LmsApiMockMixin, BasketMixin,
- EnterpriseServiceMockMixin, TestCase):
- """ BasketAddItemsView view tests. """
- path = reverse('basket:basket-add')
-
- def setUp(self):
- super(BasketAddItemsViewTests, self).setUp()
- self.user = self.create_user()
- self.client.login(username=self.user.username, password=self.password)
-
- self.course = CourseFactory(partner=self.partner)
- product = self.course.create_or_update_seat('verified', False, 50)
- self.stock_record = StockRecordFactory(product=product, partner=self.partner)
- self.catalog = Catalog.objects.create(partner=self.partner)
- self.catalog.stock_records.add(self.stock_record)
-
- def _get_response(self, product_skus, **url_params):
- qs = urllib.parse.urlencode({'sku': product_skus}, True)
- url = '{root}?{qs}'.format(root=self.path, qs=qs)
- for name, value in url_params.items():
- url += '&{}={}'.format(name, value)
- return self.client.get(url)
-
- def test_add_multiple_products_to_basket(self):
- """ Verify the basket accepts multiple products. """
- products = ProductFactory.create_batch(3, stockrecords__partner=self.partner)
- response = self._get_response([product.stockrecords.first().partner_sku for product in products])
- self.assertEqual(response.status_code, 303)
-
- basket = response.wsgi_request.basket
- self.assertEqual(basket.status, Basket.OPEN)
- self.assertEqual(basket.lines.count(), len(products))
-
- def test_basket_with_utm_params(self):
- """ Verify the basket includes utm params after redirect. """
- products = ProductFactory.create_batch(3, stockrecords__partner=self.partner)
- response = self._get_response(
- [product.stockrecords.first().partner_sku for product in products],
- utm_source='test',
- )
- expected_url = self.get_full_url(reverse('basket:summary')) + '?utm_source=test'
- self.assertEqual(response.url, expected_url)
-
- def test_redirect_to_basket_summary(self):
- """
- Verify the view redirects to the basket summary page, and that the user's basket is prepared for checkout.
- """
- self.create_coupon(catalog=self.catalog, code=COUPON_CODE, benefit_value=5)
-
- self.mock_course_runs_endpoint(self.site_configuration.discovery_api_url, course_run=self.course)
- response = self._get_response(self.stock_record.partner_sku, code=COUPON_CODE)
- expected_url = self.get_full_url(reverse('basket:summary'))
- self.assertRedirects(response, expected_url, status_code=303)
-
- basket = Basket.objects.get(owner=self.user, site=self.site)
- self.assertEqual(basket.status, Basket.OPEN)
- self.assertEqual(basket.lines.count(), 1)
- self.assertTrue(basket.contains_a_voucher)
- self.assertEqual(basket.lines.first().product, self.stock_record.product)
-
- @ddt.data(*itertools.product((True, False), (True, False)))
- @ddt.unpack
- def test_microfrontend_for_single_course_purchase_if_configured(self, enable_redirect, set_url):
- microfrontend_url = self.configure_redirect_to_microfrontend(enable_redirect, set_url)
- response = self._get_response(self.stock_record.partner_sku, utm_source='test')
-
- expect_microfrontend = enable_redirect and set_url
- expected_url = microfrontend_url if expect_microfrontend else self.get_full_url(reverse('basket:summary'))
- expected_url += '?utm_source=test'
- self.assertRedirects(response, expected_url, status_code=303, fetch_redirect_response=False)
-
- def test_add_invalid_code_to_basket(self):
- """
- When the BasketAddItemsView receives an invalid code as a parameter, add a message to the url.
- This message will be displayed on the payment page.
- """
- microfrontend_url = self.configure_redirect_to_microfrontend(True, True)
- response = self._get_response(self.stock_record.partner_sku, code='invalidcode')
- expected_url = microfrontend_url + '?error_message=Code%20invalidcode%20is%20invalid.'
- self.assertRedirects(response, expected_url, status_code=303, fetch_redirect_response=False)
-
- def test_microfrontend_for_enrollment_code_seat(self):
- microfrontend_url = self.configure_redirect_to_microfrontend()
-
- course, __, enrollment_code = self.prepare_course_seat_and_enrollment_code()
- basket = factories.BasketFactory(owner=self.user, site=self.site)
- basket.add_product(enrollment_code, 1)
- self.mock_course_runs_endpoint(self.site_configuration.discovery_api_url, course_run=course)
-
- response = self._get_response(enrollment_code.stockrecords.first().partner_sku)
- self.assertRedirects(response, microfrontend_url, status_code=303, fetch_redirect_response=False)
-
- def test_add_multiple_products_no_skus_provided(self):
- """ Verify the Bad request exception is thrown when no skus are provided. """
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.content.decode('utf-8'), 'No SKUs provided.')
-
- def test_add_multiple_products_no_available_products(self):
- """ Verify the Bad request exception is thrown when no skus are provided. """
- response = self.client.get(self.path, data=[('sku', 1), ('sku', 2)])
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.content.decode('utf-8'), 'Products with SKU(s) [1, 2] do not exist.')
-
- @ddt.data(Voucher.SINGLE_USE, Voucher.MULTI_USE)
- def test_add_multiple_products_and_use_voucher(self, usage):
- """ Verify the basket accepts multiple products and a single use voucher. """
- products = ProductFactory.create_batch(3, stockrecords__partner=self.partner)
- product_range = factories.RangeFactory(products=products)
- voucher, __ = prepare_voucher(_range=product_range, usage=usage)
-
- response = self._get_response(
- [product.stockrecords.first().partner_sku for product in products],
- code=voucher.code,
- )
- self.assertEqual(response.status_code, 303)
- basket = response.wsgi_request.basket
- self.assertEqual(basket.status, Basket.OPEN)
- self.assertTrue(basket.contains_voucher(voucher.code))
-
- def test_all_already_purchased_products(self):
- """
- Test user can not purchase products again using the multiple item view
- """
- course = CourseFactory(partner=self.partner)
- product1 = course.create_or_update_seat("Verified", True, 0)
- product2 = course.create_or_update_seat("Professional", True, 0)
- stock_record = StockRecordFactory(product=product1, partner=self.partner)
- catalog = Catalog.objects.create(partner=self.partner)
- catalog.stock_records.add(stock_record)
- stock_record = StockRecordFactory(product=product2, partner=self.partner)
- catalog.stock_records.add(stock_record)
-
- with mock.patch.object(UserAlreadyPlacedOrder, 'user_already_placed_order', return_value=True):
- response = self._get_response(
- [product.stockrecords.first().partner_sku for product in [product1, product2]],
- )
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.context['error'], 'You have already purchased these products')
-
- def test_not_already_purchased_products(self):
- """
- Test user can purchase products which have not been already purchased
- """
- products = ProductFactory.create_batch(3, stockrecords__partner=self.partner)
- with mock.patch.object(UserAlreadyPlacedOrder, 'user_already_placed_order', return_value=False):
- response = self._get_response([product.stockrecords.first().partner_sku for product in products])
- self.assertEqual(response.status_code, 303)
-
- def test_one_already_purchased_product(self):
- """
- Test prepare_basket removes already purchased product and checkout for the rest of products
- """
- order = create_order(site=self.site, user=self.user)
- products = ProductFactory.create_batch(3, stockrecords__partner=self.partner)
- products.append(OrderLine.objects.get(order=order).product)
- response = self._get_response([product.stockrecords.first().partner_sku for product in products])
- basket = response.wsgi_request.basket
- self.assertEqual(response.status_code, 303)
- self.assertEqual(basket.lines.count(), len(products) - 1)
-
- def test_no_available_product(self):
- """ The view should return HTTP 400 if the product is not available for purchase. """
- product = self.stock_record.product
- product.expires = pytz.utc.localize(datetime.datetime.min)
- product.save()
- self.assertFalse(Selector().strategy().fetch_for_product(product).availability.is_available_to_buy)
-
- expected_content = 'No product is available to buy.'
- response = self._get_response(self.stock_record.partner_sku)
- self.assertEqual(response.status_code, 400)
- self.assertEqual(response.content.decode('utf-8'), expected_content)
-
- def test_with_both_unavailable_and_available_products(self):
- """ Verify the basket ignores unavailable products and continue with available products. """
- products = ProductFactory.create_batch(3, stockrecords__partner=self.partner)
-
- products[0].expires = pytz.utc.localize(datetime.datetime.min)
- products[0].save()
- self.assertFalse(Selector().strategy().fetch_for_product(products[0]).availability.is_available_to_buy)
-
- response = self._get_response([product.stockrecords.first().partner_sku for product in products])
- self.assertEqual(response.status_code, 303)
- basket = response.wsgi_request.basket
- self.assertEqual(basket.status, Basket.OPEN)
-
- @ddt.data(
- ('false', 'False'),
- ('true', 'True'),
- )
- @ddt.unpack
- def test_email_opt_in_when_explicitly_given(self, opt_in, expected_value):
- """
- Verify the email_opt_in query string is saved into a BasketAttribute.
- """
- response = self._get_response(self.stock_record.partner_sku, email_opt_in=opt_in)
- basket = response.wsgi_request.basket
- basket_attribute = BasketAttribute.objects.get(
- basket=basket,
- attribute_type=BasketAttributeType.objects.get(name=EMAIL_OPT_IN_ATTRIBUTE),
- )
- self.assertEqual(basket_attribute.value_text, expected_value)
-
- def test_email_opt_in_when_not_given(self):
- """
- Verify that email_opt_in defaults to false if not specified.
- """
- response = self._get_response(self.stock_record.partner_sku)
- basket = response.wsgi_request.basket
- basket_attribute = BasketAttribute.objects.get(
- basket=basket,
- attribute_type=BasketAttributeType.objects.get(name=EMAIL_OPT_IN_ATTRIBUTE),
- )
- self.assertEqual(basket_attribute.value_text, 'False')
-
- @httpretty.activate
- def test_enterprise_free_basket_redirect(self):
- """
- Verify redirect to FreeCheckoutView when basket is free
- and an Enterprise-related offer is applied.
- """
- enterprise_offer = self.prepare_enterprise_offer()
-
- opts = {
- 'ec_uuid': str(enterprise_offer.condition.enterprise_customer_uuid),
- 'course_id': self.course.id,
- 'username': self.user.username,
- }
- self.mock_consent_get(**opts)
-
- response = self._get_response(self.stock_record.partner_sku)
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, absolute_url(self.request, 'checkout:free-checkout'))
-
-
class BasketLogicTestMixin:
""" Helper functions for Basket API and BasketSummaryView tests. """
def create_empty_basket(self):
@@ -781,424 +520,6 @@ def test_enterprise_offer_free_basket_with_wrong_basket(self):
self.assertEqual(response.data['redirect'], absolute_url(self.request, 'checkout:free-checkout'))
-@httpretty.activate
-@ddt.ddt
-class BasketSummaryViewTests(EnterpriseServiceMockMixin, DiscoveryTestMixin, DiscoveryMockMixin, LmsApiMockMixin,
- ApiMockMixin, BasketMixin, BasketLogicTestMixin, TestCase):
- """ BasketSummaryView basket view tests. """
- path = reverse('basket:summary')
-
- def setUp(self):
- super(BasketSummaryViewTests, self).setUp()
- self.user = self.create_user()
- self.client.login(username=self.user.username, password=self.password)
- self.course = CourseFactory(name='BasketSummaryTest', partner=self.partner)
- site_configuration = self.site.siteconfiguration
-
- site_configuration.payment_processors = DummyProcessor.NAME
- site_configuration.client_side_payment_processor = DummyProcessor.NAME
- site_configuration.save()
-
- toggle_switch(settings.PAYMENT_PROCESSOR_SWITCH_PREFIX + DummyProcessor.NAME, True)
-
- @ddt.data(ReqConnectionError, SlumberBaseException, Timeout)
- def test_course_api_failure(self, error):
- """ Verify a connection error and timeout are logged when they happen. """
- seat = self.create_seat(self.course)
- basket = self.create_basket_and_add_product(seat)
- self.assertEqual(basket.lines.count(), 1)
-
- logger_name = 'ecommerce.extensions.basket.views'
- self.mock_api_error(
- error=error,
- url=get_lms_url('api/courses/v1/courses/{}/'.format(self.course.id))
- )
-
- with LogCapture(logger_name) as logger:
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 200)
- logger.check(
- (
- logger_name, 'ERROR',
- u'Failed to retrieve data from Discovery Service for course [{}].'.format(self.course.id)
- )
- )
-
- def test_non_seat_product(self):
- """Verify the basket accepts non-seat product types."""
- title = 'Test Product 123'
- description = 'All hail the test product.'
- product = factories.ProductFactory(title=title, description=description)
- self.create_basket_and_add_product(product)
-
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 200)
- line_data = response.context['formset_lines_data'][0][1]
- self.assertEqual(line_data['product_title'], title)
- self.assertEqual(line_data['product_description'], description)
-
- def test_enrollment_code_seat_type(self):
- """Verify the correct seat type attribute is retrieved."""
- course, __, enrollment_code = self.prepare_course_seat_and_enrollment_code()
- self.create_basket_and_add_product(enrollment_code)
- self.mock_course_runs_endpoint(self.site_configuration.discovery_api_url, course_run=course)
-
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 200)
- self.assertFalse(response.context['show_voucher_form'])
- line_data = response.context['formset_lines_data'][0][1]
- self.assertEqual(line_data['seat_type'], enrollment_code.attr.seat_type.capitalize())
-
- def test_microfrontend_for_single_course_purchase(self):
- microfrontend_url = self.configure_redirect_to_microfrontend()
-
- seat = self.create_seat(self.course)
- self.create_basket_and_add_product(seat)
- response = self.client.get(self.path)
- self.assertRedirects(response, microfrontend_url, status_code=302, fetch_redirect_response=False)
-
- def test_microfrontend_with_consent_failed_param(self):
- microfrontend_url = self.configure_redirect_to_microfrontend()
-
- params = 'consent_failed=THISISACOUPONCODE'
- url = '{}?{}'.format(self.path, params)
- response = self.client.get(url)
- expected_redirect_url = '{}?{}'.format(microfrontend_url, params)
- self.assertRedirects(response, expected_redirect_url, status_code=302, fetch_redirect_response=False)
-
- def test_microfrontend_for_enrollment_code_seat_type(self):
- microfrontend_url = self.configure_redirect_to_microfrontend()
-
- course, __, enrollment_code = self.prepare_course_seat_and_enrollment_code()
- self.create_basket_and_add_product(enrollment_code)
- self.mock_course_runs_endpoint(self.site_configuration.discovery_api_url, course_run=course)
- response = self.client.get(self.path)
- self.assertRedirects(response, microfrontend_url, status_code=302, fetch_redirect_response=False)
-
- @ddt.data(
- (Benefit.PERCENTAGE, 100),
- (Benefit.PERCENTAGE, 50),
- (Benefit.FIXED, 50)
- )
- @ddt.unpack
- @override_settings(PAYMENT_PROCESSORS=['ecommerce.extensions.payment.tests.processors.DummyProcessor'])
- def test_response_success(self, benefit_type, benefit_value):
- """ Verify a successful response is returned. """
- seat = self.create_seat(self.course, 500)
- basket = self.create_basket_and_add_product(seat)
- self.mock_access_token_response()
- self.create_and_apply_benefit_to_basket(basket, seat, benefit_type, benefit_value)
-
- self.assertEqual(basket.lines.count(), 1)
- self.mock_course_run_detail_endpoint(
- self.course, discovery_api_url=self.site_configuration.discovery_api_url
- )
-
- benefit, __ = Benefit.objects.get_or_create(type=benefit_type, value=benefit_value)
- with self.assert_events_fired_to_segment(basket):
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 200)
-
- self.assertEqual(len(response.context['formset_lines_data']), 1)
-
- line_data = response.context['formset_lines_data'][0][1]
- self.assertEqual(line_data['benefit_value'], format_benefit_value(benefit))
- self.assertEqual(line_data['seat_type'], seat.attr.certificate_type.capitalize())
- self.assertEqual(line_data['product_title'], self.course.name)
- self.assertFalse(line_data['enrollment_code'])
- self.assertEqual(response.context['payment_processors'][0].NAME, DummyProcessor.NAME)
-
- def test_track_segment_event_exception(self):
- """ Verify error log when track_segment_event fails. """
- seat = self.create_seat(self.course)
- basket = self.create_basket_and_add_product(seat)
- self.mock_access_token_response()
- self.mock_course_run_detail_endpoint(
- self.course, discovery_api_url=self.site_configuration.discovery_api_url
- )
- self.assertEqual(basket.lines.count(), 1)
-
- logger_name = 'ecommerce.extensions.basket.views'
- with LogCapture(logger_name) as logger:
- with mock.patch('ecommerce.extensions.basket.views.track_segment_event') as mock_track:
- mock_track.side_effect = Exception()
-
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 200)
-
- logger.check((
- logger_name, 'ERROR',
- u'Failed to fire Cart Viewed event for basket [{}]'.format(basket.id)
- ))
-
- def assert_empty_basket(self):
- """ Assert that the basket is empty on visiting the basket summary page. """
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.context['formset_lines_data'], [])
- self.assertEqual(response.context['total_benefit'], None)
-
- def test_no_basket_response(self):
- """ Verify there are no form, line and benefit data in the context for a non-existing basket. """
- self.assert_empty_basket()
-
- def test_line_item_discount_data(self):
- """ Verify that line item has correct discount data. """
- self.mock_course_runs_endpoint(self.site_configuration.discovery_api_url, course_run=self.course)
- seat = self.create_seat(self.course)
- basket = self.create_basket_and_add_product(seat)
- self.create_and_apply_benefit_to_basket(basket, seat, Benefit.PERCENTAGE, 50)
-
- course_without_benefit = CourseFactory()
- seat_without_benefit = self.create_seat(course_without_benefit)
- basket.add_product(seat_without_benefit, 1)
-
- response = self.client.get(self.path)
- lines = response.context['formset_lines_data']
- self.assertEqual(lines[0][1]['benefit_value'], '50%')
- self.assertEqual(lines[1][1]['benefit_value'], None)
-
- @mock.patch('ecommerce.extensions.offer.dynamic_conditional_offer.get_decoded_jwt_discount_from_request')
- @ddt.data(
- {'discount_percent': 15, 'discount_applicable': True},
- {'discount_percent': 15, 'discount_applicable': False},
- None)
- @override_flag(DYNAMIC_DISCOUNT_FLAG, active=True)
- def test_line_item_discount_data_dynamic_discount(self, discount_json, mock_get_discount):
- """ Verify that line item has correct discount data. """
- mock_get_discount.return_value = discount_json
-
- self.mock_course_runs_endpoint(self.site_configuration.discovery_api_url, course_run=self.course)
- seat = self.create_seat(self.course)
- basket = self.create_basket_and_add_product(seat)
- Applicator().apply(basket)
-
- response = self.client.get(self.path)
- lines = response.context['formset_lines_data']
- if discount_json and discount_json['discount_applicable']:
- self.assertEqual(
- lines[0][1]['line'].discount_value,
- discount_json['discount_percent'] / Decimal('100') * lines[0][1]['line'].price_incl_tax)
- else:
- self.assertEqual(
- lines[0][1]['line'].discount_value,
- Decimal(0))
-
- def test_cached_course(self):
- """ Verify that the course info is cached. """
- seat = self.create_seat(self.course, 50)
- basket = self.create_basket_and_add_product(seat)
- self.mock_access_token_response()
- self.assertEqual(basket.lines.count(), 1)
- self.mock_course_run_detail_endpoint(
- self.course, discovery_api_url=self.site_configuration.discovery_api_url
- )
-
- cache_key = get_cache_key(
- site_domain=self.site,
- resource="{}-{}".format('course_runs', self.course.id)
- )
- course_before_cached_response = TieredCache.get_cached_response(cache_key)
- self.assertFalse(course_before_cached_response.is_found)
-
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 200)
- course_after_cached_response = TieredCache.get_cached_response(cache_key)
- self.assertEqual(course_after_cached_response.value['title'], self.course.name)
-
- @ddt.data({
- 'course': 'edX+DemoX',
- 'short_description': None,
- 'title': 'Junk',
- 'start': '2013-02-05T05:00:00Z',
- }, {
- 'course': 'edX+DemoX',
- 'short_description': None,
- })
- def test_empty_catalog_api_response(self, course_info):
- """ Check to see if we can handle empty response from the catalog api """
- seat = self.create_seat(self.course)
- self.create_basket_and_add_product(seat)
- self.mock_access_token_response()
- self.mock_course_run_detail_endpoint(
- self.course, self.site_configuration.discovery_api_url, course_info
- )
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 200)
- line_data = response.context['formset_lines_data'][0][1]
- self.assertEqual(line_data.get('image_url'), None)
- self.assertEqual(line_data.get('course_short_description'), None)
-
- def assert_order_details_in_context(self, product):
- """Assert order details message is in basket context for passed product."""
- self.create_basket_and_add_product(product)
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 200)
- self.assertIsNotNone(response.context['order_details_msg'])
-
- @ddt.data(True, False)
- def test_order_details_msg(self, id_verification):
- """Verify the order details message is displayed for seats and enrollment codes."""
- __, seat, enrollment_code = self.prepare_course_seat_and_enrollment_code(
- seat_type='professional', id_verification=id_verification
- )
- self.assert_order_details_in_context(seat)
- self.assert_order_details_in_context(enrollment_code)
-
- @override_flag(CLIENT_SIDE_CHECKOUT_FLAG_NAME, active=True)
- @override_settings(PAYMENT_PROCESSORS=['ecommerce.extensions.payment.tests.processors.DummyProcessor'])
- def test_client_side_checkout(self):
- """ Verify the view returns the data necessary to initiate client-side checkout. """
- seat = self.create_seat(self.course)
- basket = self.create_basket_and_add_product(seat)
-
- response = self.client.get(self.get_full_url(self.path))
- self.assertEqual(response.status_code, 200)
- self.assertTrue(response.context['enable_client_side_checkout'])
-
- actual_processor = response.context['client_side_payment_processor']
- self.assertIsInstance(actual_processor, DummyProcessor)
-
- payment_form = response.context['payment_form']
- self.assertIsInstance(payment_form, PaymentForm)
- self.assertEqual(payment_form.initial['basket'], basket)
-
- @override_flag(CLIENT_SIDE_CHECKOUT_FLAG_NAME, active=True)
- def test_client_side_checkout_with_invalid_configuration(self):
- """ Verify an error is raised if a payment processor is defined as the client-side processor,
- but is not active in the system."""
- self.site.siteconfiguration.client_side_payment_processor = 'blah'
- self.site.siteconfiguration.save()
-
- seat = self.create_seat(self.course)
- self.create_basket_and_add_product(seat)
-
- with self.assertRaises(SiteConfigurationError):
- self.client.get(self.get_full_url(self.path))
-
- def test_login_required_basket_summary(self):
- """ The view should redirect to the login page if the user is not logged in. """
- self.client.logout()
- response = self.client.get(self.path)
- expected_url = '{path}?next={next}'.format(path=reverse(settings.LOGIN_URL),
- next=urllib.parse.quote(self.path))
- self.assertRedirects(response, expected_url, target_status_code=302)
-
- @ddt.data(
- (None, None),
- ('invalid-date', None),
- ('2017-02-01T00:00:00', datetime.datetime(2017, 2, 1)),
- )
- @ddt.unpack
- @override_settings(PAYMENT_PROCESSORS=['ecommerce.extensions.payment.tests.processors.DummyProcessor'])
- def test_context_data_contains_course_dates(self, date_string, expected_result):
- seat = self.create_seat(self.course)
- self.mock_access_token_response()
- self.create_basket_and_add_product(seat)
- self.mock_course_run_detail_endpoint(
- self.course,
- self.site_configuration.discovery_api_url,
- {
- 'start': date_string,
- 'end': date_string
- }
- )
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 200)
- for _, line_data in response.context['formset_lines_data']:
- self.assertEqual(line_data['course_start'], expected_result)
- self.assertEqual(line_data['course_end'], expected_result)
-
- def test_course_about_url(self):
- """
- Test that in case of bulk enrollment, We have the marketing url from course metadata
- if present in response.
- """
- course_run_info = {
- "course": "edX+DemoX",
- "title": 'course title here',
- "short_description": 'Foo',
- "start": "2013-02-05T05:00:00Z",
- "image": {
- "src": "/path/to/image.jpg",
- },
- 'enrollment_end': None,
- 'marketing_url': '/path/to/marketing/site'
- }
- self.mock_access_token_response()
- course, __, enrollment_code = self.prepare_course_seat_and_enrollment_code()
- self.create_basket_and_add_product(enrollment_code)
- self.mock_course_run_detail_endpoint(
- course,
- self.site_configuration.discovery_api_url,
- course_run_info
- )
-
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 200)
- messages = list(response.context['messages'])
- self.assertEqual(len(messages), 1)
- self.assertContains(response, '/path/to/marketing/site', status_code=200)
-
- def test_failed_enterprise_consent_sends_message(self):
- """
- Test that if we receive an indication via a query parameter that data sharing
- consent was attempted, but failed, we send a message indicating such.
- """
- seat = self.create_seat(self.course)
- self.create_basket_and_add_product(seat)
-
- params = 'consent_failed=THISISACOUPONCODE'
-
- url = '{path}?{params}'.format(
- path=self.get_full_url(self.path),
- params=params
- )
- response = self.client.get(url)
- message = list(response.context['messages'])[0]
-
- self.assertEqual(
- str(message),
- 'Could not apply the code \'THISISACOUPONCODE\'; it requires data sharing consent.'
- )
-
- @httpretty.activate
- def test_enterprise_free_basket_redirect(self):
- self.course_run.create_or_update_seat('verified', True, Decimal(100))
- self.create_basket_and_add_product(self.course_run.seat_products[0])
- enterprise_offer = self.prepare_enterprise_offer(enterprise_customer_name='Foo Enterprise')
-
- opts = {
- 'ec_uuid': str(enterprise_offer.condition.enterprise_customer_uuid),
- 'course_id': self.course_run.seat_products[0].course_id,
- 'username': self.user.username,
- }
- self.mock_consent_get(**opts)
-
- response = self.client.get(self.path)
- self.assertRedirects(
- response,
- absolute_url(self.request, 'checkout:free-checkout'),
- fetch_redirect_response=False,
- )
-
- @override_settings(PAYMENT_PROCESSORS=['ecommerce.extensions.payment.tests.processors.DummyProcessor'])
- @ddt.data(100, 50)
- def test_discounted_free_basket(self, percentage_benefit):
- seat = self.create_seat(self.course, seat_price=100)
- basket = self.create_basket_and_add_product(seat)
- self.mock_access_token_response()
- self.mock_course_run_detail_endpoint(
- self.course, discovery_api_url=self.site_configuration.discovery_api_url
- )
-
- self.create_and_apply_benefit_to_basket(basket, seat, Benefit.PERCENTAGE, percentage_benefit)
-
- response = self.client.get(self.path)
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.context['free_basket'], percentage_benefit == 100)
-
-
@httpretty.activate
class VoucherAddMixin(LmsApiMockMixin, DiscoveryMockMixin):
def setUp(self):
@@ -1411,7 +732,7 @@ def test_activation_not_required_for_inactive_user_when_disabled_for_site(self):
self.basket.add_product(product)
messages = [{
'message_type': 'info',
- 'user_message': u"Coupon code '{code}' added to basket.".format(code=COUPON_CODE),
+ 'user_message': f"Coupon code '{COUPON_CODE}' added to basket.",
}]
self.assert_response(
product=product,
@@ -1477,108 +798,6 @@ def test_account_activation_rendered_with_error(self):
self.assertIn(u'Not Found', response.content.decode('utf-8'))
-@httpretty.activate
-class VoucherAddViewTests(VoucherAddMixin, TestCase):
- """ Tests for VoucherAddView. """
-
- def setUp(self):
- super(VoucherAddViewTests, self).setUp()
- self.view = VoucherAddView()
- self.view.request = self.request
-
- self.form = BasketVoucherForm()
- self.form.cleaned_data = {'code': COUPON_CODE}
-
- def get_error_message_from_request(self):
- return list(get_messages(self.request))[-1].message
-
- def assert_response(self, **kwargs):
- self.assert_form_valid_message(kwargs['messages'][0]['user_message'])
-
- def assert_response_redirect(self, expected_redirect_url):
- resp = self.view.form_valid(self.form)
- self.assertIsInstance(resp, HttpResponseRedirect)
- self.assertEqual(resp.url, expected_redirect_url)
-
- def assert_form_valid_message(self, expected):
- """ Asserts the expected message is logged via messages framework when the
- view's form_valid method is called. """
- self.view.form_valid(self.form)
-
- actual = self.get_error_message_from_request()
- self.assertEqual(str(actual), expected)
-
- def test_voucher_valid_without_site(self):
- """ Verify coupon works when the sites on the coupon and request are the same. """
- self.mock_access_token_response()
- self.mock_account_api(self.request, self.user.username, data={'is_active': True})
- __, product = prepare_voucher(code=COUPON_CODE, site=self.request.site)
- self.basket.add_product(product)
- self.assert_form_valid_message("Coupon code '{code}' added to basket.".format(code=COUPON_CODE))
-
- def test_voucher_not_valid_for_other_site(self):
- """ Verify correct error message is returned when coupon is applied against on the wrong site. """
- other_site = SiteConfigurationFactory().site
- self.mock_access_token_response()
- self.mock_account_api(self.request, self.user.username, data={'is_active': True})
- voucher, product = prepare_voucher(code=COUPON_CODE, site=other_site)
- self.basket.add_product(product)
- self.assert_form_valid_message("Coupon code '{code}' is not valid for this basket.".format(code=voucher.code))
-
- def test_form_valid_without_basket_id(self):
- """ Verify the view redirects to the basket summary view if the basket has no ID. """
- self.request.basket = Basket()
- response = self.view.form_valid(self.form)
- self.assertEqual(response.url, reverse('basket:summary'))
-
- def assert_basket_discounts(self, expected_offer_discounts=None, expected_voucher_discounts=None):
- """Helper to determine if the expected offer is applied to a basket.
- The basket is retrieved from the response because Oscar uses
- SimpleLazyObjects to operate with baskets."""
- expected_offer_discounts = expected_offer_discounts or []
- expected_voucher_discounts = expected_voucher_discounts or []
-
- response = self.client.get(reverse('basket:summary'))
- basket = response.context['basket']
-
- actual_offer_discounts = [discount['offer'] for discount in basket.offer_discounts]
- actual_voucher_discounts = [discount['offer'] for discount in basket.voucher_discounts]
-
- self.assertEqual(actual_offer_discounts, expected_offer_discounts)
- self.assertEqual(actual_voucher_discounts, expected_voucher_discounts)
-
- def test_coupon_applied_on_site_offer(self):
- """Coupon offer supersedes site offer."""
- self.mock_access_token_response()
- self.mock_account_api(self.request, self.user.username, data={'is_active': True})
- product_price = 100
- site_offer_discount = 20
- voucher_discount = 10
-
- voucher, product = prepare_voucher(benefit_value=voucher_discount)
- stockrecord = product.stockrecords.first()
- stockrecord.price_excl_tax = product_price
- stockrecord.save()
-
- _range = factories.RangeFactory(includes_all_products=True)
- site_offer = factories.ConditionalOfferFactory(
- offer_type=ConditionalOffer.SITE,
- benefit=factories.BenefitFactory(range=_range, value=site_offer_discount),
- condition=factories.ConditionFactory(type=Condition.COVERAGE, value=1, range=_range)
- )
- self.basket.add_product(product)
- # Only site offer is applied to the basket.
- self.assert_basket_discounts([site_offer])
-
- # Only the voucher offer is applied to the basket.
- self.client.post(reverse('basket:vouchers-add'), data={'code': voucher.code})
- self.assert_basket_discounts(expected_voucher_discounts=[voucher.offers.first()])
-
- # Site offer discount is still present after removing voucher.
- self.client.post(reverse('basket:vouchers-remove', kwargs={'pk': voucher.id}))
- self.assert_basket_discounts([site_offer])
-
-
@httpretty.activate
class QuantityApiViewTests(PaymentApiResponseTestMixin, BasketMixin, DiscoveryMockMixin, TestCase):
""" QuantityApiViewTests basket api tests. """
diff --git a/ecommerce/extensions/basket/utils.py b/ecommerce/extensions/basket/utils.py
index 7dedbcd825d..a4e722abb40 100644
--- a/ecommerce/extensions/basket/utils.py
+++ b/ecommerce/extensions/basket/utils.py
@@ -15,13 +15,11 @@
from oscar.apps.basket.signals import voucher_addition
from oscar.core.loading import get_class, get_model
-from ecommerce.core.url_utils import absolute_url
from ecommerce.courses.utils import mode_for_product
from ecommerce.extensions.analytics.utils import track_segment_event
from ecommerce.extensions.basket.constants import PURCHASER_BEHALF_ATTRIBUTE
from ecommerce.extensions.order.exceptions import AlreadyPlacedOrderException
from ecommerce.extensions.order.utils import UserAlreadyPlacedOrder
-from ecommerce.extensions.payment.constants import DISABLE_MICROFRONTEND_FOR_BASKET_PAGE_FLAG_NAME
from ecommerce.extensions.payment.utils import embargo_check
from ecommerce.programs.utils import get_program
from ecommerce.referrals.models import Referral
@@ -34,8 +32,6 @@
ORGANIZATION_ATTRIBUTE_TYPE = 'organization'
ENTERPRISE_CATALOG_ATTRIBUTE_TYPE = 'enterprise_catalog_uuid'
StockRecord = get_model('partner', 'StockRecord')
-OrderLine = get_model('order', 'Line')
-Refund = get_model('refund', 'Refund')
Voucher = get_model('voucher', 'Voucher')
logger = logging.getLogger(__name__)
@@ -64,29 +60,8 @@ def add_flex_microform_flag_to_url(url, request, force_flag=None):
)
-def get_payment_microfrontend_or_basket_url(request):
- url = get_payment_microfrontend_url_if_configured(request)
- if not url:
- url = absolute_url(request, 'basket:summary')
- return url
-
-
-def get_payment_microfrontend_url_if_configured(request):
- if _use_payment_microfrontend(request):
- return request.site.siteconfiguration.payment_microfrontend_url
-
- return None
-
-
-def _use_payment_microfrontend(request):
- """
- Return whether the current request should use the payment MFE.
- """
- return (
- request.site.siteconfiguration.enable_microfrontend_for_basket_page and
- request.site.siteconfiguration.payment_microfrontend_url and
- not waffle.flag_is_active(request, DISABLE_MICROFRONTEND_FOR_BASKET_PAGE_FLAG_NAME)
- )
+def get_payment_microfrontend(request):
+ return request.site.siteconfiguration.payment_microfrontend_url
@newrelic.agent.function_trace()
@@ -102,14 +77,6 @@ def add_utm_params_to_url(url, params):
return url
-@newrelic.agent.function_trace()
-def add_invalid_code_message_to_url(url, code):
- if code:
- message = 'error_message=Code {code} is invalid.'.format(code=str(code))
- url += '&' + message if '?' in url else '?' + message
- return url
-
-
@newrelic.agent.function_trace()
def prepare_basket(request, products, voucher=None):
"""
diff --git a/ecommerce/extensions/basket/views.py b/ecommerce/extensions/basket/views.py
index 08edab9f49c..bd225009e63 100644
--- a/ecommerce/extensions/basket/views.py
+++ b/ecommerce/extensions/basket/views.py
@@ -2,7 +2,6 @@
import logging
-import time
import urllib
from collections import OrderedDict
from datetime import datetime
@@ -11,15 +10,11 @@
import dateutil.parser
import newrelic.agent
import waffle
-from django.http import HttpResponseBadRequest, HttpResponseRedirect
-from django.shortcuts import render
+from django.http import HttpResponseRedirect
from django.urls import reverse
-from django.utils.html import escape
from django.utils.translation import ugettext as _
-from edx_rest_framework_extensions.permissions import LoginRedirectIfUnauthenticated
from opaque_keys.edx.keys import CourseKey
from oscar.apps.basket.signals import voucher_removal
-from oscar.apps.basket.views import VoucherAddView as BaseVoucherAddView
from oscar.apps.basket.views import * # pylint: disable=wildcard-import, unused-wildcard-import
from oscar.core.prices import Price
from requests.exceptions import ConnectionError as ReqConnectionError
@@ -42,23 +37,16 @@
parse_consent_params
)
from ecommerce.extensions.analytics.utils import (
- prepare_analytics_data,
track_braze_event,
track_segment_event,
translate_basket_line_for_segment
)
from ecommerce.extensions.basket import message_utils
-from ecommerce.extensions.basket.constants import EMAIL_OPT_IN_ATTRIBUTE
-from ecommerce.extensions.basket.exceptions import BadRequestException, RedirectException, VoucherException
+from ecommerce.extensions.basket.exceptions import RedirectException, VoucherException
from ecommerce.extensions.basket.utils import (
- add_invalid_code_message_to_url,
- add_utm_params_to_url,
apply_offers_on_basket,
apply_voucher_on_basket_and_check_discount,
get_basket_switch_data,
- get_payment_microfrontend_or_basket_url,
- get_payment_microfrontend_url_if_configured,
- prepare_basket,
validate_voucher
)
from ecommerce.extensions.offer.constants import DYNAMIC_DISCOUNT_FLAG
@@ -69,23 +57,13 @@
get_quantized_benefit_value,
get_redirect_to_email_confirmation_if_required
)
-from ecommerce.extensions.order.exceptions import AlreadyPlacedOrderException
-from ecommerce.extensions.partner.shortcuts import get_partner_for_site
-from ecommerce.extensions.payment.constants import CLIENT_SIDE_CHECKOUT_FLAG_NAME
from ecommerce.extensions.payment.forms import PaymentForm
from ecommerce.programs.utils import get_program
-Basket = get_model('basket', 'basket')
BasketAttribute = get_model('basket', 'BasketAttribute')
-BasketAttributeType = get_model('basket', 'BasketAttributeType')
BUNDLE = 'bundle_identifier'
ConditionalOffer = get_model('offer', 'ConditionalOffer')
-Benefit = get_model('offer', 'Benefit')
logger = logging.getLogger(__name__)
-Product = get_model('catalogue', 'Product')
-StockRecord = get_model('partner', 'StockRecord')
-Voucher = get_model('voucher', 'Voucher')
-Selector = get_class('partner.strategy', 'Selector')
class BasketLogicMixin:
@@ -404,213 +382,6 @@ def _deserialize_date(self, date_string):
return None
-class BasketAddItemsView(BasketLogicMixin, APIView):
- """
- View that adds multiple products to a user's basket.
- An additional coupon code can be supplied so the offer is applied to the basket.
- """
- permission_classes = (LoginRedirectIfUnauthenticated,)
-
- def get(self, request):
- # Send time when this view is called - https://openedx.atlassian.net/browse/REV-984
- properties = {'emitted_at': time.time()}
- track_segment_event(request.site, request.user, 'Basket Add Items View Called', properties)
-
- try:
- skus = self._get_skus(request)
- products = self._get_products(request, skus)
- voucher = None
- invalid_code = None
- code = request.GET.get('code', None)
- try:
- voucher = self._get_voucher(request)
- except Voucher.DoesNotExist: # pragma: nocover
- # Display an error message when an invalid code is passed as a parameter
- invalid_code = code
-
- logger.info('Starting payment flow for user [%s] for products [%s].', request.user.username, skus)
-
- available_products = self._get_available_products(request, products)
-
- try:
- basket = prepare_basket(request, available_products, voucher)
- except AlreadyPlacedOrderException:
- return render(request, 'edx/error.html', {'error': _('You have already purchased these products')})
-
- self._set_email_preference_on_basket(request, basket)
-
- # Used basket object from request to allow enterprise offers
- # being applied on basket via BasketMiddleware
- self.verify_enterprise_needs(request.basket)
- if code and not request.basket.vouchers.exists():
- if not (len(available_products) == 1 and available_products[0].is_enrollment_code_product):
- # Display an error message when an invalid code is passed as a parameter
- invalid_code = code
- return self._redirect_response_to_basket_or_payment(request, invalid_code)
-
- except BadRequestException as e:
- return HttpResponseBadRequest(str(e))
- except RedirectException as e:
- return e.response
-
- def _get_skus(self, request):
- skus = [escape(sku) for sku in request.GET.getlist('sku')]
- if not skus:
- raise BadRequestException(_('No SKUs provided.'))
- return skus
-
- def _get_products(self, request, skus):
- partner = get_partner_for_site(request)
- products = Product.objects.filter(stockrecords__partner=partner, stockrecords__partner_sku__in=skus)
- if not products:
- raise BadRequestException(_('Products with SKU(s) [{skus}] do not exist.').format(skus=', '.join(skus)))
- return products
-
- def _get_voucher(self, request):
- code = request.GET.get('code', None)
- return Voucher.objects.get(code=code) if code else None
-
- def _get_available_products(self, request, products):
- unavailable_product_ids = []
- for product in products:
- purchase_info = request.strategy.fetch_for_product(product)
- if not purchase_info.availability.is_available_to_buy:
- logger.warning('Product [%s] is not available to buy.', product.title)
- unavailable_product_ids.append(product.id)
-
- available_products = products.exclude(id__in=unavailable_product_ids)
- if not available_products:
- raise BadRequestException(_('No product is available to buy.'))
- return available_products
-
- def _set_email_preference_on_basket(self, request, basket):
- """
- Associate the user's email opt in preferences with the basket in
- order to opt them in later as part of fulfillment
- """
- BasketAttribute.objects.update_or_create(
- basket=basket,
- attribute_type=BasketAttributeType.objects.get(name=EMAIL_OPT_IN_ATTRIBUTE),
- defaults={'value_text': request.GET.get('email_opt_in') == 'true'},
- )
-
- def _redirect_response_to_basket_or_payment(self, request, invalid_code=None):
- redirect_url = get_payment_microfrontend_or_basket_url(request)
- redirect_url = add_utm_params_to_url(redirect_url, list(self.request.GET.items()))
- redirect_url = add_invalid_code_message_to_url(redirect_url, invalid_code)
-
- return HttpResponseRedirect(redirect_url, status=303)
-
-
-class BasketSummaryView(BasketLogicMixin, BasketView):
- @newrelic.agent.function_trace()
- def get_context_data(self, **kwargs):
- context = super(BasketSummaryView, self).get_context_data(**kwargs)
- return self._add_to_context_data(context)
-
- @newrelic.agent.function_trace()
- def get(self, request, *args, **kwargs):
- basket = request.basket
-
- try:
- self.fire_segment_events(request, basket)
- self.verify_enterprise_needs(basket)
- self._redirect_to_payment_microfrontend_if_configured(request)
- except RedirectException as e:
- return e.response
-
- return super(BasketSummaryView, self).get(request, *args, **kwargs)
-
- def _redirect_to_payment_microfrontend_if_configured(self, request):
- microfrontend_url = get_payment_microfrontend_url_if_configured(request)
- if microfrontend_url:
- # For now, the enterprise consent form validation is communicated via
- # a URL parameter, which must be forwarded via this redirect.
- consent_failed_param_to_forward = request.GET.get(CONSENT_FAILED_PARAM)
- if consent_failed_param_to_forward:
- microfrontend_url = '{}?{}={}'.format(
- microfrontend_url,
- CONSENT_FAILED_PARAM,
- consent_failed_param_to_forward,
- )
- redirect_response = HttpResponseRedirect(microfrontend_url)
- raise RedirectException(response=redirect_response)
-
- @newrelic.agent.function_trace()
- def _add_to_context_data(self, context):
- formset = context.get('formset', [])
- lines = context.get('line_list', [])
- site_configuration = self.request.site.siteconfiguration
-
- context_updates, lines_data = self.process_basket_lines(lines)
- context.update(context_updates)
- context.update(self.process_totals(context))
-
- context.update({
- 'analytics_data': prepare_analytics_data(
- self.request.user,
- site_configuration.segment_key,
- ),
- 'enable_client_side_checkout': False,
- 'sdn_check': site_configuration.enable_sdn_check
- })
-
- payment_processors = site_configuration.get_payment_processors()
- if (
- site_configuration.client_side_payment_processor and
- waffle.flag_is_active(self.request, CLIENT_SIDE_CHECKOUT_FLAG_NAME)
- ):
- payment_processors_data = self._get_payment_processors_data(payment_processors)
- context.update(payment_processors_data)
-
- context.update({
- 'formset_lines_data': list(zip(formset, lines_data)),
- 'homepage_url': get_lms_url(''),
- 'min_seat_quantity': 1,
- 'max_seat_quantity': 100,
- 'payment_processors': payment_processors,
- 'lms_url_root': site_configuration.lms_url_root,
- })
- return context
-
- @newrelic.agent.function_trace()
- def _get_payment_processors_data(self, payment_processors):
- """Retrieve information about payment processors for the client side checkout basket.
-
- Args:
- payment_processors (list): List of all available payment processors.
- Returns:
- A dictionary containing information about the payment processor(s) with which the
- basket view context needs to be updated with.
- """
- site_configuration = self.request.site.siteconfiguration
- payment_processor_class = site_configuration.get_client_side_payment_processor_class()
-
- if payment_processor_class:
- payment_processor = payment_processor_class(self.request.site)
- current_year = datetime.today().year
-
- return {
- 'client_side_payment_processor': payment_processor,
- 'enable_client_side_checkout': True,
- 'months': list(range(1, 13)),
- 'payment_form': PaymentForm(
- user=self.request.user,
- request=self.request,
- initial={'basket': self.request.basket},
- label_suffix=''
- ),
- 'paypal_enabled': 'paypal' in (p.NAME for p in payment_processors),
- # Assumption is that the credit card duration is 15 years
- 'years': list(range(current_year, current_year + 16)),
- }
- else:
- msg = 'Unable to load client-side payment processor [{processor}] for ' \
- 'site configuration [{sc}]'.format(processor=site_configuration.client_side_payment_processor,
- sc=site_configuration.id)
- raise SiteConfigurationError(msg)
-
-
class CaptureContextApiLogicMixin: # pragma: no cover
"""
Business logic for the capture context API.
@@ -1050,26 +821,6 @@ def _get_voucher(self, code):
raise VoucherException() from voucher_no_exist
-class VoucherAddView(VoucherAddLogicMixin, BaseVoucherAddView): # pylint: disable=function-redefined
- """
- Deprecated: Adds a voucher to the basket.
-
- Ensure any changes made here are also made to VoucherAddApiView.
- """
- def form_valid(self, form):
- code = form.cleaned_data['code']
-
- try:
- self.verify_and_apply_voucher(code)
- except RedirectException as e:
- return e.response
- except VoucherException:
- # errors are passed via messages object
- pass
-
- return redirect_to_referrer(self.request, 'basket:summary')
-
-
class VoucherAddApiView(VoucherAddLogicMixin, PaymentApiLogicMixin, APIView):
"""
Api for adding voucher to a basket.
diff --git a/ecommerce/extensions/checkout/tests/test_views.py b/ecommerce/extensions/checkout/tests/test_views.py
index 824fba2e3ef..3827d29c282 100644
--- a/ecommerce/extensions/checkout/tests/test_views.py
+++ b/ecommerce/extensions/checkout/tests/test_views.py
@@ -56,8 +56,8 @@ def prepare_basket(self, price, bundle=False):
def test_empty_basket(self):
""" Verify redirect to basket summary in case of empty basket. """
response = self.client.get(self.path)
- expected_url = reverse('basket:summary')
- self.assertRedirects(response, expected_url)
+ expected_url = self.site_configuration.payment_microfrontend_url
+ self.assertRedirects(response, expected_url, target_status_code=302)
def test_non_free_basket(self):
""" Verify an exception is raised when the URL is being accessed to with a non-free basket. """
diff --git a/ecommerce/extensions/checkout/views.py b/ecommerce/extensions/checkout/views.py
index 528c5b60090..b23cdca2c09 100644
--- a/ecommerce/extensions/checkout/views.py
+++ b/ecommerce/extensions/checkout/views.py
@@ -25,6 +25,7 @@
get_lms_program_dashboard_url
)
from ecommerce.enterprise.api import fetch_enterprise_learner_data
+from ecommerce.extensions.basket.utils import get_payment_microfrontend
from ecommerce.enterprise.utils import has_enterprise_offer
from ecommerce.extensions.checkout.exceptions import BasketNotFreeError
from ecommerce.extensions.checkout.mixins import EdxOrderPlacementMixin
@@ -88,7 +89,7 @@ def get_redirect_url(self, *args, **kwargs):
else:
# If a user's basket is empty redirect the user to the basket summary
# page which displays the appropriate message for empty baskets.
- url = reverse('basket:summary')
+ url = get_payment_microfrontend(request)
return url
diff --git a/ecommerce/extensions/payment/constants.py b/ecommerce/extensions/payment/constants.py
index a393e85ab84..2761af4506f 100644
--- a/ecommerce/extensions/payment/constants.py
+++ b/ecommerce/extensions/payment/constants.py
@@ -42,18 +42,6 @@
CLIENT_SIDE_CHECKOUT_FLAG_NAME = 'enable_client_side_checkout'
-# .. toggle_name: disable_microfrontend_for_basket_page
-# .. toggle_type: waffle_flag
-# .. toggle_default: False
-# .. toggle_description: Allows viewing the old basket page even when using a new micro-frontend based basket page
-# .. toggle_category: micro-frontend
-# .. toggle_use_cases: open_edx
-# .. toggle_creation_date: 2019-10-03
-# .. toggle_expiration_date: 2020-12-31
-# .. toggle_tickets: DEPR-42
-# .. toggle_status: supported
-DISABLE_MICROFRONTEND_FOR_BASKET_PAGE_FLAG_NAME = 'disable_microfrontend_for_basket_page'
-
# Paypal only supports 4 languages, which are prioritized by country.
# https://developer.paypal.com/docs/classic/api/locale_codes/
PAYPAL_LOCALES = {
diff --git a/ecommerce/extensions/payment/tests/views/test_cybersource.py b/ecommerce/extensions/payment/tests/views/test_cybersource.py
index 345b7c2b6e2..ab142f98500 100644
--- a/ecommerce/extensions/payment/tests/views/test_cybersource.py
+++ b/ecommerce/extensions/payment/tests/views/test_cybersource.py
@@ -19,7 +19,6 @@
from ecommerce.core.models import User
from ecommerce.extensions.api.serializers import OrderSerializer
-from ecommerce.extensions.basket.utils import get_payment_microfrontend_or_basket_url
from ecommerce.extensions.checkout.utils import get_receipt_page_url
from ecommerce.extensions.order.constants import PaymentEventTypeName
from ecommerce.extensions.payment.core.sdn import SDNClient
@@ -362,7 +361,7 @@ def test_decline(self):
request = RequestFactory(SERVER_NAME='testserver.fake').post(self.path, data)
request.site = self.site
- assert json.loads(response.content)['redirectTo'] == get_payment_microfrontend_or_basket_url(request)
+ assert json.loads(response.content)['redirectTo'] == self.site_configuration.payment_microfrontend_url
# Ensure the basket is frozen
basket = Basket.objects.get(pk=basket.pk)
@@ -392,7 +391,7 @@ def test_authorized_pending_review_request(self):
request = RequestFactory(SERVER_NAME='testserver.fake').post(self.path, data)
request.site = self.site
- assert json.loads(response.content)['redirectTo'] == get_payment_microfrontend_or_basket_url(request)
+ assert json.loads(response.content)['redirectTo'] == self.site_configuration.payment_microfrontend_url
# Ensure the basket is frozen
basket = Basket.objects.get(pk=basket.pk)
@@ -430,7 +429,7 @@ def test_authorized_pending_review_request_reversal_failed(self):
request = RequestFactory(SERVER_NAME='testserver.fake').post(self.path, data)
request.site = self.site
- assert json.loads(response.content)['redirectTo'] == get_payment_microfrontend_or_basket_url(request)
+ assert json.loads(response.content)['redirectTo'] == self.site_configuration.payment_microfrontend_url
# Ensure the basket is frozen
basket = Basket.objects.get(pk=basket.pk)
@@ -512,21 +511,6 @@ def test_post(self, status, body):
""" The view should POST to the given URL and return the response. """
self._call_to_apple_pay_and_assert_response(status, body)
- @responses.activate
- @ddt.data(*itertools.product((True, False), (True, False)))
- @ddt.unpack
- def test_with_microfrontend(self, request_from_mfe, enable_microfrontend):
- self.site.siteconfiguration.enable_microfrontend_for_basket_page = enable_microfrontend
- self.site.siteconfiguration.payment_microfrontend_url = 'http://{}'.format(self.payment_microfrontend_domain)
- self.site.siteconfiguration.save()
-
- self._call_to_apple_pay_and_assert_response(
- 200,
- {'foo': 'bar'},
- request_from_mfe,
- request_from_mfe and enable_microfrontend,
- )
-
def test_post_without_url(self):
""" The view should return HTTP 400 if no url parameter is posted. """
response = self.client.post(self.url)
diff --git a/ecommerce/extensions/payment/views/cybersource.py b/ecommerce/extensions/payment/views/cybersource.py
index 3f14b8561bf..9425d884d0c 100644
--- a/ecommerce/extensions/payment/views/cybersource.py
+++ b/ecommerce/extensions/payment/views/cybersource.py
@@ -21,7 +21,7 @@
from ecommerce.extensions.basket.utils import (
add_utm_params_to_url,
basket_add_organization_attribute,
- get_payment_microfrontend_or_basket_url
+ get_payment_microfrontend
)
from ecommerce.extensions.checkout.mixins import EdxOrderPlacementMixin
from ecommerce.extensions.checkout.utils import get_receipt_page_url
@@ -469,7 +469,7 @@ def redirect_to_receipt_page(self):
}, status=201)
def redirect_on_transaction_declined(self):
- redirect_url = get_payment_microfrontend_or_basket_url(self.request)
+ redirect_url = get_payment_microfrontend(self.request)
redirect_url = add_utm_params_to_url(redirect_url, list(self.request.GET.items()))
return JsonResponse({
'redirectTo': redirect_url,
diff --git a/ecommerce/templates/oscar/basket/basket.html b/ecommerce/templates/oscar/basket/basket.html
deleted file mode 100644
index 08ee975cc1c..00000000000
--- a/ecommerce/templates/oscar/basket/basket.html
+++ /dev/null
@@ -1,73 +0,0 @@
-{% extends 'edx/base.html' %}
-
-{% load core_extras %}
-{% load i18n %}
-{% load django_markup %}
-{% load static %}
-
-{% block title %}
-{% trans 'Basket' as tmsg %}{{ tmsg | force_escape }}
-{% endblock title %}
-
-{% block navbar %}
- {% include 'edx/partials/_student_navbar.html' %}
-{% endblock navbar %}
-
-{% block pre_app_js %}
- {# Right now, dynamic discounts only apply to single verified certs #}
- {% if formset_lines_data|length == 1 %}
-
- {% endif %}
-{% endblock %}
-
-{% block javascript %}
-
-
-
-{% endblock %}
-
-{% block skip_link %}
-
-{% endblock skip_link %}
-
-{% block content %}
-
-
-
- {# Use a partial template so that AJAX can be used to re-render basket #}
- {% if basket.is_empty %}
- {% block emptybasket %}
-
-
{% trans "Your basket is empty" as tmsg %}{{ tmsg | force_escape }}
- {% blocktrans asvar tmsg %}
- If you attempted to make a purchase, you have not been charged. Return to your {link_start}{link_middle}{homepage_url}dashboard{link_end} to try
- again, or {link_start}{homepage_url}{link_middle}contact {platform_name} Support{link_end}.
- {% endblocktrans %}
- {% interpolate_html tmsg link_start='
'|safe %}
-
- {% endblock %}
- {% else %}
- {% if enable_client_side_checkout %}
- {% include 'oscar/basket/partials/client_side_checkout_basket.html' %}
- {% else %}
- {% include 'oscar/basket/partials/hosted_checkout_basket.html' %}
- {% endif %}
- {% endif %}
-
-
-
-{% endblock content %}
-
-{% block post_js %}
- {# Load payment processor code after the basket app to ensure that all neccessary hooks are in place. #}
- {% if enable_client_side_checkout %}
- {% include client_side_payment_processor.get_template_name %}
- {% endif %}
-{% endblock %}
diff --git a/ecommerce/templates/oscar/basket/messages/new_total.html b/ecommerce/templates/oscar/basket/messages/new_total.html
deleted file mode 100644
index 9fe2b87039e..00000000000
--- a/ecommerce/templates/oscar/basket/messages/new_total.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{% load i18n %}
-{% load django_markup %}
-{% load currency_filters %}
-
-
- {% if basket.is_empty %}
- {% trans "Your basket is now empty" as tmsg %}{{ tmsg | force_escape }}
- {% else %}
- {% if basket.is_tax_known %}
- {% blocktrans asvar tmsg %}
- {strong_start}We’ve updated your quantity.{strong_end}
- {paragraph_start}Your cart includes {num_items} enrollment codes at a total cost of {total}, that you will receive via email.{paragraph_end}
- {% endblocktrans %}
- {% interpolate_html tmsg num_items=basket.num_items|safe total=basket.total_incl_tax|currency:basket.currency|safe strong_start=''|safe strong_end=''|safe paragraph_start='
'|safe paragraph_end='
'|safe %}
- {% else %}
- {% blocktrans asvar tmsg %}
- {strong_start}We’ve updated your quantity.{strong_end}
- {paragraph_start}Your cart includes {num_items} enrollment codes at a total cost of {total}, that you will receive via email.{paragraph_end}
- {% endblocktrans %}
- {% interpolate_html tmsg num_items=basket.num_items total=basket.total_excl_tax|currency:basket.currency|safe strong_start=''|safe strong_end=''|safe paragraph_start=''|safe paragraph_end='
'|safe %}
- {% endif %}
- {% endif %}
-
-
-{% if include_buttons %}
-
- {% trans "View basket" as tmsg %}{{ tmsg | force_escape }}
- {% trans "Checkout now" as tmsg %}{{ tmsg | force_escape }}
-
-{% endif %}
diff --git a/ecommerce/templates/oscar/basket/partials/add_voucher_form.html b/ecommerce/templates/oscar/basket/partials/add_voucher_form.html
deleted file mode 100644
index 49a37504031..00000000000
--- a/ecommerce/templates/oscar/basket/partials/add_voucher_form.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% load i18n %}
-
-
diff --git a/ecommerce/templates/oscar/basket/partials/client_side_checkout_basket.html b/ecommerce/templates/oscar/basket/partials/client_side_checkout_basket.html
deleted file mode 100644
index 2972e6b8052..00000000000
--- a/ecommerce/templates/oscar/basket/partials/client_side_checkout_basket.html
+++ /dev/null
@@ -1,281 +0,0 @@
-{% load i18n %}
-{% load currency_filters %}
-{% load crispy_forms_tags %}
-{% load offer_tags %}
-{% load widget_tweaks %}
-{% load static %}
-
-{% include 'oscar/partials/alert_messages.html' %}
-
-
-
-
-
{% trans "summary" as tmsg %}{{ tmsg | force_escape }}
- {% if is_enrollment_code_purchase %}
-
-
- {{line_price|currency:basket.currency}}
- {{basket.num_items}}
- {{order_total.incl_tax|currency:basket.currency}}
-
- {% else %}
-
- {% trans "Price" as tmsg %}{{ tmsg | force_escape }}
-
- {{basket.total_incl_tax_excl_discounts|currency:basket.currency}}
-
- {% endif %}
- {% if basket.total_discount %}
-
- {% trans "Discounts applied" as tmsg %}{{ tmsg | force_escape }}
-
- -{{basket.total_discount|currency:basket.currency}}
-
- {% endif %}
-
-
-
- {% trans "TOTAL" as tmsg %}{{ tmsg | force_escape }}
-
- {{ order_total.incl_tax|currency:basket.currency }}
-
-
- {% if order_details_msg %}
-
-
{% trans "order details" as tmsg %}{{ tmsg | force_escape }}
-
{{ order_details_msg | safe}}
-
- {% endif %}
-
-
-
-
diff --git a/ecommerce/templates/oscar/basket/partials/hosted_checkout_basket.html b/ecommerce/templates/oscar/basket/partials/hosted_checkout_basket.html
deleted file mode 100644
index 7a53ab61513..00000000000
--- a/ecommerce/templates/oscar/basket/partials/hosted_checkout_basket.html
+++ /dev/null
@@ -1,189 +0,0 @@
-{% load i18n %}
-{% load django_markup %}
-{% load core_extras %}
-{% load currency_filters %}
-{% load purchase_info_tags %}
-{% load widget_tweaks %}
-
-{% if not is_bulk_purchase %}
- {% include 'oscar/partials/alert_messages.html' %}
-{% endif %}
-
-
- {% block basket_form_main %}
-
- {% endblock %}
-
-
-
- {% if show_voucher_form %}
- {% block vouchers %}
- {% if basket.contains_a_voucher %}
-
- {% for voucher in basket.vouchers.all %}
-
- {% filter force_escape %}
- {% blocktrans with voucher_code=voucher.code %}
- Coupon code {{ voucher_code }} applied
- {% endblocktrans %}
- {% endfilter %}
-
-
- {% endfor %}
-
- {% else %}
- {# Hide the entire section if a custom BasketView doesn't pass in a voucher form #}
- {% if voucher_form %}
-
- {% endif %}
- {% endif %}
- {% endblock vouchers %}
- {% endif %}
-
-
- {% block order_total %}
- {% trans "Total:" as tmsg %}{{ tmsg | force_escape }}
- {{ order_total.incl_tax|currency:basket.currency }}
- {% endblock %}
-
-
-
-
-
-
- {# Switch Basket view in between single and bulk purchase items #}
- {% if partner_sku %}
-
- {% endif %}
-
-
-
-
-
-
-
-
- {# Translators: tags will bold the text within. Keep the tags and translate the text within. #}
- {% trans "{strong_start}Note:{strong_end} To complete your enrollment, select Checkout or Checkout with PayPal." as tmsg %}
- {% interpolate_html tmsg strong_start=''|safe strong_end=''|safe %}
-
-
-
-
diff --git a/ecommerce/templates/oscar/basket/partials/seat_type.html b/ecommerce/templates/oscar/basket/partials/seat_type.html
deleted file mode 100644
index 3b7f699cbec..00000000000
--- a/ecommerce/templates/oscar/basket/partials/seat_type.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% load i18n %}
-
-{% if line_data.seat_type %}
-
- {% filter force_escape %}
- {% blocktrans with seat_type=line_data.seat_type %}
- {{ seat_type }} Certificate
- {% endblocktrans %}
- {% endfilter %}
-
-{% endif %}
diff --git a/ecommerce/templates/payment/cybersource.html b/ecommerce/templates/payment/cybersource.html
deleted file mode 100644
index e1d00903216..00000000000
--- a/ecommerce/templates/payment/cybersource.html
+++ /dev/null
@@ -1,23 +0,0 @@
-{% load static %}
-
-
-{# NOTE: Using compress tags here results in the JS not being loaded. #}
-{# We have no idea why after multiple hours of investigation. #}
-
-
diff --git a/ecommerce/tests/factories.py b/ecommerce/tests/factories.py
index 8f3e724d8df..64a74678691 100644
--- a/ecommerce/tests/factories.py
+++ b/ecommerce/tests/factories.py
@@ -40,7 +40,8 @@ class Meta:
enable_sdn_check = False
enable_embargo_check = False
enable_partial_program = False
- discovery_api_url = 'http://{}.fake/'.format(Faker().domain_name())
+ discovery_api_url = f'http://{Faker().domain_name()}.fake/'
+ payment_microfrontend_url = f'http://{Faker().domain_name()}.fake/'
class StockRecordFactory(OscarStockRecordFactory):