diff --git a/src/pretix/api/serializers/event.py b/src/pretix/api/serializers/event.py index c9bc170dd..ac766fd64 100644 --- a/src/pretix/api/serializers/event.py +++ b/src/pretix/api/serializers/event.py @@ -612,6 +612,7 @@ class EventSettingsSerializer(serializers.Serializer): 'cancel_allow_user_paid_keep_fees', 'cancel_allow_user_paid_keep_percentage', 'cancel_allow_user_paid_adjust_fees', + 'cancel_allow_user_paid_refund_as_giftcard', ] def __init__(self, *args, **kwargs): diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index 1ffb0171f..9764475f3 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -1210,6 +1210,7 @@ class GiftCardPayment(BasePaymentProvider): ) refund.info_data = { 'gift_card': gc.pk, + 'gift_card_code': gc.secret, 'transaction_id': trans.pk, } refund.done() diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index e0ab3579c..d9114108f 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -1888,7 +1888,8 @@ def perform_order(self, event: Event, payment_provider: str, positions: List[str raise OrderError(str(error_messages['busy'])) -def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=OrderRefund.REFUND_SOURCE_BUYER): +def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=OrderRefund.REFUND_SOURCE_BUYER, + refund_as_giftcard=False): notify_admin = False error = False if isinstance(order, int): @@ -1897,9 +1898,49 @@ def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=Ord if refund_amount <= Decimal('0.00'): return - proposals = order.propose_auto_refunds(refund_amount) - can_auto_refund_sum = sum(proposals.values()) - can_auto_refund = (allow_partial and can_auto_refund_sum) or can_auto_refund_sum == refund_amount + if refund_as_giftcard: + proposals = {} + can_auto_refund = True + can_auto_refund_sum = refund_amount + with transaction.atomic(): + giftcard = order.event.organizer.issued_gift_cards.create( + currency=order.event.currency, + testmode=order.testmode + ) + giftcard.log_action('pretix.giftcards.created', data={}) + r = order.refunds.create( + order=order, + payment=None, + source=source, + state=OrderRefund.REFUND_STATE_CREATED, + execution_date=now(), + amount=can_auto_refund_sum, + provider='giftcard', + info=json.dumps({ + 'gift_card': giftcard.pk + }) + ) + try: + r.payment_provider.execute_refund(r) + except PaymentException as e: + with transaction.atomic(): + r.state = OrderRefund.REFUND_STATE_FAILED + r.save() + order.log_action('pretix.event.order.refund.failed', { + 'local_id': r.local_id, + 'provider': r.provider, + 'error': str(e) + }) + error = True + notify_admin = True + else: + if r.state != OrderRefund.REFUND_STATE_DONE: + notify_admin = True + + else: + proposals = order.propose_auto_refunds(refund_amount) + can_auto_refund_sum = sum(proposals.values()) + can_auto_refund = (allow_partial and can_auto_refund_sum) or can_auto_refund_sum == refund_amount if can_auto_refund: for p, value in proposals.items(): with transaction.atomic(): @@ -1961,13 +2002,13 @@ def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=Ord @app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,)) @scopes_disabled() def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_token=None, oauth_application=None, - device=None, cancellation_fee=None, try_auto_refund=False): + device=None, cancellation_fee=None, try_auto_refund=False, refund_as_giftcard=False): try: try: ret = _cancel_order(order, user, send_mail, api_token, device, oauth_application, cancellation_fee) if try_auto_refund: - _try_auto_refund(order) + _try_auto_refund(order, refund_as_giftcard=refund_as_giftcard) return ret except LockTimeoutException: self.retry() diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index 506888a07..6a2720267 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -917,6 +917,29 @@ DEFAULTS = { help_text=_("With this option enabled, your customers can choose to get a smaller refund to support you.") ) }, + 'cancel_allow_user_paid_refund_as_giftcard': { + 'default': 'off', + 'type': str, + 'serializer_class': serializers.ChoiceField, + 'serializer_kwargs': dict( + choices=[ + ('off', _('All refunds are issued to the original payment method')), + ('option', _('Customers can choose between a gift card and a refund to their payment method')), + ('force', _('All refunds are issued as gift cards')), + ], + ), + 'form_class': forms.ChoiceField, + 'form_kwargs': dict( + label=_('Refund method'), + choices=[ + ('off', _('All refunds are issued to the original payment method')), + ('option', _('Customers can choose between a gift card and a refund to their payment method')), + ('force', _('All refunds are issued as gift cards')), + ], + widget=forms.RadioSelect, + # When adding a new ordering, remember to also define it in the event model + ) + }, 'cancel_allow_user_paid_until': { 'default': None, 'type': RelativeDateWrapper, diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 0ca97b92c..5fdf5770c 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -566,6 +566,7 @@ class CancelSettingsForm(SettingsForm): 'cancel_allow_user_paid_keep_fees', 'cancel_allow_user_paid_keep_percentage', 'cancel_allow_user_paid_adjust_fees', + 'cancel_allow_user_paid_refund_as_giftcard', ] diff --git a/src/pretix/control/templates/pretixcontrol/event/cancel.html b/src/pretix/control/templates/pretixcontrol/event/cancel.html index a4eb9e9b7..1550eafe7 100644 --- a/src/pretix/control/templates/pretixcontrol/event/cancel.html +++ b/src/pretix/control/templates/pretixcontrol/event/cancel.html @@ -20,6 +20,7 @@ {% bootstrap_field form.cancel_allow_user_paid_keep_fees layout="control" %} {% bootstrap_field form.cancel_allow_user_paid_until layout="control" %} {% bootstrap_field form.cancel_allow_user_paid_adjust_fees layout="control" %} + {% bootstrap_field form.cancel_allow_user_paid_refund_as_giftcard layout="control" %} {% if not gets_notification %}
+ {% blocktrans trimmed %} + You can cancel this order, but you will not receive a refund. + {% endblocktrans %} + {% trans "This will invalidate all tickets in this order." %} +
+ {% elif order.user_cancel_fee %}{% blocktrans trimmed with fee=order.user_cancel_fee|money:request.event.currency %} You can cancel this order. In this case, a cancellation fee of {{ fee }} - will be kept and you will receive a refund of the remainder to your original payment method. + will be kept and you will receive a refund of the remainder. {% endblocktrans %} + {% if request.event.settings.cancel_allow_user_paid_refund_as_giftcard == "force" %} + {% trans "The refund will be issued in form of a gift card that you can use for further purchases." %} + {% elif request.event.settings.cancel_allow_user_paid_refund_as_giftcard == "option" %} + {% trans "The refund can be issued to your original payment method or as a gift card." %} + {% else %} + {% trans "The refund will be issued to your original payment method." %} + {% endif %} {% trans "This will invalidate all tickets in this order." %}
{% else %}{% blocktrans trimmed %} - You can cancel this order and receive a full refund to your original payment method. + You can cancel this order and receive a full refund. {% endblocktrans %} + {% if request.event.settings.cancel_allow_user_paid_refund_as_giftcard == "force" %} + {% trans "The refund will be issued in form of a gift card that you can use for further purchases." %} + {% elif request.event.settings.cancel_allow_user_paid_refund_as_giftcard == "option" %} + {% trans "The refund can be issued to your original payment method or as a gift card." %} + {% else %} + {% trans "The refund will be issued to your original payment method." %} + {% endif %} {% trans "This will invalidate all tickets in this order." %}
{% endif %} diff --git a/src/pretix/presale/templates/pretixpresale/event/order_cancel.html b/src/pretix/presale/templates/pretixpresale/event/order_cancel.html index d99ba098a..e9fd9f7c6 100644 --- a/src/pretix/presale/templates/pretixpresale/event/order_cancel.html +++ b/src/pretix/presale/templates/pretixpresale/event/order_cancel.html @@ -10,12 +10,15 @@ Cancel order: {{ code }} {% endblocktrans %} -