diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index c7239a10f8..69eb74c0bd 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -1542,7 +1542,7 @@ class OrderPayment(models.Model): return self.order.event.get_payment_providers(cached=True).get(self.provider) @transaction.atomic() - def _mark_paid(self, force, count_waitinglist, user, auth, ignore_date=False, overpaid=False): + def _mark_paid_inner(self, force, count_waitinglist, user, auth, ignore_date=False, overpaid=False): from pretix.base.signals import order_paid can_be_paid = self.order._can_be_paid(count_waitinglist=count_waitinglist, ignore_date=ignore_date, force=force) if can_be_paid is not True: @@ -1618,10 +1618,6 @@ class OrderPayment(models.Model): :type mail_text: str :raises Quota.QuotaExceededException: if the quota is exceeded and ``force`` is ``False`` """ - from pretix.base.services.invoices import ( - generate_invoice, invoice_qualified, - ) - with transaction.atomic(): locked_instance = OrderPayment.objects.select_for_update().get(pk=self.pk) if locked_instance.state == self.PAYMENT_STATE_CONFIRMED: @@ -1665,6 +1661,14 @@ class OrderPayment(models.Model): )) return + self._mark_order_paid(count_waitinglist, send_mail, force, user, auth, mail_text, ignore_date, lock, payment_sum - refund_sum) + + def _mark_order_paid(self, count_waitinglist=True, send_mail=True, force=False, user=None, auth=None, mail_text='', + ignore_date=False, lock=True, payment_refund_sum=0): + from pretix.base.services.invoices import ( + generate_invoice, invoice_qualified, + ) + if (self.order.status == Order.STATUS_PENDING and self.order.expires > now() + timedelta(seconds=LOCK_TIMEOUT * 2)) or not lock: # Performance optimization. In this case, there's really no reason to lock everything and an atomic # database transaction is more than enough. @@ -1673,8 +1677,8 @@ class OrderPayment(models.Model): lockfn = self.order.event.lock with lockfn(): - self._mark_paid(force, count_waitinglist, user, auth, overpaid=payment_sum - refund_sum > self.order.total, - ignore_date=ignore_date) + self._mark_paid_inner(force, count_waitinglist, user, auth, overpaid=payment_refund_sum > self.order.total, + ignore_date=ignore_date) invoice = None if invoice_qualified(self.order): diff --git a/src/pretix/control/views/orders.py b/src/pretix/control/views/orders.py index 68986d5b41..eaa3c1368f 100644 --- a/src/pretix/control/views/orders.py +++ b/src/pretix/control/views/orders.py @@ -1177,6 +1177,19 @@ class OrderTransition(OrderView): to = self.request.POST.get('status', '') if self.order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) and to == 'p' and self.mark_paid_form.is_valid(): ps = self.mark_paid_form.cleaned_data['amount'] + + if ps == Decimal('0.00') and self.order.pending_sum <= Decimal('0.00'): + p = self.order.payments.filter(state=OrderPayment.PAYMENT_STATE_CONFIRMED).last() + if p: + p._mark_order_paid( + user=self.request.user, + send_mail=self.mark_paid_form.cleaned_data['send_email'], + force=self.mark_paid_form.cleaned_data.get('force', False), + payment_refund_sum=self.order.payment_refund_sum, + ) + messages.success(self.request, _('The order has been marked as paid.')) + return redirect(self.get_order_url()) + try: p = self.order.payments.get( state__in=(OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED), diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index dda9350fb2..46611c0178 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -577,16 +577,22 @@ class OrderPayChangeMethod(EventViewMixin, OrderDetailMixin, TemplateView): @transaction.atomic() def mark_paid_free(self): - p = self.order.payments.create( - state=OrderPayment.PAYMENT_STATE_CREATED, - provider='manual', - amount=Decimal('0.00'), - fee=None - ) - try: - p.confirm() - except SendMailException: - pass + p = self.order.payments.filter(state=OrderPayment.PAYMENT_STATE_CONFIRMED).last() + if not p: + p = self.order.payments.create( + state=OrderPayment.PAYMENT_STATE_CREATED, + provider='free', + amount=Decimal('0.00'), + fee=None + ) + try: + p.confirm() + except SendMailException: + pass + else: + p._mark_order_paid( + payment_refund_sum=self.order.payment_refund_sum + ) def get(self, request, *args, **kwargs): if self.order.pending_sum <= Decimal('0.00'): diff --git a/src/tests/control/test_orders.py b/src/tests/control/test_orders.py index d937a9eac1..81b1c56eeb 100644 --- a/src/tests/control/test_orders.py +++ b/src/tests/control/test_orders.py @@ -1043,6 +1043,7 @@ def test_order_mark_paid_overpaid_expired(client, env): o.save() o.payments.create(state=OrderPayment.PAYMENT_STATE_CONFIRMED, amount=o.total * 2) assert o.pending_sum == -1 * o.total + assert o.payments.count() == 2 q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) @@ -1057,7 +1058,7 @@ def test_order_mark_paid_overpaid_expired(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.status == Order.STATUS_PAID - assert o.payments.last().amount == 0 + assert o.payments.count() == 2 assert o.pending_sum == -1 * o.total