Payment term in minutes (#1760)

Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
Felix Rindt
2020-09-14 13:44:28 +02:00
committed by GitHub
parent 2f21dc8c3c
commit 8f2c125435
12 changed files with 155 additions and 30 deletions

View File

@@ -12,7 +12,9 @@ from django.utils.timezone import now
from django.utils.translation import get_language, gettext_lazy as _
from inlinestyler.utils import inline_css
from pretix.base.i18n import LazyCurrencyNumber, LazyDate, LazyNumber
from pretix.base.i18n import (
LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber,
)
from pretix.base.models import Event
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import (
@@ -315,9 +317,8 @@ def base_placeholders(sender, **kwargs):
lambda event: LazyCurrencyNumber(Decimal('42.23'), event.currency)
),
SimpleFunctionalMailTextPlaceholder(
'expire_date', ['event', 'order'], lambda event, order: LazyDate(order.expires.astimezone(event.timezone)),
'expire_date', ['event', 'order'], lambda event, order: LazyExpiresDate(order.expires.astimezone(event.timezone)),
lambda event: LazyDate(now() + timedelta(days=15))
# TODO: This used to be "date" in some placeholders, add a migration!
),
SimpleFunctionalMailTextPlaceholder(
'url', ['order', 'event'], lambda order, event: build_absolute_uri(

View File

@@ -27,6 +27,21 @@ class LazyDate:
return date_format(self.value, "SHORT_DATE_FORMAT")
class LazyExpiresDate:
def __init__(self, expires):
self.value = expires
def __format__(self, format_spec):
return self.__str__()
def __str__(self):
at_end_of_day = self.value.hour == 23 and self.value.minute == 59 and self.value.second >= 59
if at_end_of_day:
return date_format(self.value, "SHORT_DATE_FORMAT")
else:
return date_format(self.value, "SHORT_DATETIME_FORMAT")
class LazyCurrencyNumber:
def __init__(self, value, currency):
self.value = value

View File

@@ -392,13 +392,19 @@ class Order(LockModel, LoggedModel):
def set_expires(self, now_dt=None, subevents=None):
now_dt = now_dt or now()
tz = pytz.timezone(self.event.settings.timezone)
exp_by_date = now_dt.astimezone(tz) + timedelta(days=self.event.settings.get('payment_term_days', as_type=int))
exp_by_date = exp_by_date.astimezone(tz).replace(hour=23, minute=59, second=59, microsecond=0)
if self.event.settings.get('payment_term_weekdays'):
if exp_by_date.weekday() == 5:
exp_by_date += timedelta(days=2)
elif exp_by_date.weekday() == 6:
exp_by_date += timedelta(days=1)
mode = self.event.settings.get('payment_term_mode')
if mode == 'days':
exp_by_date = now_dt.astimezone(tz) + timedelta(days=self.event.settings.get('payment_term_days', as_type=int))
exp_by_date = exp_by_date.astimezone(tz).replace(hour=23, minute=59, second=59, microsecond=0)
if self.event.settings.get('payment_term_weekdays'):
if exp_by_date.weekday() == 5:
exp_by_date += timedelta(days=2)
elif exp_by_date.weekday() == 6:
exp_by_date += timedelta(days=1)
elif mode == 'minutes':
exp_by_date = now_dt.astimezone(tz) + timedelta(minutes=self.event.settings.get('payment_term_minutes', as_type=int))
else:
raise ValueError("'payment_term_mode' has an invalid value '{}'.".format(mode))
self.expires = exp_by_date

View File

@@ -423,6 +423,29 @@ DEFAULTS = {
"if you want.")
)
},
'payment_term_mode': {
'default': 'days',
'type': str,
'form_class': forms.ChoiceField,
'serializer_class': serializers.ChoiceField,
'serializer_kwargs': dict(
choices=(
('days', _("in days")),
('minutes', _("in minutes"))
),
),
'form_kwargs': dict(
label=_("Set payment term"),
widget=forms.RadioSelect,
required=True,
choices=(
('days', _("in days")),
('minutes', _("in minutes"))
),
help_text=_("If using days, the order will expire at the end of the last day. "
"Using minutes is more exact, but should only be used for real-time payment methods.")
)
},
'payment_term_days': {
'default': '14',
'type': int,
@@ -430,11 +453,16 @@ DEFAULTS = {
'serializer_class': serializers.IntegerField,
'form_kwargs': dict(
label=_('Payment term in days'),
widget=forms.NumberInput(
attrs={
'data-display-dependency': '#id_payment_term_mode_0',
'data-required-if': '#id_payment_term_mode_0'
},
),
help_text=_("The number of days after placing an order the user has to pay to preserve their reservation. If "
"you use slow payment methods like bank transfer, we recommend 14 days. If you only use real-time "
"payment methods, we recommend still setting two or three days to allow people to retry failed "
"payments."),
required=True,
validators=[MinValueValidator(0),
MaxValueValidator(1000000)]
),
@@ -443,18 +471,6 @@ DEFAULTS = {
MaxValueValidator(1000000)]
)
},
'payment_term_last': {
'default': None,
'type': RelativeDateWrapper,
'form_class': RelativeDateField,
'serializer_class': SerializerRelativeDateField,
'form_kwargs': dict(
label=_('Last date of payments'),
help_text=_("The last date any payments are accepted. This has precedence over the number of "
"days configured above. If you use the event series feature and an order contains tickets for "
"multiple dates, the earliest date will be used."),
)
},
'payment_term_weekdays': {
'default': 'True',
'type': bool,
@@ -464,7 +480,49 @@ DEFAULTS = {
label=_('Only end payment terms on weekdays'),
help_text=_("If this is activated and the payment term of any order ends on a Saturday or Sunday, it will be "
"moved to the next Monday instead. This is required in some countries by civil law. This will "
"not effect the last date of payments configured above."),
"not effect the last date of payments configured below."),
widget=forms.CheckboxInput(
attrs={
'data-display-dependency': '#id_payment_term_mode_0',
'data-required-if': '#id_payment_term_mode_0'
},
),
)
},
'payment_term_minutes': {
'default': '30',
'type': int,
'form_class': forms.IntegerField,
'serializer_class': serializers.IntegerField,
'form_kwargs': dict(
label=_('Payment term in minutes'),
help_text=_("The number of minutes after placing an order the user has to pay to preserve their reservation. "
"Only use this if you exclusively offer real-time payment methods. Please note that for technical resons, "
"the actual time frame might be a few minutes longer before the order is marked as expired."),
validators=[MinValueValidator(0),
MaxValueValidator(1440)],
widget=forms.NumberInput(
attrs={
'data-display-dependency': '#id_payment_term_mode_1',
'data-required-if': '#id_payment_term_mode_1'
},
),
),
'serializer_kwargs': dict(
validators=[MinValueValidator(0),
MaxValueValidator(1440)]
)
},
'payment_term_last': {
'default': None,
'type': RelativeDateWrapper,
'form_class': RelativeDateField,
'serializer_class': SerializerRelativeDateField,
'form_kwargs': dict(
label=_('Last date of payments'),
help_text=_("The last date any payments are accepted. This has precedence over the terms "
"configured above. If you use the event series feature and an order contains tickets for "
"multiple dates, the earliest date will be used."),
)
},
'payment_term_expire_automatically': {