mirror of
https://github.com/pretix/pretix.git
synced 2026-09-14 16:24:43 +00:00
Bank import: useful matching of negative transactions
Negative transactions are never matched automatically, which does not change. However, when matching them manually, a negative payment was created, which does not make much sense. Now, if a negative payment is manually matched, the system checks whether: a) There is a manual refund in pending state. In this case, the refund will be marked as done. b) There is a bank transfer payment of the same amount, in which case this is handled like a credit card chargeback (i.e. notification on the dashboard, ...) c) Otherwise, an error will be returned asking the user to create a refund object manually.
This commit is contained in:
@@ -2,6 +2,7 @@ import csv
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
@@ -14,7 +15,7 @@ from django.utils.timezone import now
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.views.generic import DetailView, ListView, View
|
from django.views.generic import DetailView, ListView, View
|
||||||
|
|
||||||
from pretix.base.models import Order, OrderPayment, Quota
|
from pretix.base.models import Order, OrderPayment, OrderRefund, Quota
|
||||||
from pretix.base.services.mail import SendMailException
|
from pretix.base.services.mail import SendMailException
|
||||||
from pretix.base.settings import SettingsSandbox
|
from pretix.base.settings import SettingsSandbox
|
||||||
from pretix.base.templatetags.money import money_filter
|
from pretix.base.templatetags.money import money_filter
|
||||||
@@ -44,6 +45,45 @@ class ActionView(View):
|
|||||||
return self._accept_ignore_amount(trans)
|
return self._accept_ignore_amount(trans)
|
||||||
|
|
||||||
def _accept_ignore_amount(self, trans):
|
def _accept_ignore_amount(self, trans):
|
||||||
|
if trans.amount < Decimal('0.00'):
|
||||||
|
ref = trans.order.refunds.filter(
|
||||||
|
amount=trans.amount * -1,
|
||||||
|
provider='manual',
|
||||||
|
state__in=(OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_STATE_CREATED)
|
||||||
|
).first()
|
||||||
|
p = trans.order.payments.filter(
|
||||||
|
amount=trans.amount * -1,
|
||||||
|
provider='banktransfer',
|
||||||
|
state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED)
|
||||||
|
).first()
|
||||||
|
if ref:
|
||||||
|
ref.done(user=self.request.user)
|
||||||
|
trans.state = BankTransaction.STATE_VALID
|
||||||
|
trans.save()
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'ok',
|
||||||
|
})
|
||||||
|
elif p:
|
||||||
|
p.create_external_refund(
|
||||||
|
amount=trans.amount * -1,
|
||||||
|
info=json.dumps({
|
||||||
|
'reference': trans.reference,
|
||||||
|
'date': trans.date,
|
||||||
|
'payer': trans.payer,
|
||||||
|
'trans_id': trans.pk
|
||||||
|
})
|
||||||
|
)
|
||||||
|
trans.state = BankTransaction.STATE_VALID
|
||||||
|
trans.save()
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'ok',
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'error',
|
||||||
|
'message': _('Negative amount but refund can\'t be logged, please create manual refund first.')
|
||||||
|
})
|
||||||
|
|
||||||
if trans.order.status == Order.STATUS_PAID:
|
if trans.order.status == Order.STATUS_PAID:
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ from datetime import timedelta
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
from django_scopes import scopes_disabled
|
||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Event, Item, Order, OrderPosition, Organizer, Quota, Team, User,
|
Event, Item, Order, OrderPayment, OrderPosition, OrderRefund, Organizer,
|
||||||
|
Quota, Team, User,
|
||||||
)
|
)
|
||||||
from pretix.plugins.banktransfer.models import BankImportJob, BankTransaction
|
from pretix.plugins.banktransfer.models import BankImportJob, BankTransaction
|
||||||
|
|
||||||
@@ -256,3 +258,75 @@ def test_assign_order_organizer_no_permission_for_event(env, client):
|
|||||||
'action_{}'.format(trans.pk): 'assign:{}-{}'.format(env[0].slug.upper(), env[2].code),
|
'action_{}'.format(trans.pk): 'assign:{}-{}'.format(env[0].slug.upper(), env[2].code),
|
||||||
}).content.decode('utf-8'))
|
}).content.decode('utf-8'))
|
||||||
assert r['status'] == 'error'
|
assert r['status'] == 'error'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retry_refund(env, client):
|
||||||
|
job = BankImportJob.objects.create(event=env[0])
|
||||||
|
trans = BankTransaction.objects.create(event=env[0], import_job=job, payer='Foo',
|
||||||
|
state=BankTransaction.STATE_ERROR,
|
||||||
|
amount=-23, date='unknown', order=env[3])
|
||||||
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
|
env[3].status = Order.STATUS_PAID
|
||||||
|
env[3].save()
|
||||||
|
r = json.loads(client.post('/control/event/{}/{}/banktransfer/action/'.format(env[0].organizer.slug, env[0].slug), {
|
||||||
|
'action_{}'.format(trans.pk): 'retry',
|
||||||
|
}).content.decode('utf-8'))
|
||||||
|
assert r['status'] == 'error'
|
||||||
|
trans.refresh_from_db()
|
||||||
|
assert trans.state == BankTransaction.STATE_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retry_refund_external(env, client):
|
||||||
|
job = BankImportJob.objects.create(event=env[0])
|
||||||
|
trans = BankTransaction.objects.create(event=env[0], import_job=job, payer='Foo',
|
||||||
|
state=BankTransaction.STATE_ERROR,
|
||||||
|
amount=-23, date='unknown', order=env[3])
|
||||||
|
with scopes_disabled():
|
||||||
|
p = env[3].payments.create(amount=23, provider='banktransfer', state=OrderPayment.PAYMENT_STATE_CONFIRMED)
|
||||||
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
|
env[3].status = Order.STATUS_PAID
|
||||||
|
env[3].save()
|
||||||
|
r = json.loads(client.post('/control/event/{}/{}/banktransfer/action/'.format(env[0].organizer.slug, env[0].slug), {
|
||||||
|
'action_{}'.format(trans.pk): 'retry',
|
||||||
|
}).content.decode('utf-8'))
|
||||||
|
assert r['status'] == 'ok'
|
||||||
|
trans.refresh_from_db()
|
||||||
|
assert trans.state == BankTransaction.STATE_VALID
|
||||||
|
env[3].refresh_from_db()
|
||||||
|
assert env[3].status == Order.STATUS_PAID
|
||||||
|
with scopes_disabled():
|
||||||
|
r = env[3].refunds.first()
|
||||||
|
assert r
|
||||||
|
assert r.provider == "banktransfer"
|
||||||
|
assert r.amount == 23
|
||||||
|
assert r.payment == p
|
||||||
|
assert r.state == OrderRefund.REFUND_STATE_EXTERNAL
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_retry_refund_complete(env, client):
|
||||||
|
job = BankImportJob.objects.create(event=env[0])
|
||||||
|
trans = BankTransaction.objects.create(event=env[0], import_job=job, payer='Foo',
|
||||||
|
state=BankTransaction.STATE_ERROR,
|
||||||
|
amount=-23, date='unknown', order=env[3])
|
||||||
|
with scopes_disabled():
|
||||||
|
env[3].payments.create(amount=23, provider='banktransfer', state=OrderPayment.PAYMENT_STATE_CONFIRMED)
|
||||||
|
ref = env[3].refunds.create(amount=23, provider='manual', state=OrderRefund.REFUND_STATE_CREATED)
|
||||||
|
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||||
|
env[3].status = Order.STATUS_CANCELED
|
||||||
|
env[3].save()
|
||||||
|
r = json.loads(client.post('/control/event/{}/{}/banktransfer/action/'.format(env[0].organizer.slug, env[0].slug), {
|
||||||
|
'action_{}'.format(trans.pk): 'retry',
|
||||||
|
}).content.decode('utf-8'))
|
||||||
|
assert r['status'] == 'ok'
|
||||||
|
trans.refresh_from_db()
|
||||||
|
assert trans.state == BankTransaction.STATE_VALID
|
||||||
|
env[3].refresh_from_db()
|
||||||
|
assert env[3].status == Order.STATUS_CANCELED
|
||||||
|
ref.refresh_from_db()
|
||||||
|
assert ref.provider == "manual"
|
||||||
|
assert ref.amount == 23
|
||||||
|
assert ref.payment is None
|
||||||
|
assert ref.state == OrderRefund.REFUND_STATE_DONE
|
||||||
|
|||||||
Reference in New Issue
Block a user