diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index 95342c8337..c214b9aed3 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -1,13 +1,16 @@ from collections import OrderedDict +from datetime import date from decimal import Decimal from typing import Any, Dict +import pytz from django import forms from django.contrib import messages from django.dispatch import receiver from django.forms import Form from django.http import HttpRequest from django.template.loader import get_template +from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ from pretix.base.decimal import round_decimal @@ -86,7 +89,7 @@ class BasePaymentProvider: settings keys and the values should be corresponding Django form fields. The default implementation returns the appropriate fields for the ``_enabled``, - ``_fee_abs`` and ``_fee_percent`` settings mentioned above. + ``_fee_abs``, ``_fee_percent`` and ``_availability_date`` settings mentioned above. We suggest that you return an ``OrderedDict`` object instead of a dictionary and make use of the default implementation. Your implementation could look @@ -126,6 +129,13 @@ class BasePaymentProvider: help_text=_('Percentage'), required=False )), + ('_availability_date', + forms.DateField( + label=_('Available until'), + help_text=_('Users will not be able to choose this payment provider after the given date.'), + required=False, + widget=forms.DateInput(attrs={'class': 'datepickerfield'}) + )), ('_fee_reverse_calc', forms.BooleanField( label=_('Calculate the fee from the total value including the fee.'), @@ -190,6 +200,14 @@ class BasePaymentProvider: form.fields = self.payment_form_fields return form + def _is_still_available(self, now_dt=None): + now_dt = now_dt or now() + tz = pytz.timezone(self.event.settings.timezone) + availability_date = self.settings.get('_availability_date', as_type=date) + if availability_date: + return availability_date >= now_dt.astimezone(tz).date() + return True + def is_allowed(self, request: HttpRequest) -> bool: """ You can use this method to disable this payment provider for certain groups @@ -197,9 +215,9 @@ class BasePaymentProvider: user will not be able to select this payment method. This will only be called during checkout, not on retrying. - The default implementation always returns ``True``. + The default implementation checks for the _availability_date setting to be either unset or in the future. """ - return True + return self._is_still_available() def payment_form_render(self, request: HttpRequest) -> str: """ @@ -335,11 +353,11 @@ class BasePaymentProvider: Will be called to check whether it is allowed to change the payment method of an order to this one. - The default implementation always returns ``True``. + The default implementation checks for the _availability_date setting to be either unset or in the future. :param order: The order object """ - return True + return self._is_still_available() def order_can_retry(self, order: Order) -> bool: """ diff --git a/src/tests/base/test_payment.py b/src/tests/base/test_payment.py index cc40bb4a54..51e1296795 100644 --- a/src/tests/base/test_payment.py +++ b/src/tests/base/test_payment.py @@ -1,6 +1,8 @@ +import datetime from decimal import Decimal import pytest +import pytz from django.utils.timezone import now from tests.testdummy.payment import DummyPaymentProvider @@ -50,3 +52,32 @@ def test_payment_fee_reverse_percent_and_abs_default(event): prov.settings.set('_fee_abs', Decimal('0.30')) prov.settings.set('_fee_percent', Decimal('2.90')) assert prov.calculate_fee(Decimal('100.00')) == Decimal('3.30') + + +@pytest.mark.django_db +def test_availability_date_available(event): + prov = DummyPaymentProvider(event) + prov.settings.set('_availability_date', datetime.date.today() + datetime.timedelta(days=1)) + result = prov._is_still_available() + assert result + + +@pytest.mark.django_db +def test_availability_date_not_available(event): + prov = DummyPaymentProvider(event) + prov.settings.set('_availability_date', datetime.date.today() - datetime.timedelta(days=1)) + result = prov._is_still_available() + assert not result + + +@pytest.mark.django_db +def test_availability_date_timezones(event): + event.settings.set('timezone', 'US/Pacific') + prov = DummyPaymentProvider(event) + prov.settings.set('_availability_date', '2016-12-01') + + tz = pytz.timezone('US/Pacific') + utc = pytz.timezone('UTC') + assert prov._is_still_available(tz.localize(datetime.datetime(2016, 11, 30, 23, 0, 0)).astimezone(utc)) + assert prov._is_still_available(tz.localize(datetime.datetime(2016, 12, 1, 23, 59, 0)).astimezone(utc)) + assert not prov._is_still_available(tz.localize(datetime.datetime(2016, 12, 2, 0, 0, 1)).astimezone(utc))