diff --git a/src/pretix/base/channels.py b/src/pretix/base/channels.py index 0a89cc2257..e3295c4b5c 100644 --- a/src/pretix/base/channels.py +++ b/src/pretix/base/channels.py @@ -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 diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index e5911772bc..ea8962596e 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -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): diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index 1d79c260b8..f646a7331c 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -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 diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index e383fbdd18..4460bc31dc 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -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() diff --git a/src/pretix/control/templates/pretixcontrol/event/payment.html b/src/pretix/control/templates/pretixcontrol/event/payment.html index 170e07b0a7..3e0bc1de0f 100644 --- a/src/pretix/control/templates/pretixcontrol/event/payment.html +++ b/src/pretix/control/templates/pretixcontrol/event/payment.html @@ -28,6 +28,12 @@ {% endif %} + + {% for channel in provider.sales_channels %} + + {% endfor %} + diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py index 0d59eccddd..6533b428c1 100644 --- a/src/pretix/control/views/event.py +++ b/src/pretix/control/views/event.py @@ -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 diff --git a/src/pretix/presale/checkoutflow.py b/src/pretix/presale/checkoutflow.py index d2a3898412..8b5e62535a 100644 --- a/src/pretix/presale/checkoutflow.py +++ b/src/pretix/presale/checkoutflow.py @@ -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 diff --git a/src/tests/control/test_events.py b/src/tests/control/test_events.py index d77ec66140..b589a30c52 100644 --- a/src/tests/control/test_events.py +++ b/src/tests/control/test_events.py @@ -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)