mirror of
https://github.com/pretix/pretix.git
synced 2025-12-05 21:32:28 +00:00
Compare commits
2 Commits
mail-model
...
payment-av
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a1e1a7de1 | ||
|
|
c246a46b15 |
@@ -336,6 +336,12 @@ class BasePaymentProvider:
|
||||
help_text=_('Users will not be able to choose this payment provider after the given date.'),
|
||||
required=False,
|
||||
)),
|
||||
('_availability_start',
|
||||
RelativeDateField(
|
||||
label=_('Available from'),
|
||||
help_text=_('Users will not be able to choose this payment provider before the given date.'),
|
||||
required=False,
|
||||
)),
|
||||
('_total_min',
|
||||
forms.DecimalField(
|
||||
label=_('Minimum order total'),
|
||||
@@ -539,40 +545,57 @@ class BasePaymentProvider:
|
||||
|
||||
return form
|
||||
|
||||
def _is_still_available(self, now_dt=None, cart_id=None, order=None):
|
||||
def _convert_availability_date_to_absolute(self, rel_date, cart_id=None, order=None):
|
||||
if not rel_date:
|
||||
return None
|
||||
# In an event series, we use min() here, which makes it less restrictive than max() and thus makes
|
||||
# it harder to put one self into a situation where no payment provider is available.
|
||||
if self.event.has_subevents and cart_id:
|
||||
dates = [
|
||||
rel_date.datetime(se).date()
|
||||
for se in self.event.subevents.filter(
|
||||
id__in=CartPosition.objects.filter(
|
||||
cart_id=cart_id, event=self.event
|
||||
).values_list('subevent', flat=True)
|
||||
)
|
||||
]
|
||||
return min(dates) if dates else None
|
||||
elif self.event.has_subevents and order:
|
||||
dates = [
|
||||
rel_date.datetime(se).date()
|
||||
for se in self.event.subevents.filter(
|
||||
id__in=order.positions.values_list('subevent', flat=True)
|
||||
)
|
||||
]
|
||||
return min(dates) if dates else None
|
||||
elif self.event.has_subevents:
|
||||
raise NotImplementedError('Payment provider is not subevent-ready.')
|
||||
else:
|
||||
return rel_date.datetime(self.event).date()
|
||||
|
||||
def _is_available_by_time(self, now_dt=None, cart_id=None, order=None):
|
||||
now_dt = now_dt or now()
|
||||
tz = ZoneInfo(self.event.settings.timezone)
|
||||
|
||||
availability_date = self.settings.get('_availability_date', as_type=RelativeDateWrapper)
|
||||
if availability_date:
|
||||
if self.event.has_subevents and cart_id:
|
||||
dates = [
|
||||
availability_date.datetime(se).date()
|
||||
for se in self.event.subevents.filter(
|
||||
id__in=CartPosition.objects.filter(
|
||||
cart_id=cart_id, event=self.event
|
||||
).values_list('subevent', flat=True)
|
||||
)
|
||||
]
|
||||
availability_date = min(dates) if dates else None
|
||||
elif self.event.has_subevents and order:
|
||||
dates = [
|
||||
availability_date.datetime(se).date()
|
||||
for se in self.event.subevents.filter(
|
||||
id__in=order.positions.values_list('subevent', flat=True)
|
||||
)
|
||||
]
|
||||
availability_date = min(dates) if dates else None
|
||||
elif self.event.has_subevents:
|
||||
logger.error('Payment provider is not subevent-ready.')
|
||||
return False
|
||||
else:
|
||||
availability_date = availability_date.datetime(self.event).date()
|
||||
try:
|
||||
availability_start = self._convert_availability_date_to_absolute(
|
||||
self.settings.get('_availability_start', as_type=RelativeDateWrapper), cart_id, order)
|
||||
|
||||
if availability_date:
|
||||
return availability_date >= now_dt.astimezone(tz).date()
|
||||
if availability_start:
|
||||
if availability_start > now_dt.astimezone(tz).date():
|
||||
return False
|
||||
|
||||
return True
|
||||
availability_end = self._convert_availability_date_to_absolute(
|
||||
self.settings.get('_availability_date', as_type=RelativeDateWrapper), cart_id, order)
|
||||
|
||||
if availability_end:
|
||||
if availability_end < now_dt.astimezone(tz).date():
|
||||
return False
|
||||
|
||||
return True
|
||||
except NotImplementedError:
|
||||
logger.exception('Unable to check availability')
|
||||
return False
|
||||
|
||||
def is_allowed(self, request: HttpRequest, total: Decimal=None) -> bool:
|
||||
"""
|
||||
@@ -581,9 +604,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 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``
|
||||
and ``_restrict_to_sales_channels`` setting.
|
||||
The default implementation checks for the ``_availability_date`` setting to be either unset or in the future
|
||||
and for the ``_availability_from``, ``_total_max``, and ``_total_min`` requirements to be met. It also checks
|
||||
the ``_restrict_countries`` and ``_restrict_to_sales_channels`` setting.
|
||||
|
||||
:param total: The total value without the payment method fee, after taxes.
|
||||
|
||||
@@ -592,7 +615,7 @@ class BasePaymentProvider:
|
||||
The ``total`` parameter has been added. For backwards compatibility, this method is called again
|
||||
without this parameter if it raises a ``TypeError`` on first try.
|
||||
"""
|
||||
timing = self._is_still_available(cart_id=get_or_create_cart_id(request))
|
||||
timing = self._is_available_by_time(cart_id=get_or_create_cart_id(request))
|
||||
pricing = True
|
||||
|
||||
if (self.settings._total_max is not None or self.settings._total_min is not None) and total is None:
|
||||
@@ -776,8 +799,8 @@ 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 checks for the _availability_date setting to be either unset or in the future,
|
||||
as well as for the _total_max, _total_min and _restricted_countries settings.
|
||||
The default implementation checks for the ``_availability_date`` setting to be either unset or in the future,
|
||||
as well as for the ``_availabilty_from``, ``_total_max``, ``_total_min``, and ``_restricted_countries`` settings.
|
||||
|
||||
:param order: The order object
|
||||
"""
|
||||
@@ -804,7 +827,7 @@ class BasePaymentProvider:
|
||||
if order.sales_channel not in self.settings.get('_restrict_to_sales_channels', as_type=list, default=['web']):
|
||||
return False
|
||||
|
||||
return self._is_still_available(order=order)
|
||||
return self._is_available_by_time(order=order)
|
||||
|
||||
def payment_prepare(self, request: HttpRequest, payment: OrderPayment) -> Union[bool, str]:
|
||||
"""
|
||||
|
||||
@@ -554,9 +554,6 @@ class StripeMethod(BasePaymentProvider):
|
||||
ctx = {'request': request, 'event': self.event, 'settings': self.settings, 'provider': self}
|
||||
return template.render(ctx)
|
||||
|
||||
def payment_can_retry(self, payment):
|
||||
return self._is_still_available(order=payment.order)
|
||||
|
||||
def _charge_source(self, request, source, payment):
|
||||
try:
|
||||
params = {}
|
||||
@@ -1581,9 +1578,6 @@ class StripeSofort(StripeMethod):
|
||||
return True
|
||||
return False
|
||||
|
||||
def payment_can_retry(self, payment):
|
||||
return payment.state != OrderPayment.PAYMENT_STATE_PENDING and self._is_still_available(order=payment.order)
|
||||
|
||||
def payment_presale_render(self, payment: OrderPayment) -> str:
|
||||
pi = payment.info_data or {}
|
||||
try:
|
||||
|
||||
@@ -97,7 +97,15 @@ def test_payment_fee_reverse_percent_and_abs_default(event):
|
||||
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()
|
||||
result = prov._is_available_by_time()
|
||||
assert result
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_availability_start_available(event):
|
||||
prov = DummyPaymentProvider(event)
|
||||
prov.settings.set('_availability_start', datetime.date.today() - datetime.timedelta(days=1))
|
||||
result = prov._is_available_by_time()
|
||||
assert result
|
||||
|
||||
|
||||
@@ -105,7 +113,15 @@ def test_availability_date_available(event):
|
||||
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()
|
||||
result = prov._is_available_by_time()
|
||||
assert not result
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_availability_start_not_available(event):
|
||||
prov = DummyPaymentProvider(event)
|
||||
prov.settings.set('_availability_start', datetime.date.today() + datetime.timedelta(days=1))
|
||||
result = prov._is_available_by_time()
|
||||
assert not result
|
||||
|
||||
|
||||
@@ -121,9 +137,26 @@ def test_availability_date_relative(event):
|
||||
))
|
||||
|
||||
utc = datetime.timezone.utc
|
||||
assert prov._is_still_available(datetime.datetime(2016, 11, 30, 23, 0, 0, tzinfo=tz).astimezone(utc))
|
||||
assert prov._is_still_available(datetime.datetime(2016, 12, 1, 23, 59, 0, tzinfo=tz).astimezone(utc))
|
||||
assert not prov._is_still_available(datetime.datetime(2016, 12, 2, 0, 0, 1, tzinfo=tz).astimezone(utc))
|
||||
assert prov._is_available_by_time(datetime.datetime(2016, 11, 30, 23, 0, 0, tzinfo=tz).astimezone(utc))
|
||||
assert prov._is_available_by_time(datetime.datetime(2016, 12, 1, 23, 59, 0, tzinfo=tz).astimezone(utc))
|
||||
assert not prov._is_available_by_time(datetime.datetime(2016, 12, 2, 0, 0, 1, tzinfo=tz).astimezone(utc))
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_availability_start_relative(event):
|
||||
event.settings.set('timezone', 'US/Pacific')
|
||||
tz = ZoneInfo('US/Pacific')
|
||||
event.date_from = datetime.datetime(2016, 12, 3, 12, 0, 0, tzinfo=tz)
|
||||
event.save()
|
||||
prov = DummyPaymentProvider(event)
|
||||
prov.settings.set('_availability_start', RelativeDateWrapper(
|
||||
RelativeDate(days_before=2, time=datetime.time(12, 0), base_date_name='date_from', minutes_before=None)
|
||||
))
|
||||
|
||||
utc = datetime.timezone.utc
|
||||
assert not prov._is_available_by_time(datetime.datetime(2016, 11, 30, 23, 0, 0, tzinfo=tz).astimezone(utc))
|
||||
assert prov._is_available_by_time(datetime.datetime(2016, 12, 1, 0, 0, tzinfo=tz).astimezone(utc))
|
||||
assert prov._is_available_by_time(datetime.datetime(2016, 12, 2, 0, 0, 1, tzinfo=tz).astimezone(utc))
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -134,9 +167,9 @@ def test_availability_date_timezones(event):
|
||||
|
||||
tz = ZoneInfo('US/Pacific')
|
||||
utc = ZoneInfo('UTC')
|
||||
assert prov._is_still_available(datetime.datetime(2016, 11, 30, 23, 0, 0, tzinfo=tz).astimezone(utc))
|
||||
assert prov._is_still_available(datetime.datetime(2016, 12, 1, 23, 59, 0, tzinfo=tz).astimezone(utc))
|
||||
assert not prov._is_still_available(datetime.datetime(2016, 12, 2, 0, 0, 1, tzinfo=tz).astimezone(utc))
|
||||
assert prov._is_available_by_time(datetime.datetime(2016, 11, 30, 23, 0, 0, tzinfo=tz).astimezone(utc))
|
||||
assert prov._is_available_by_time(datetime.datetime(2016, 12, 1, 23, 59, 0, tzinfo=tz).astimezone(utc))
|
||||
assert not prov._is_available_by_time(datetime.datetime(2016, 12, 2, 0, 0, 1, tzinfo=tz).astimezone(utc))
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -162,12 +195,12 @@ def test_availability_date_cart_relative_subevents(event):
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days_before=3, time=None, base_date_name='date_from', minutes_before=None)
|
||||
))
|
||||
assert prov._is_still_available(cart_id="123")
|
||||
assert prov._is_available_by_time(cart_id="123")
|
||||
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days_before=4, time=None, base_date_name='date_from', minutes_before=None)
|
||||
))
|
||||
assert not prov._is_still_available(cart_id="123")
|
||||
assert not prov._is_available_by_time(cart_id="123")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -201,9 +234,9 @@ def test_availability_date_order_relative_subevents(event):
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days_before=3, time=None, base_date_name='date_from', minutes_before=None)
|
||||
))
|
||||
assert prov._is_still_available(order=order)
|
||||
assert prov._is_available_by_time(order=order)
|
||||
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days_before=4, time=None, base_date_name='date_from', minutes_before=None)
|
||||
))
|
||||
assert not prov._is_still_available(order=order)
|
||||
assert not prov._is_available_by_time(order=order)
|
||||
|
||||
Reference in New Issue
Block a user