diff --git a/src/pretix/plugins/banktransfer/tasks.py b/src/pretix/plugins/banktransfer/tasks.py index 75877576c..023ab0f67 100644 --- a/src/pretix/plugins/banktransfer/tasks.py +++ b/src/pretix/plugins/banktransfer/tasks.py @@ -32,6 +32,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under the License. +import json import logging import re from decimal import Decimal @@ -42,13 +43,14 @@ from django.conf import settings from django.db import transaction from django.db.models import Max, Min, Q from django.db.models.functions import Length +from django.utils.timezone import now from django.utils.translation import gettext_noop from django_scopes import scope, scopes_disabled from pretix.base.email import get_email_context from pretix.base.i18n import language from pretix.base.models import ( - Event, Invoice, Order, OrderPayment, Organizer, Quota, + Event, Invoice, Order, OrderPayment, OrderRefund, Organizer, Quota, ) from pretix.base.payment import PaymentException from pretix.base.services.locking import LockTimeoutException @@ -194,6 +196,53 @@ def _handle_transaction(trans: BankTransaction, matches: tuple, event: Event = N trans.state = BankTransaction.STATE_VALID for order, amount in splits: + info_data = { + 'reference': trans.reference, + 'date': trans.date_parsed.isoformat() if trans.date_parsed else trans.date, + 'payer': trans.payer, + 'iban': trans.iban, + 'bic': trans.bic, + 'full_amount': str(trans.amount), + 'trans_id': trans.pk + } + if amount < Decimal("0.00"): + pending_refund = order.refunds.filter( + amount=-amount, + provider__in=('manual', 'banktransfer'), + state__in=(OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_STATE_TRANSIT), + ).first() + existing_payment = order.payments.filter( + provider='banktransfer', + state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED,), + ).first() + if pending_refund: + pending_refund.provider = "banktransfer" + pending_refund.info_data = { + **pending_refund.info_data, + **info_data, + } + pending_refund.done() + elif existing_payment: + existing_payment.create_external_refund( + amount=-amount, + info=json.dumps(info_data) + ) + else: + r = order.refunds.create( + state=OrderRefund.REFUND_STATE_EXTERNAL, + source=OrderRefund.REFUND_SOURCE_EXTERNAL, + amount=-amount, + order=order, + execution_date=now(), + provider='banktransfer', + info=json.dumps(info_data) + ) + order.log_action('pretix.event.order.refund.created.externally', { + 'local_id': r.local_id, + 'provider': r.provider, + }) + continue + try: p, created = order.payments.get_or_create( amount=amount, @@ -213,13 +262,7 @@ def _handle_transaction(trans: BankTransaction, matches: tuple, event: Event = N p.info_data = { **p.info_data, - 'reference': trans.reference, - 'date': trans.date_parsed.isoformat() if trans.date_parsed else trans.date, - 'payer': trans.payer, - 'iban': trans.iban, - 'bic': trans.bic, - 'full_amount': str(trans.amount), - 'trans_id': trans.pk + **info_data, } if created: diff --git a/src/tests/plugins/banktransfer/test_import.py b/src/tests/plugins/banktransfer/test_import.py index 549600d4a..c2a22b537 100644 --- a/src/tests/plugins/banktransfer/test_import.py +++ b/src/tests/plugins/banktransfer/test_import.py @@ -43,8 +43,8 @@ from django.utils.timezone import now from django_scopes import scopes_disabled from pretix.base.models import ( - Event, Item, Order, OrderFee, OrderPayment, OrderPosition, Organizer, - Quota, Team, User, + Event, Item, Order, OrderFee, OrderPayment, OrderPosition, OrderRefund, + Organizer, Quota, Team, User, ) from pretix.base.services.invoices import generate_invoice from pretix.plugins.banktransfer.models import BankImportJob, BankTransaction @@ -141,6 +141,11 @@ def orga_job(env): return BankImportJob.objects.create(organizer=env[0].organizer).pk +@pytest.fixture +def orga_job2(env): + return BankImportJob.objects.create(organizer=env[0].organizer).pk + + @pytest.mark.django_db def test_mark_paid(env, job): djmail.outbox = [] @@ -610,3 +615,81 @@ def test_pending_paypal_replace_fee_missing(env, job): assert env[2].fees.count() == 1 assert env[2].fees.last().value == Decimal('1.00') assert env[2].total == Decimal('24.00') + + +@pytest.mark.django_db +def test_refund_handling_no_payments(env, orga_job): + process_banktransfers(orga_job, [{ + 'payer': 'Karla Kundin', + 'reference': 'Bestellung DUMMY-1234S', + 'date': '2016-01-26', + 'amount': '-23.00' + }]) + with scopes_disabled(): + env[2].refresh_from_db() + assert env[2].status == Order.STATUS_PENDING + assert env[2].payments.count() == 0 + r = env[2].refunds.get() + assert r.state == OrderRefund.REFUND_STATE_EXTERNAL + assert r.amount == Decimal("23.00") + + +@pytest.mark.django_db +def test_refund_handling_refund_for_payment(env, orga_job, orga_job2): + process_banktransfers(orga_job, [{ + 'payer': 'Karla Kundin', + 'reference': 'Bestellung DUMMY-1234S', + 'date': '2016-01-26', + 'amount': '13.00' + }]) + with scopes_disabled(): + env[2].refresh_from_db() + assert env[2].status == Order.STATUS_PENDING + p = env[2].payments.get() + + process_banktransfers(orga_job2, [{ + 'payer': 'Karla Kundin', + 'reference': 'Erstattung DUMMY-1234S', + 'date': '2016-01-27', + 'amount': '-13.00' + }]) + with scopes_disabled(): + env[2].refresh_from_db() + assert env[2].status == Order.STATUS_PENDING + assert env[2].payments.count() == 1 + r = env[2].refunds.get() + assert r.state == OrderRefund.REFUND_STATE_EXTERNAL + assert r.payment == p + assert r.amount == Decimal("13.00") + + +@pytest.mark.django_db +def test_refund_handling_pending_refund(env, orga_job, orga_job2): + process_banktransfers(orga_job, [{ + 'payer': 'Karla Kundin', + 'reference': 'Bestellung DUMMY-1234S', + 'date': '2016-01-26', + 'amount': '23.00' + }]) + with scopes_disabled(): + env[2].refresh_from_db() + assert env[2].status == Order.STATUS_PAID + env[2].status = Order.STATUS_PENDING + env[2].save() + r = env[2].refunds.create( + state=OrderRefund.REFUND_STATE_CREATED, + provider="manual", + amount="23.00", + ) + + process_banktransfers(orga_job2, [{ + 'payer': 'Karla Kundin', + 'reference': 'Erstattung DUMMY-1234S', + 'date': '2016-01-27', + 'amount': '-23.00' + }]) + with scopes_disabled(): + env[2].refresh_from_db() + r.refresh_from_db() + assert env[2].payments.count() == 1 + assert r.state == OrderRefund.REFUND_STATE_DONE