Restrict Payment Providers to Sales Channels (#1481)

* Allow to restrict payment providers to specific sales channels

* Fix test

* Add `payment_restrictions_supported`-property to SalesChannels
This commit is contained in:
Martin Gross
2019-11-12 17:11:43 +01:00
committed by Raphael Michel
parent 384e7f8fc1
commit 6896682dd1
8 changed files with 51 additions and 9 deletions

View File

@@ -42,6 +42,17 @@ class SalesChannel:
"""
return True
@property
def payment_restrictions_supported(self) -> bool:
"""
If this property is ``True``, organizers can restrict the usage of payment providers to this sales channel.
Example: pretixPOS provides its own sales channel, ignores the configured payment providers completely and
handles payments locally. Therefor, this property should be set to ``False`` for the pretixPOS sales channel as
the event organizer cannot restrict the usage of any payment provider through the backend.
"""
return True
def get_all_sales_channels():
global _ALL_CHANNELS

View File

@@ -20,6 +20,7 @@ from django_countries import Countries
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
from i18nfield.strings import LazyI18nString
from pretix.base.channels import get_all_sales_channels
from pretix.base.forms import PlaceholderValidator
from pretix.base.models import (
CartPosition, Event, GiftCard, InvoiceAddress, Order, OrderPayment,
@@ -62,12 +63,11 @@ class BasePaymentProvider:
def __str__(self):
return self.identifier
@property
def is_implicit(self) -> bool:
def is_implicit(self, request: HttpRequest) -> bool:
"""
Returns whether or whether not this payment provider is an "implicit" payment provider that will
*always* and unconditionally be used if is_allowed() returns True and does not require any input.
This is intended to be used by the FreePaymentProvider, which skips the payment choice page.
This is intended to be used by the FreeOrderProvider, which skips the payment choice page.
By default, this returns ``False``. Please do not set this if you don't know exactly what you are doing.
"""
return False
@@ -278,8 +278,21 @@ class BasePaymentProvider:
required=False,
disabled=not self.event.settings.invoice_address_required
)),
('_restrict_to_sales_channels',
forms.MultipleChoiceField(
label=_('Restrict to specific sales channels'),
choices=(
(c.identifier, c.verbose_name) for c in get_all_sales_channels().values()
if c.payment_restrictions_supported
),
initial=['web'],
widget=forms.CheckboxSelectMultiple,
help_text=_(
'Only allow the usage of this payment provider in the following sales channels'),
))
])
d['_restricted_countries']._as_type = list
d['_restrict_to_sales_channels']._as_type = list
return d
def settings_form_clean(self, cleaned_data):
@@ -391,7 +404,7 @@ class BasePaymentProvider:
The default implementation checks for the _availability_date setting to be either unset or in the future
and for the _total_max and _total_min requirements to be met. It also checks the ``_restrict_countries``
setting.
and ``_restrict_to_sales_channels`` setting.
:param total: The total value without the payment method fee, after taxes.
@@ -432,6 +445,10 @@ class BasePaymentProvider:
if str(ia.country) not in restricted_countries:
return False
if hasattr(request, 'sales_channel') and request.sales_channel.identifier not in \
self.settings.get('_restrict_to_sales_channels', as_type=list, default=['web']):
return False
return timing and pricing
def payment_form_render(self, request: HttpRequest, total: Decimal) -> str:
@@ -765,8 +782,7 @@ class ManualPayment(BasePaymentProvider):
return _('In test mode, you can just manually mark this order as paid in the backend after it has been '
'created.')
@property
def is_implicit(self):
def is_implicit(self, request: HttpRequest):
return 'pretix.plugins.manualpayment' not in self.event.plugins
def is_allowed(self, request: HttpRequest, total: Decimal=None):

View File

@@ -137,6 +137,10 @@ DEFAULTS = {
'default': 'True',
'type': bool
},
'payment_resellers__restrict_to_sales_channels': {
'default': ['resellers'],
'type': list
},
'payment_term_accept_late': {
'default': 'True',
'type': bool

View File

@@ -719,7 +719,7 @@ class ProviderForm(SettingsForm):
v.set_event(self.obj)
if hasattr(v, '_as_type'):
self.initial[k] = self.obj.settings.get(k, as_type=v._as_type)
self.initial[k] = self.obj.settings.get(k, as_type=v._as_type, default=v.initial)
def clean(self):
cleaned_data = super().clean()

View File

@@ -28,6 +28,12 @@
</span>
{% endif %}
</td>
<td class="iconcol">
{% for channel in provider.sales_channels %}
<span class="fa fa-{{ channel.icon }} text-muted"
data-toggle="tooltip" title="{% trans channel.verbose_name %}"></span>
{% endfor %}
</td>
<td class="text-right flip">
<a href="{% url 'control:event.settings.payment.provider' event=request.event.slug organizer=request.organizer.slug provider=provider.identifier %}"
class="btn btn-default">

View File

@@ -392,11 +392,15 @@ class PaymentSettings(EventSettingsViewMixin, EventSettingsFormView):
context = super().get_context_data(*args, **kwargs)
context['providers'] = sorted(
[p for p in self.request.event.get_payment_providers().values()
if not p.is_implicit and (p.settings_form_fields or p.settings_content_render(self.request))],
if not (p.is_implicit(self.request) if callable(p.is_implicit) else p.is_implicit) and
(p.settings_form_fields or p.settings_content_render(self.request))],
key=lambda s: s.verbose_name
)
sales_channels = get_all_sales_channels()
for p in context['providers']:
p.show_enabled = p.is_enabled
p.sales_channels = [sales_channels[channel] for channel in p.settings.get('_restrict_to_sales_channels', as_type=list, default=['web'])]
if p.is_meta:
p.show_enabled = p.settings._enabled in (True, 'True')
return context

View File

@@ -589,7 +589,7 @@ class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
return False
for p in self.request.event.get_payment_providers().values():
if p.is_implicit:
if p.is_implicit(request) if callable(p.is_implicit) else p.is_implicit:
if self._is_allowed(p, request):
self.cart_session['payment'] = p.identifier
return False

View File

@@ -359,6 +359,7 @@ class EventsTest(SoupTest):
'payment_banktransfer__fee_abs': '12.23',
'payment_banktransfer_bank_details_type': 'other',
'payment_banktransfer_bank_details_0': 'Test',
'payment_banktransfer__restrict_to_sales_channels': ['web'],
})
self.event1.settings.flush()
assert self.event1.settings.get('payment_banktransfer__enabled', as_type=bool)