diff --git a/src/pretix/control/templates/pretixcontrol/organizers/giftcard.html b/src/pretix/control/templates/pretixcontrol/organizers/giftcard.html index b6b05d74f7..aadaaf95df 100644 --- a/src/pretix/control/templates/pretixcontrol/organizers/giftcard.html +++ b/src/pretix/control/templates/pretixcontrol/organizers/giftcard.html @@ -64,6 +64,14 @@ {{ t.order.full_code }} + {% if t.value > 0 and t.value <= card.value %} + + {% endif %} {% else %} {% trans "Manual transaction" %}{% if t.text %}: {{ t.text }}{% endif %} {% endif %} diff --git a/src/pretix/control/views/organizer.py b/src/pretix/control/views/organizer.py index a8e2980679..923d0e9d01 100644 --- a/src/pretix/control/views/organizer.py +++ b/src/pretix/control/views/organizer.py @@ -23,11 +23,12 @@ from django.views.generic import ( from pretix.api.models import WebHook from pretix.base.auth import get_auth_backends from pretix.base.models import ( - Device, GiftCard, Organizer, Team, TeamInvite, User, + Device, GiftCard, OrderPayment, Organizer, Team, TeamInvite, User, ) from pretix.base.models.event import Event, EventMetaProperty, EventMetaValue from pretix.base.models.giftcards import gen_giftcard_secret from pretix.base.models.organizer import TeamAPIToken +from pretix.base.payment import PaymentException from pretix.base.services.mail import SendMailException, mail from pretix.control.forms.filter import ( EventFilterForm, GiftCardFilterForm, OrganizerFilterForm, @@ -998,7 +999,36 @@ class GiftCardDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi @transaction.atomic() def post(self, request, *args, **kwargs): self.object = GiftCard.objects.select_for_update().get(pk=self.get_object().pk) - if 'value' in request.POST: + if 'revert' in request.POST: + t = get_object_or_404(self.object.transactions.all(), pk=request.POST.get('revert'), order__isnull=False) + if self.object.value - t.value < Decimal('0.00'): + messages.error(request, _('Gift cards are not allowed to have negative values.')) + elif t.value > 0: + r = t.order.payments.create( + order=t.order, + state=OrderPayment.PAYMENT_STATE_CREATED, + amount=t.value, + provider='giftcard', + info=json.dumps({ + 'gift_card': self.object.pk, + 'retry': True, + }) + ) + try: + r.payment_provider.execute_payment(None, r) + except PaymentException as e: + with transaction.atomic(): + r.state = OrderPayment.PAYMENT_STATE_FAILED + r.save() + t.order.log_action('pretix.event.order.payment.failed', { + 'local_id': r.local_id, + 'provider': r.provider, + 'error': str(e) + }) + messages.error(request, _('The transaction could not be reversed.')) + else: + messages.success(request, _('The transaction has been reversed.')) + elif 'value' in request.POST: try: value = DecimalField(localize=True).to_python(request.POST.get('value')) except ValidationError: diff --git a/src/tests/control/test_giftcards.py b/src/tests/control/test_giftcards.py index 47c787bb89..c4b85473ef 100644 --- a/src/tests/control/test_giftcards.py +++ b/src/tests/control/test_giftcards.py @@ -1,6 +1,12 @@ -import pytest +from datetime import timedelta -from pretix.base.models import Organizer, Team, User +import pytest +from django.utils.timezone import now +from django_scopes import scopes_disabled + +from pretix.base.models import ( + Order, OrderPayment, OrderRefund, Organizer, Team, User, +) @pytest.fixture @@ -83,6 +89,37 @@ def test_card_detail_view_transact(organizer, admin_user, gift_card, client): assert gift_card.all_logentries().count() == 1 +@pytest.mark.django_db +def test_card_detail_view_transact_revert_refund(organizer, admin_user, gift_card, client): + with scopes_disabled(): + event = organizer.events.create( + name='Dummy', slug='dummy', + date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.stripe,tests.testdummy' + ) + o = Order.objects.create( + code='FOO', event=event, email='dummy@dummy.test', + status=Order.STATUS_CANCELED, + datetime=now(), expires=now() + timedelta(days=10), + total=14, locale='en' + ) + o.payments.create( + amount=o.total, provider='banktransfer', state=OrderPayment.PAYMENT_STATE_CONFIRMED + ) + r = o.refunds.create( + amount=o.total, provider='giftcard', state=OrderRefund.REFUND_STATE_DONE + ) + t = gift_card.transactions.create(value=14, order=o, refund=r) + + client.login(email='dummy@dummy.dummy', password='dummy') + r = client.post('/control/organizer/dummy/giftcard/{}/'.format(gift_card.pk), { + 'revert': str(t.pk) + }) + assert 'alert-success' in r.rendered_content + assert gift_card.value == 42 + o.refresh_from_db() + assert o.pending_sum == -14 + + @pytest.mark.django_db def test_card_detail_view_transact_min_value(organizer, admin_user, gift_card, client): client.login(email='dummy@dummy.dummy', password='dummy')