mirror of
https://github.com/pretix/pretix.git
synced 2026-05-11 16:13:59 +00:00
Allow to create invoices before bank transfer runs (#1734)
Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
@@ -126,6 +126,8 @@ The provider class
|
|||||||
|
|
||||||
.. autoattribute:: test_mode_message
|
.. autoattribute:: test_mode_message
|
||||||
|
|
||||||
|
.. autoattribute:: requires_invoice_immediately
|
||||||
|
|
||||||
|
|
||||||
Additional views
|
Additional views
|
||||||
----------------
|
----------------
|
||||||
|
|||||||
@@ -168,13 +168,23 @@ class BasePaymentProvider:
|
|||||||
@property
|
@property
|
||||||
def abort_pending_allowed(self) -> bool:
|
def abort_pending_allowed(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Whether or not a user can abort a payment in pending start to switch to another
|
Whether or not a user can abort a payment in pending state to switch to another
|
||||||
payment method. This returns ``False`` by default which is no guarantee that
|
payment method. This returns ``False`` by default which is no guarantee that
|
||||||
aborting a pending payment can never happen, it just hides the frontend button
|
aborting a pending payment can never happen, it just hides the frontend button
|
||||||
to avoid users accidentally committing double payments.
|
to avoid users accidentally committing double payments.
|
||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def requires_invoice_immediately(self):
|
||||||
|
"""
|
||||||
|
Return whether this payment method requires an invoice to exist for an order, even though the event
|
||||||
|
is configured to only create invoices for paid orders.
|
||||||
|
By default this is False, but it might be overwritten for e.g. bank transfer.
|
||||||
|
`execute_payment` is called after the invoice is created.
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def settings_form_fields(self) -> dict:
|
def settings_form_fields(self) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -770,7 +780,7 @@ class BasePaymentProvider:
|
|||||||
|
|
||||||
def matching_id(self, payment: OrderPayment):
|
def matching_id(self, payment: OrderPayment):
|
||||||
"""
|
"""
|
||||||
Will be called to get an ID for a matching this payment when comparing pretix records with records of an external
|
Will be called to get an ID for matching this payment when comparing pretix records with records of an external
|
||||||
source. This should return the main transaction ID for your API.
|
source. This should return the main transaction ID for your API.
|
||||||
|
|
||||||
:param payment: The payment in question.
|
:param payment: The payment in question.
|
||||||
|
|||||||
@@ -933,8 +933,9 @@ def _perform_order(event: Event, payment_provider: str, position_ids: List[str],
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
invoice = order.invoices.last() # Might be generated by plugin already
|
invoice = order.invoices.last() # Might be generated by plugin already
|
||||||
if event.settings.get('invoice_generate') == 'True' and invoice_qualified(order):
|
if not invoice and invoice_qualified(order):
|
||||||
if not invoice:
|
if event.settings.get('invoice_generate') == 'True' or (
|
||||||
|
event.settings.get('invoice_generate') == 'paid' and payment.payment_provider.requires_invoice_immediately):
|
||||||
invoice = generate_invoice(
|
invoice = generate_invoice(
|
||||||
order,
|
order,
|
||||||
trigger_pdf=not event.settings.invoice_email_attachment or not order.email
|
trigger_pdf=not event.settings.invoice_email_attachment or not order.email
|
||||||
@@ -1876,7 +1877,11 @@ class OrderChangeManager:
|
|||||||
if self.reissue_invoice and self._invoice_dirty:
|
if self.reissue_invoice and self._invoice_dirty:
|
||||||
if i:
|
if i:
|
||||||
self._invoices.append(generate_cancellation(i))
|
self._invoices.append(generate_cancellation(i))
|
||||||
if (i or self.event.settings.invoice_generate == 'True') and invoice_qualified(self.order):
|
if invoice_qualified(self.order) and \
|
||||||
|
(i or
|
||||||
|
self.event.settings.invoice_generate == 'True' or (
|
||||||
|
self.open_payment is not None and self.event.settings.invoice_generate == 'paid' and
|
||||||
|
self.open_payment.payment_provider.requires_invoice_immediately)):
|
||||||
self._invoices.append(generate_invoice(self.order))
|
self._invoices.append(generate_invoice(self.order))
|
||||||
|
|
||||||
def _check_complete_cancel(self):
|
def _check_complete_cancel(self):
|
||||||
|
|||||||
@@ -525,7 +525,7 @@ DEFAULTS = {
|
|||||||
('admin', _('Only manually in admin panel')),
|
('admin', _('Only manually in admin panel')),
|
||||||
('user', _('Automatically on user request')),
|
('user', _('Automatically on user request')),
|
||||||
('True', _('Automatically for all created orders')),
|
('True', _('Automatically for all created orders')),
|
||||||
('paid', _('Automatically on payment')),
|
('paid', _('Automatically on payment or when required by payment method')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
@@ -536,7 +536,7 @@ DEFAULTS = {
|
|||||||
('admin', _('Only manually in admin panel')),
|
('admin', _('Only manually in admin panel')),
|
||||||
('user', _('Automatically on user request')),
|
('user', _('Automatically on user request')),
|
||||||
('True', _('Automatically for all created orders')),
|
('True', _('Automatically for all created orders')),
|
||||||
('paid', _('Automatically on payment')),
|
('paid', _('Automatically on payment or when required by payment method')),
|
||||||
),
|
),
|
||||||
help_text=_("Invoices will never be automatically generated for free orders.")
|
help_text=_("Invoices will never be automatically generated for free orders.")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -98,6 +98,12 @@ class BankTransfer(BasePaymentProvider):
|
|||||||
}},
|
}},
|
||||||
required=False
|
required=False
|
||||||
)),
|
)),
|
||||||
|
('invoice_immediately',
|
||||||
|
forms.BooleanField(
|
||||||
|
label=_('Create an invoice for orders using bank transfer immediately if the event is otherwise '
|
||||||
|
'configured to create invoices after payment is completed.'),
|
||||||
|
required=False,
|
||||||
|
)),
|
||||||
('public_name', I18nFormField(
|
('public_name', I18nFormField(
|
||||||
label=_('Payment method name'),
|
label=_('Payment method name'),
|
||||||
widget=I18nTextInput,
|
widget=I18nTextInput,
|
||||||
@@ -119,6 +125,10 @@ class BankTransfer(BasePaymentProvider):
|
|||||||
return _('In test mode, you can just manually mark this order as paid in the backend after it has been '
|
return _('In test mode, you can just manually mark this order as paid in the backend after it has been '
|
||||||
'created.')
|
'created.')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def requires_invoice_immediately(self):
|
||||||
|
return self.settings.get('invoice_immediately', False, as_type=bool)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def settings_form_fields(self):
|
def settings_form_fields(self):
|
||||||
d = OrderedDict(list(super().settings_form_fields.items()) + list(BankTransfer.form_fields().items()))
|
d = OrderedDict(list(super().settings_form_fields.items()) + list(BankTransfer.form_fields().items()))
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ def get_checkout_flow(event):
|
|||||||
# Sort by priority
|
# Sort by priority
|
||||||
flow.sort(key=lambda p: p.priority)
|
flow.sort(key=lambda p: p.priority)
|
||||||
|
|
||||||
# Create a double-linked-list for esasy forwards/backwards traversal
|
# Create a double-linked-list for easy forwards/backwards traversal
|
||||||
last = None
|
last = None
|
||||||
for step in flow:
|
for step in flow:
|
||||||
step._previous = last
|
step._previous = last
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TicketPageMixin,
|
|||||||
[p.generate_ticket for p in ctx['cart']['positions']].count(True) > 1
|
[p.generate_ticket for p in ctx['cart']['positions']].count(True) > 1
|
||||||
)
|
)
|
||||||
ctx['invoices'] = list(self.order.invoices.all())
|
ctx['invoices'] = list(self.order.invoices.all())
|
||||||
ctx['can_generate_invoice'] = can_generate_invoice(self.request.event, self.order, True)
|
ctx['can_generate_invoice'] = can_generate_invoice(self.request.event, self.order, ignore_payments=True)
|
||||||
if ctx['can_generate_invoice']:
|
if ctx['can_generate_invoice']:
|
||||||
if not self.order.payments.exclude(
|
if not self.order.payments.exclude(
|
||||||
state__in=[OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED]
|
state__in=[OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED]
|
||||||
@@ -388,6 +388,14 @@ class OrderPaymentConfirm(EventViewMixin, OrderDetailMixin, TemplateView):
|
|||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
|
if not self.order.invoices.exists() and invoice_qualified(self.order):
|
||||||
|
if self.request.event.settings.get('invoice_generate') == 'True' or (
|
||||||
|
self.request.event.settings.get('invoice_generate') == 'paid' and self.payment.payment_provider.requires_invoice_immediately):
|
||||||
|
i = generate_invoice(self.order)
|
||||||
|
self.order.log_action('pretix.event.order.invoice.generated', data={
|
||||||
|
'invoice': i.pk
|
||||||
|
})
|
||||||
|
messages.success(self.request, _('An invoice has been generated.'))
|
||||||
resp = self.payment.payment_provider.execute_payment(request, self.payment)
|
resp = self.payment.payment_provider.execute_payment(request, self.payment)
|
||||||
except PaymentException as e:
|
except PaymentException as e:
|
||||||
messages.error(request, str(e))
|
messages.error(request, str(e))
|
||||||
|
|||||||
@@ -2,19 +2,19 @@ import configparser
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import django.conf.locale
|
||||||
|
from django.utils.crypto import get_random_string
|
||||||
from kombu import Queue
|
from kombu import Queue
|
||||||
|
from pkg_resources import iter_entry_points
|
||||||
from pycountry import currencies
|
from pycountry import currencies
|
||||||
|
|
||||||
import django.conf.locale
|
|
||||||
from django.contrib.messages import constants as messages # NOQA
|
|
||||||
from django.utils.crypto import get_random_string
|
|
||||||
from django.utils.translation import gettext_lazy as _ # NOQA
|
|
||||||
from pkg_resources import iter_entry_points
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
|
||||||
|
from django.contrib.messages import constants as messages # NOQA
|
||||||
|
from django.utils.translation import gettext_lazy as _ # NOQA
|
||||||
|
|
||||||
config = configparser.RawConfigParser()
|
config = configparser.RawConfigParser()
|
||||||
if 'PRETIX_CONFIG_FILE' in os.environ:
|
if 'PRETIX_CONFIG_FILE' in os.environ:
|
||||||
config.read_file(open(os.environ.get('PRETIX_CONFIG_FILE'), encoding='utf-8'))
|
config.read_file(open(os.environ.get('PRETIX_CONFIG_FILE'), encoding='utf-8'))
|
||||||
@@ -637,7 +637,10 @@ SENTRY_ENABLED = False
|
|||||||
if config.has_option('sentry', 'dsn') and not any(c in sys.argv for c in ('shell', 'shell_scoped', 'shell_plus')):
|
if config.has_option('sentry', 'dsn') and not any(c in sys.argv for c in ('shell', 'shell_scoped', 'shell_plus')):
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
from sentry_sdk.integrations.celery import CeleryIntegration
|
from sentry_sdk.integrations.celery import CeleryIntegration
|
||||||
from sentry_sdk.integrations.logging import LoggingIntegration, ignore_logger
|
from sentry_sdk.integrations.logging import (
|
||||||
|
LoggingIntegration, ignore_logger,
|
||||||
|
)
|
||||||
|
|
||||||
from .sentry import PretixSentryIntegration, setup_custom_filters
|
from .sentry import PretixSentryIntegration, setup_custom_filters
|
||||||
|
|
||||||
SENTRY_ENABLED = True
|
SENTRY_ENABLED = True
|
||||||
|
|||||||
Reference in New Issue
Block a user