diff --git a/.gitea/workflows/cicd.yaml b/.gitea/workflows/cicd.yaml
index 3ddc71a06e..877d6f721e 100644
--- a/.gitea/workflows/cicd.yaml
+++ b/.gitea/workflows/cicd.yaml
@@ -13,6 +13,6 @@ jobs:
- name: Login to Docker Registry
run: podman login -u ${{ secrets.REGISTRY_USERNAME }} -p ${{ secrets.REGISTRY_TOKEN }} cr.ortlerstrasse.de
- name: Build Docker image
- run: podman build -t cr.ortlerstrasse.de/cgo/pretix:2025.1.0 .
+ run: podman build -t cr.ortlerstrasse.de/cgo/pretix:latest .
- name: Push Docker image
- run: podman push cr.ortlerstrasse.de/cgo/pretix:2025.1.0
\ No newline at end of file
+ run: podman push cr.ortlerstrasse.de/cgo/pretix:latest
\ No newline at end of file
diff --git a/src/pretix/__init__.py b/src/pretix/__init__.py
index 3f06d78526..7504f262fb 100644
--- a/src/pretix/__init__.py
+++ b/src/pretix/__init__.py
@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# .
#
-__version__ = "2025.1.0"
+__version__ = "2025.2.0.dev0"
diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py
index 81fa9b9e71..69bea13597 100644
--- a/src/pretix/base/payment.py
+++ b/src/pretix/base/payment.py
@@ -957,12 +957,19 @@ class BasePaymentProvider:
def cancel_payment(self, payment: OrderPayment):
"""
- Will be called to cancel a payment. The default implementation just sets the payment state to canceled,
- but in some cases you might want to notify an external provider.
+ Will be called to cancel a payment. The default implementation fails if the payment is
+ ``OrderPayment.PAYMENT_STATE_PENDING`` and ``abort_pending_allowed`` is false. Otherwise, it just sets the
+ payment state to canceled. In some cases you might want to modify this behaviour to notify the external provider
+ of the cancellation.
On success, you should set ``payment.state = OrderPayment.PAYMENT_STATE_CANCELED`` (or call the super method).
On failure, you should raise a PaymentException.
"""
+ if payment.state == OrderPayment.PAYMENT_STATE_PENDING and not self.abort_pending_allowed:
+ raise PaymentException(_(
+ "This payment is already being processed and can not be canceled any more."
+ ))
+
payment.state = OrderPayment.PAYMENT_STATE_CANCELED
payment.save(update_fields=['state'])
diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py
index 9e12e9e30b..4563183982 100644
--- a/src/pretix/base/services/orders.py
+++ b/src/pretix/base/services/orders.py
@@ -3114,14 +3114,34 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
}
)
+ new_invoice_created = False
if recreate_invoices:
+ # Lock to prevent duplicate invoice creation
+ order = Order.objects.select_for_update(of=OF_SELF).get(pk=order.pk)
+
i = order.invoices.filter(is_cancellation=False).last()
- if i and order.total != oldtotal and not i.canceled:
+ has_active_invoice = i and not i.canceled
+
+ if has_active_invoice and order.total != oldtotal:
generate_cancellation(i)
generate_invoice(order)
+ new_invoice_created = True
+
+ elif (not has_active_invoice or order.invoice_dirty) and invoice_qualified(order):
+ if order.event.settings.get('invoice_generate') == 'True' or (
+ order.event.settings.get('invoice_generate') == 'paid' and
+ new_payment.payment_provider.requires_invoice_immediately
+ ):
+ if has_active_invoice:
+ generate_cancellation(i)
+ i = generate_invoice(order)
+ new_invoice_created = True
+ order.log_action('pretix.event.order.invoice.generated', data={
+ 'invoice': i.pk
+ })
order.create_transactions()
- return old_fee, new_fee, fee, new_payment
+ return old_fee, new_fee, fee, new_payment, new_invoice_created
@receiver(order_paid, dispatch_uid="pretixbase_order_paid_giftcards")
diff --git a/src/pretix/locale/ko/LC_MESSAGES/django.po b/src/pretix/locale/ko/LC_MESSAGES/django.po
index 7970cf7fc6..0c7c1c1073 100644
--- a/src/pretix/locale/ko/LC_MESSAGES/django.po
+++ b/src/pretix/locale/ko/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-29 13:18+0000\n"
-"PO-Revision-Date: 2025-01-31 05:00+0000\n"
+"PO-Revision-Date: 2025-02-03 16:07+0000\n"
"Last-Translator: 조정화 \n"
"Language-Team: Korean \n"
@@ -319,7 +319,7 @@ msgstr "질문 간의 순환 의존성이 감지되었습니다."
#: pretix/api/serializers/item.py:543 pretix/control/forms/item.py:191
msgid "This type of question cannot be asked during check-in."
-msgstr ""
+msgstr "체크인 하는 동안에는 그러한 질문을 할 수 없습니다."
#: pretix/api/serializers/item.py:546 pretix/control/forms/item.py:199
msgid "This type of question cannot be shown during check-in."
diff --git a/src/pretix/locale/pt/LC_MESSAGES/django.po b/src/pretix/locale/pt/LC_MESSAGES/django.po
index c97f38ccff..f4fefb399a 100644
--- a/src/pretix/locale/pt/LC_MESSAGES/django.po
+++ b/src/pretix/locale/pt/LC_MESSAGES/django.po
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-29 13:18+0000\n"
-"PO-Revision-Date: 2021-08-12 21:00+0000\n"
-"Last-Translator: amandajurno \n"
+"PO-Revision-Date: 2025-02-03 16:07+0000\n"
+"Last-Translator: Cornelius Kibelka \n"
"Language-Team: Portuguese \n"
"Language: pt\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Weblate 4.6\n"
+"X-Generator: Weblate 5.9.2\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -3399,10 +3399,8 @@ msgid "The relevant plugin is currently not active."
msgstr ""
#: pretix/base/logentrytypes.py:49
-#, fuzzy
-#| msgid "Delete"
msgid "(deleted)"
-msgstr "Deletar"
+msgstr "(deletado)"
#: pretix/base/logentrytypes.py:78
#, python-brace-format
diff --git a/src/pretix/locale/pt_PT/LC_MESSAGES/django.po b/src/pretix/locale/pt_PT/LC_MESSAGES/django.po
index 476494cc23..6303157da7 100644
--- a/src/pretix/locale/pt_PT/LC_MESSAGES/django.po
+++ b/src/pretix/locale/pt_PT/LC_MESSAGES/django.po
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-29 13:18+0000\n"
-"PO-Revision-Date: 2025-01-10 20:00+0000\n"
-"Last-Translator: David Vaz \n"
+"PO-Revision-Date: 2025-02-01 17:45+0000\n"
+"Last-Translator: Vasco Baleia \n"
"Language-Team: Portuguese (Portugal) \n"
"Language: pt_PT\n"
@@ -33831,7 +33831,7 @@ msgstr "Ver outra data"
#: pretix/presale/templates/pretixpresale/event/index.html:89
msgid "Choose date to book a ticket"
-msgstr "Escolha uma data para comprar um bilhete"
+msgstr "Escolha um evento"
#: pretix/presale/templates/pretixpresale/event/index.html:152
#: pretix/presale/views/waiting.py:141 pretix/presale/views/widget.py:756
@@ -34625,11 +34625,9 @@ msgstr "Layout de bilhete PDF"
#: pretix/presale/templates/pretixpresale/fragment_event_list_status.html:18
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html:60
#: pretix/presale/views/widget.py:416
-#, fuzzy
-#| msgid "Pay now"
msgctxt "available_event_in_list"
msgid "Buy now"
-msgstr "Pagar agora"
+msgstr "Disponível"
#: pretix/presale/templates/pretixpresale/fragment_calendar.html:93
#: pretix/presale/templates/pretixpresale/fragment_calendar.html:108
diff --git a/src/pretix/plugins/banktransfer/tasks.py b/src/pretix/plugins/banktransfer/tasks.py
index 15c084d57f..174ff437e3 100644
--- a/src/pretix/plugins/banktransfer/tasks.py
+++ b/src/pretix/plugins/banktransfer/tasks.py
@@ -267,8 +267,9 @@ def _handle_transaction(trans: BankTransaction, matches: tuple, event: Event = N
if created:
# We're perform a payment method switching on-demand here
- old_fee, new_fee, fee, p = change_payment_provider(order, p.payment_provider, p.amount,
- new_payment=p, create_log=False) # noqa
+ old_fee, new_fee, fee, p, new_invoice_created = change_payment_provider(
+ order, p.payment_provider, p.amount, new_payment=p, create_log=False
+ ) # noqa
if fee:
p.fee = fee
p.save(update_fields=['fee'])
diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py
index 1843ec3988..e373e14d9e 100644
--- a/src/pretix/presale/views/order.py
+++ b/src/pretix/presale/views/order.py
@@ -86,7 +86,6 @@ from pretix.base.signals import order_modified, register_ticket_outputs
from pretix.base.templatetags.money import money_filter
from pretix.base.views.mixins import OrderQuestionsViewMixin
from pretix.base.views.tasks import AsyncAction
-from pretix.helpers import OF_SELF
from pretix.helpers.http import redirect_to_url
from pretix.helpers.safedownload import check_token
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
@@ -445,22 +444,8 @@ class OrderPaymentConfirm(EventViewMixin, OrderDetailMixin, TemplateView):
def post(self, request, *args, **kwargs):
try:
- with transaction.atomic():
- order = Order.objects.select_for_update(of=OF_SELF).get(pk=self.order.pk)
- i = order.invoices.filter(is_cancellation=False).last()
- has_active_invoice = i and not i.canceled
- if (not has_active_invoice or order.invoice_dirty) and invoice_qualified(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):
- if has_active_invoice:
- generate_cancellation(i)
- i = generate_invoice(order)
- order.log_action('pretix.event.order.invoice.generated', data={
- 'invoice': i.pk
- })
- messages.success(self.request, _('An invoice has been generated.'))
- self.payment.process_initiated = True
- self.payment.save(update_fields=['process_initiated'])
+ self.payment.process_initiated = True
+ self.payment.save(update_fields=['process_initiated'])
resp = self.payment.payment_provider.execute_payment(request, self.payment)
except PaymentException as e:
messages.error(request, str(e))
@@ -674,7 +659,12 @@ class OrderPayChangeMethod(EventViewMixin, OrderDetailMixin, TemplateView):
request.session['payment_change_{}'.format(self.order.pk)] = '1'
with transaction.atomic():
- old_fee, new_fee, fee, newpayment = change_payment_provider(self.order, p['provider'], None)
+ old_fee, new_fee, fee, newpayment, new_invoice_created = change_payment_provider(
+ self.order, p['provider'], None
+ )
+
+ if new_invoice_created:
+ messages.success(self.request, _('An invoice has been generated.'))
resp = p['provider'].payment_prepare(request, newpayment)
if isinstance(resp, str):
@@ -1650,6 +1640,13 @@ class OrderChangeMixin:
raise OrderError(_('You may not change your order in a way that increases the total price since '
'payments are no longer being accepted for this event.'))
+ if ocm._totaldiff > Decimal('0.00') and self.order.status == Order.STATUS_PENDING:
+ for p in self.order.payments.filter(state=OrderPayment.PAYMENT_STATE_PENDING):
+ if not p.payment_provider.abort_pending_allowed:
+ raise OrderError(_('You may not change your order in a way that requires additional payment while '
+ 'we are processing your current payment. Please check back after your current '
+ 'payment has been accepted.'))
+
@method_decorator(xframe_options_exempt, 'dispatch')
class OrderChange(OrderChangeMixin, EventViewMixin, OrderDetailMixin, TemplateView):
diff --git a/src/tests/plugins/banktransfer/test_import.py b/src/tests/plugins/banktransfer/test_import.py
index 05e2a76b87..49a7f8db37 100644
--- a/src/tests/plugins/banktransfer/test_import.py
+++ b/src/tests/plugins/banktransfer/test_import.py
@@ -574,7 +574,7 @@ def test_pending_paypal_drop_fee(env, job):
env[2].save()
p = env[2].payments.create(
provider='paypal',
- state=OrderPayment.PAYMENT_STATE_PENDING,
+ state=OrderPayment.PAYMENT_STATE_CREATED,
fee=fee,
amount=env[2].total
)