forked from CGM_Public/pretix_original
Allow to create invoices before bank transfer runs (#1734)
Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
@@ -168,13 +168,23 @@ class BasePaymentProvider:
|
||||
@property
|
||||
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
|
||||
aborting a pending payment can never happen, it just hides the frontend button
|
||||
to avoid users accidentally committing double payments.
|
||||
"""
|
||||
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
|
||||
def settings_form_fields(self) -> dict:
|
||||
"""
|
||||
@@ -770,7 +780,7 @@ class BasePaymentProvider:
|
||||
|
||||
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.
|
||||
|
||||
:param payment: The payment in question.
|
||||
|
||||
@@ -933,8 +933,9 @@ def _perform_order(event: Event, payment_provider: str, position_ids: List[str],
|
||||
pass
|
||||
|
||||
invoice = order.invoices.last() # Might be generated by plugin already
|
||||
if event.settings.get('invoice_generate') == 'True' and invoice_qualified(order):
|
||||
if not invoice:
|
||||
if not invoice and invoice_qualified(order):
|
||||
if event.settings.get('invoice_generate') == 'True' or (
|
||||
event.settings.get('invoice_generate') == 'paid' and payment.payment_provider.requires_invoice_immediately):
|
||||
invoice = generate_invoice(
|
||||
order,
|
||||
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 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))
|
||||
|
||||
def _check_complete_cancel(self):
|
||||
|
||||
@@ -525,7 +525,7 @@ DEFAULTS = {
|
||||
('admin', _('Only manually in admin panel')),
|
||||
('user', _('Automatically on user request')),
|
||||
('True', _('Automatically for all created orders')),
|
||||
('paid', _('Automatically on payment')),
|
||||
('paid', _('Automatically on payment or when required by payment method')),
|
||||
),
|
||||
),
|
||||
'form_kwargs': dict(
|
||||
@@ -536,7 +536,7 @@ DEFAULTS = {
|
||||
('admin', _('Only manually in admin panel')),
|
||||
('user', _('Automatically on user request')),
|
||||
('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.")
|
||||
)
|
||||
|
||||
@@ -98,6 +98,12 @@ class BankTransfer(BasePaymentProvider):
|
||||
}},
|
||||
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(
|
||||
label=_('Payment method name'),
|
||||
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 '
|
||||
'created.')
|
||||
|
||||
@property
|
||||
def requires_invoice_immediately(self):
|
||||
return self.settings.get('invoice_immediately', False, as_type=bool)
|
||||
|
||||
@property
|
||||
def settings_form_fields(self):
|
||||
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
|
||||
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
|
||||
for step in flow:
|
||||
step._previous = last
|
||||
|
||||
@@ -189,10 +189,10 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TicketPageMixin,
|
||||
[p.generate_ticket for p in ctx['cart']['positions']].count(True) > 1
|
||||
)
|
||||
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 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]
|
||||
).exists() and self.order.status == Order.STATUS_PENDING:
|
||||
ctx['generate_invoice_requires'] = 'payment'
|
||||
ctx['url'] = build_absolute_uri(
|
||||
@@ -388,6 +388,14 @@ class OrderPaymentConfirm(EventViewMixin, OrderDetailMixin, TemplateView):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
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)
|
||||
except PaymentException as e:
|
||||
messages.error(request, str(e))
|
||||
|
||||
@@ -2,19 +2,19 @@ import configparser
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import django.conf.locale
|
||||
from django.utils.crypto import get_random_string
|
||||
from kombu import Queue
|
||||
from pkg_resources import iter_entry_points
|
||||
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 django.contrib.messages import constants as messages # NOQA
|
||||
from django.utils.translation import gettext_lazy as _ # NOQA
|
||||
|
||||
config = configparser.RawConfigParser()
|
||||
if 'PRETIX_CONFIG_FILE' in os.environ:
|
||||
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')):
|
||||
import sentry_sdk
|
||||
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
|
||||
|
||||
SENTRY_ENABLED = True
|
||||
|
||||
Reference in New Issue
Block a user