Commit 234c223d by Peter Fogg

Allow disabling payment processors.

Adds Waffle switches of the form payment_{processor}_active which
disable the corresponding payment processor from showing up in API
responses. This results in the processor not being shown in the LMS's
payment page.

ECOM-2346
parent e317f0eb
import json
from django.conf import settings
from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.test import override_settings
from waffle.models import Switch
from ecommerce.extensions.payment.tests.processors import DummyProcessor, AnotherDummyProcessor
from ecommerce.tests.testcases import TestCase
......@@ -13,10 +15,17 @@ class PaymentProcessorListViewTests(TestCase):
def setUp(self):
self.token = self.generate_jwt_token_header(self.create_user())
self.toggle_payment_processor('dummy', True)
self.toggle_payment_processor('another-dummy', True)
# Clear the view cache
cache.clear()
def toggle_payment_processor(self, processor, active):
"""Set the given payment processor's Waffle switch."""
switch, __ = Switch.objects.get_or_create(name=settings.PAYMENT_PROCESSOR_SWITCH_PREFIX + processor)
switch.active = active
switch.save()
def assert_processor_list_matches(self, expected):
""" DRY helper. """
response = self.client.get(reverse('api:v2:payment:list_processors'), HTTP_AUTHORIZATION=self.token)
......@@ -40,3 +49,19 @@ class PaymentProcessorListViewTests(TestCase):
def test_get_many(self):
"""Ensure multiple processors in settings are handled correctly."""
self.assert_processor_list_matches([DummyProcessor.NAME, AnotherDummyProcessor.NAME])
@override_settings(PAYMENT_PROCESSORS=[
'ecommerce.extensions.payment.tests.processors.DummyProcessor',
])
def test_processor_disabled(self):
self.toggle_payment_processor('dummy', False)
self.assert_processor_list_matches([])
@override_settings(PAYMENT_PROCESSORS=[
'ecommerce.extensions.payment.tests.processors.DummyProcessor',
'ecommerce.extensions.payment.tests.processors.AnotherDummyProcessor',
])
def test_waffle_switches_clear_cache(self):
self.assert_processor_list_matches([DummyProcessor.NAME, AnotherDummyProcessor.NAME])
self.toggle_payment_processor('dummy', False)
self.assert_processor_list_matches([AnotherDummyProcessor.NAME])
from django.conf.urls import url, include
from django.views.decorators.cache import cache_page
from rest_framework_extensions.routers import ExtendedSimpleRouter
from ecommerce.core.constants import COURSE_ID_PATTERN
......@@ -37,7 +36,7 @@ ORDER_URLS = [
]
PAYMENT_URLS = [
url(r'^processors/$', cache_page(60 * 30)(payment_views.PaymentProcessorListView.as_view()),
url(r'^processors/$', payment_views.PaymentProcessorListView.as_view(),
name='list_processors'),
]
......
......@@ -2,17 +2,31 @@
from django.conf import settings
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from rest_framework_extensions.cache.decorators import cache_response
from ecommerce.extensions.api import serializers
from ecommerce.extensions.payment.helpers import get_processor_class
PAYMENT_PROCESSOR_CACHE_KEY = 'PAYMENT_PROCESSOR_LIST'
PAYMENT_PROCESSOR_CACHE_TIMEOUT = 60 * 30
class PaymentProcessorListView(generics.ListAPIView):
"""View that lists the available payment processors."""
pagination_class = None
permission_classes = (IsAuthenticated,)
serializer_class = serializers.PaymentProcessorSerializer
@cache_response(
PAYMENT_PROCESSOR_CACHE_TIMEOUT,
key_func=lambda *args, **kwargs: PAYMENT_PROCESSOR_CACHE_KEY,
cache_errors=False,
)
def get(self, request):
return super(PaymentProcessorListView, self).get(request)
def get_queryset(self):
"""Fetch the list of payment processor classes based on Django settings."""
return [get_processor_class(path) for path in settings.PAYMENT_PROCESSORS]
processors = (get_processor_class(path) for path in settings.PAYMENT_PROCESSORS)
return [processor for processor in processors if processor.is_enabled()]
......@@ -15,3 +15,7 @@ class PaymentConfig(config.PaymentConfig):
'client_id': paypal_configuration['client_id'],
'client_secret': paypal_configuration['client_secret']
})
# Register signal handlers
# noinspection PyUnresolvedReferences
import ecommerce.extensions.payment.signals # pylint: disable=unused-variable
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
from ecommerce.extensions.payment.processors.cybersource import Cybersource
from ecommerce.extensions.payment.processors.paypal import Paypal
def enable_payment_processors(apps, schema_editor):
"""
Enable both existing payment processors.
"""
Switch = apps.get_model('waffle', 'Switch')
for processor in (Cybersource, Paypal):
Switch(name=settings.PAYMENT_PROCESSOR_SWITCH_PREFIX + processor.NAME, active=True).save()
def delete_processor_switches(apps, schema_editor):
"""
Remove payment processor switches.
"""
Switch = apps.get_model('waffle', 'Switch')
for processor in (Cybersource, Paypal):
Switch.objects.get(name=settings.PAYMENT_PROCESSOR_SWITCH_PREFIX + processor.NAME).delete()
class Migration(migrations.Migration):
dependencies = [
('payment', '0005_paypalwebprofile'),
]
operations = [
migrations.RunPython(enable_payment_processors, delete_processor_switches)
]
......@@ -2,6 +2,8 @@ import abc
from django.conf import settings
from oscar.core.loading import get_model
import waffle
PaymentProcessorResponse = get_model('payment', 'PaymentProcessorResponse')
......@@ -87,3 +89,10 @@ class BasePaymentProcessor(object): # pragma: no cover
currency (string): currency of the amount to be credited
"""
raise NotImplementedError
@classmethod
def is_enabled(cls):
"""
Returns True if this payment processor is enabled, and False otherwise.
"""
return waffle.switch_is_active(settings.PAYMENT_PROCESSOR_SWITCH_PREFIX + cls.NAME)
import logging
from django.conf import settings
from django.core.cache import caches
from django.db.models.signals import post_save
from django.dispatch import receiver
from waffle.models import Switch
from ecommerce.extensions.api.v2.views.payments import PAYMENT_PROCESSOR_CACHE_KEY
logger = logging.getLogger(__name__)
@receiver(post_save, sender=Switch)
def invalidate_processor_cache(*_args, **kwargs):
"""
When Waffle switches for payment processors are toggled, the
payment processor list view cache must be invalidated.
"""
switch = kwargs['instance']
parts = switch.name.split(settings.PAYMENT_PROCESSOR_SWITCH_PREFIX)
if len(parts) == 2:
processor = parts[1]
logger.info('Switched payment processor [%s] %s.', processor, 'on' if switch.active else 'off')
caches['default'].delete(PAYMENT_PROCESSOR_CACHE_KEY)
logger.info('Invalidated payment processor cache after toggling [%s].', switch.name)
......@@ -118,6 +118,8 @@ PAYMENT_PROCESSOR_CONFIG = {
'error_url': None,
},
}
PAYMENT_PROCESSOR_SWITCH_PREFIX = 'payment_processor_active_'
# END PAYMENT PROCESSING
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment