Skip to content

Commit

Permalink
Merge pull request #4751 from mirumee/move-3d-secure-config-to-plugin…
Browse files Browse the repository at this point in the history
…-braintree

Add 3D secure require flag to braintree plugin dynamic configuration
  • Loading branch information
korycins committed Sep 23, 2019
2 parents 6c88bcb + c9ddbe1 commit 9398d4f
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Refactored the backend side of `checkoutCreate` to improve performances and prevent side effects over the user's checkout if the checkout creation was to fail. - #4367 by @NyanKiyoshi
- Refactored the logic of cleaning the checkout shipping method over the API, so users do not lose the shipping method when updating their checkout. If the shipping method becomes invalid, it will be replaced by the cheapest available. - #4367 by @NyanKiyoshi & @szewczykmira
- Refactored process of getting available shipping methods to make it easier to understand and prevent human-made errors. - #4367 by @NyanKiyoshi
- Moved 3D secure option to Braintree plugin configuration and update config structure mechanism - #4751 by @salwator

## 2.7.0

Expand Down
34 changes: 31 additions & 3 deletions saleor/extensions/base_plugin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from copy import copy
from decimal import Decimal
from typing import TYPE_CHECKING, Any, List, Union

Expand Down Expand Up @@ -221,19 +222,46 @@ def _hide_secret_configuration_fields(cls, configuration: List[dict]):
@classmethod
def _append_config_structure(cls, configuration):
config_structure = getattr(cls, "CONFIG_STRUCTURE") or {}
for coniguration_field in configuration:
for configuration_field in configuration:

structure_to_add = config_structure.get(coniguration_field.get("name"))
structure_to_add = config_structure.get(configuration_field.get("name"))
if structure_to_add:
coniguration_field.update(structure_to_add)
configuration_field.update(structure_to_add)
return config_structure

@classmethod
def _update_configuration_structure(cls, configuration):
config_structure = getattr(cls, "CONFIG_STRUCTURE") or {}
desired_config_keys = set(config_structure.keys())

config = configuration.configuration or []
configured_keys = set(d["name"] for d in config)
missing_keys = desired_config_keys - configured_keys

if not missing_keys:
return

default_config = cls._get_default_configuration()
if not default_config:
return

update_values = [
copy(k)
for k in default_config["configuration"]
if k["name"] in missing_keys
]
config.extend(update_values)

configuration.configuration = config
configuration.save(update_fields=["configuration"])

@classmethod
def get_plugin_configuration(cls, queryset: QuerySet) -> "PluginConfiguration":
defaults = cls._get_default_configuration()
configuration = queryset.get_or_create(name=cls.PLUGIN_NAME, defaults=defaults)[
0
]
cls._update_configuration_structure(configuration)
if configuration.configuration:
# Let's add a translated descriptions and labels
cls._append_config_structure(configuration.configuration)
Expand Down
5 changes: 1 addition & 4 deletions saleor/payment/gateways/braintree/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@
from .errors import DEFAULT_ERROR_MESSAGE, BraintreeException
from .forms import BraintreePaymentForm

# FIXME: Move to SiteSettings
THREE_D_SECURE_REQUIRED = False

# Error codes whitelist should be a dict of code: error_msg_override
# if no error_msg_override is provided,
# then error message returned by the gateway will be used
Expand Down Expand Up @@ -170,7 +167,7 @@ def transaction_for_new_customer(
"options": {
"submit_for_settlement": config.auto_capture,
"store_in_vault_on_success": payment_information.reuse_source,
"three_d_secure": {"required": THREE_D_SECURE_REQUIRED},
"three_d_secure": {"required": config.require_3d_secure},
},
**get_customer_data(payment_information),
}
Expand Down
10 changes: 10 additions & 0 deletions saleor/payment/gateways/braintree/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ class BraintreeGatewayPlugin(BasePlugin):
),
"label": pgettext_lazy("Plugin label", "Automatic payment capture"),
},
"Require 3D secure": {
"type": ConfigurationTypeField.BOOLEAN,
"help_text": pgettext_lazy(
"Plugin help text",
"Determines if Saleor should enforce 3D secure during payment.",
),
"label": pgettext_lazy("Plugin label", "Require 3D secure"),
},
}

def __init__(self, *args, **kwargs):
Expand All @@ -108,6 +116,7 @@ def _initialize_plugin_configuration(self):
},
template_path="",
store_customer=configuration["Store customers card"],
require_3d_secure=configuration["Require 3D secure"],
)

@classmethod
Expand All @@ -123,6 +132,7 @@ def _get_default_configuration(cls):
{"name": "Merchant ID", "value": ""},
{"name": "Store customers card", "value": False},
{"name": "Automatic payment capture", "value": True},
{"name": "Require 3D secure", "value": False},
],
}
return defaults
Expand Down
1 change: 1 addition & 0 deletions saleor/payment/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class GatewayConfig:
# a unified structure
connection_params: Dict[str, Any]
store_customer: bool = False
require_3d_secure: bool = False


@dataclass
Expand Down
82 changes: 81 additions & 1 deletion tests/extensions/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@

from saleor.core.payments import Gateway
from saleor.core.taxes import TaxType

from saleor.extensions import ConfigurationTypeField
from saleor.extensions.base_plugin import BasePlugin
from saleor.extensions.manager import ExtensionsManager, get_extensions_manager
from saleor.extensions.models import PluginConfiguration


class SamplePlugin(BasePlugin):
PLUGIN_NAME = "Sample Plugin"
CONFIG_STRUCTURE = {
"Test": {"type": ConfigurationTypeField.BOOLEAN, "help_text": "", "label": ""}
}

def calculate_checkout_total(self, checkout, discounts, previous_value):
total = Money("1.0", currency=checkout.get_total().currency)
Expand Down Expand Up @@ -70,13 +75,14 @@ def _get_default_configuration(cls):
"name": "Sample Plugin",
"description": "",
"active": True,
"configuration": None,
"configuration": [{"name": "Test", "value": True}],
}
return defaults


class SamplePlugin1(BasePlugin):
PLUGIN_NAME = "Sample Plugin1"
CONFIG_STRUCTURE = {}

@classmethod
def _get_default_configuration(cls):
Expand Down Expand Up @@ -294,6 +300,80 @@ def test_manager_save_plugin_configuration():
assert not configuration.active


@pytest.fixture
def new_config():
return {"name": "Foo", "value": "bar"}


@pytest.fixture
def new_config_structure():
return {"type": ConfigurationTypeField.STRING, "help_text": "foo", "label": "foo"}


@pytest.fixture
def manager_with_plugin_enabled():
plugins = ["tests.extensions.test_manager.SamplePlugin"]
manager = ExtensionsManager(plugins=plugins)
manager.get_plugin_configuration(plugin_name="Sample Plugin")
return manager


def test_plugin_updates_configuration_shape(
new_config, new_config_structure, manager_with_plugin_enabled
):
@classmethod
def new_default_configuration(cls):
defaults = {
"name": "Sample Plugin",
"description": "",
"active": True,
"configuration": [{"name": "Test", "value": True}, new_config],
}
return defaults

SamplePlugin._get_default_configuration = new_default_configuration
SamplePlugin.CONFIG_STRUCTURE["Foo"] = new_config_structure

configuration = manager_with_plugin_enabled.get_plugin_configuration(
plugin_name="Sample Plugin"
)
configuration.refresh_from_db()
assert len(configuration.configuration) == 2
assert configuration.configuration[1] == new_config


@pytest.fixture
def manager_with_plugin_without_configuration_enabled():
plugins = ["tests.extensions.test_manager.SamplePlugin1"]
manager = ExtensionsManager(plugins=plugins)
manager.get_plugin_configuration(plugin_name="Sample Plugin1")
return manager


def test_plugin_add_new_configuration(
new_config, new_config_structure, manager_with_plugin_without_configuration_enabled
):
@classmethod
def new_default_configuration(cls):
defaults = {
"name": "Sample Plugin",
"description": "",
"active": True,
"configuration": [new_config],
}
return defaults

SamplePlugin1._get_default_configuration = new_default_configuration
SamplePlugin1.CONFIG_STRUCTURE["Foo"] = new_config_structure

config = manager_with_plugin_without_configuration_enabled.get_plugin_configuration(
plugin_name="Sample Plugin1"
)
config.refresh_from_db()
assert len(config.configuration) == 1
assert config.configuration[0] == new_config


class ActivePaymentGateway(BasePlugin):
PLUGIN_NAME = "braintree"

Expand Down

0 comments on commit 9398d4f

Please sign in to comment.