mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Payment term in minutes (#1760)
Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
@@ -576,9 +576,11 @@ class EventSettingsSerializer(serializers.Serializer):
|
||||
'attendee_company_required',
|
||||
'confirm_texts',
|
||||
'order_email_asked_twice',
|
||||
'payment_term_mode',
|
||||
'payment_term_days',
|
||||
'payment_term_last',
|
||||
'payment_term_weekdays',
|
||||
'payment_term_minutes',
|
||||
'payment_term_last',
|
||||
'payment_term_expire_automatically',
|
||||
'payment_term_accept_late',
|
||||
'payment_explanation',
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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': {
|
||||
|
||||
@@ -584,9 +584,11 @@ class CancelSettingsForm(SettingsForm):
|
||||
|
||||
class PaymentSettingsForm(SettingsForm):
|
||||
auto_fields = [
|
||||
'payment_term_mode',
|
||||
'payment_term_days',
|
||||
'payment_term_last',
|
||||
'payment_term_weekdays',
|
||||
'payment_term_minutes',
|
||||
'payment_term_last',
|
||||
'payment_term_expire_automatically',
|
||||
'payment_term_accept_late',
|
||||
'payment_explanation',
|
||||
@@ -599,6 +601,18 @@ class PaymentSettingsForm(SettingsForm):
|
||||
"will set the tax rate and reverse charge rules, other settings of the tax rule are ignored.")
|
||||
)
|
||||
|
||||
def clean_payment_term_days(self):
|
||||
value = self.cleaned_data.get('payment_term_days')
|
||||
if self.cleaned_data.get('payment_term_mode') == 'days' and value is None:
|
||||
raise ValidationError(_("This field is required."))
|
||||
return value
|
||||
|
||||
def clean_payment_term_minutes(self):
|
||||
value = self.cleaned_data.get('payment_term_minutes')
|
||||
if self.cleaned_data.get('payment_term_mode') == 'minutes' and value is None:
|
||||
raise ValidationError(_("This field is required."))
|
||||
return value
|
||||
|
||||
def clean(self):
|
||||
data = super().clean()
|
||||
settings_dict = self.obj.settings.freeze()
|
||||
|
||||
@@ -59,9 +59,11 @@
|
||||
<fieldset>
|
||||
<legend>{% trans "Deadlines" %}</legend>
|
||||
{% bootstrap_form_errors form layout="control" %}
|
||||
{% bootstrap_field form.payment_term_mode layout="control" %}
|
||||
{% bootstrap_field form.payment_term_days layout="control" %}
|
||||
{% bootstrap_field form.payment_term_last layout="control" %}
|
||||
{% bootstrap_field form.payment_term_weekdays layout="control" %}
|
||||
{% bootstrap_field form.payment_term_minutes layout="control" %}
|
||||
{% bootstrap_field form.payment_term_last layout="control" %}
|
||||
{% bootstrap_field form.payment_term_expire_automatically layout="control" %}
|
||||
{% bootstrap_field form.payment_term_accept_late layout="control" %}
|
||||
</fieldset>
|
||||
|
||||
@@ -50,9 +50,9 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
||||
f = self.request.FILES.get('background')
|
||||
error = False
|
||||
if f.size > self.maxfilesize:
|
||||
error = _('The uploaded PDF file is to large.')
|
||||
error = _('The uploaded PDF file is too large.')
|
||||
if f.size < self.minfilesize:
|
||||
error = _('The uploaded PDF file is to small.')
|
||||
error = _('The uploaded PDF file is too small.')
|
||||
if mimetypes.guess_type(f.name)[0] not in self.accepted_formats:
|
||||
error = _('Please only upload PDF files.')
|
||||
# if there was an error, add error message to response_data and return
|
||||
|
||||
10
src/pretix/helpers/templatetags/expiresformat.py
Normal file
10
src/pretix/helpers/templatetags/expiresformat.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django import template
|
||||
|
||||
from pretix.base.i18n import LazyExpiresDate
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def format_expires(order):
|
||||
return LazyExpiresDate(order.expires.astimezone(order.event.timezone))
|
||||
@@ -3,6 +3,7 @@
|
||||
{% load bootstrap3 %}
|
||||
{% load eventsignal %}
|
||||
{% load money %}
|
||||
{% load expiresformat %}
|
||||
{% load eventurl %}
|
||||
{% block title %}{% trans "Order details" %}{% endblock %}
|
||||
{% block content %}
|
||||
@@ -66,7 +67,7 @@
|
||||
<strong>{% blocktrans trimmed with total=pending_sum|money:request.event.currency %}
|
||||
A payment of {{ total }} is still pending for this order.
|
||||
{% endblocktrans %}</strong>
|
||||
<strong>{% blocktrans trimmed with date=order.expires|date:"SHORT_DATE_FORMAT" %}
|
||||
<strong>{% blocktrans trimmed with date=order|format_expires %}
|
||||
Please complete your payment before {{ date }}
|
||||
{% endblocktrans %}</strong>
|
||||
{% if last_payment %}
|
||||
|
||||
Reference in New Issue
Block a user