Skip to content

Conversation

@ArnavBallinCode
Copy link
Contributor

@ArnavBallinCode ArnavBallinCode commented Nov 13, 2025

(Fix)gift card 500 error

The issue is in the GiftCardPayment class in /app/eventyay/base/payment.py at 2 places:

1: checkout_prepare method (line ~1257)

2: payment_prepare method (line ~1338)

gc = self.event.organizer.accepted_gift_cards.get(secret=request.POST.get('giftcard'))
  1. accepted_gift_cards is a @property that returns an annotated queryset:

    @property
    def accepted_gift_cards(self):
        return GiftCard.objects.annotate(
            accepted=Exists(...)
        ).filter(Q(issuer=self) | Q(accepted=True))
  2. When calling .get() directly on this property, there might be issues with:

    • Case sensitivity in the secret matching
    • The queryset not being properly evaluated
    • Missing .all() before .get()
  3. Django's .get() on an annotated queryset can sometimes fail if the secret comparison isn't exact or if there's a case mismatch

Solution

Use .filter() with .first() instead of .get() for more robust handling, or use iexact for case-insensitive matching similar to how vouchers are handled elsewhere in the codebase.

Actually, looking at line 1309:

if self.event.vouchers.filter(code__iexact=request.POST.get('giftcard')).exists():

The code already uses code__iexact for vouchers, suggesting that case-insensitive matching is expected. The gift card lookup should use the same pattern!

The Fix:

Change from:

gc = self.event.organizer.accepted_gift_cards.get(secret=request.POST.get('giftcard'))

To:

gc = self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard')).first()
if not gc:
    raise GiftCard.DoesNotExist()

Summary by Sourcery

Fix gift card lookup in checkout_prepare and payment_prepare to prevent server errors and support case-insensitive matching

Bug Fixes:

  • Replace direct get() on accepted_gift_cards with filter(secret__iexact).first() and raise GiftCard.DoesNotExist when missing to avoid 500 errors

Enhancements:

  • Align gift card secret matching with vouchers by using case-insensitive lookup

fixes #1252

Summary by Sourcery

Use filter(secret__iexact).first() for gift card lookup in checkout_prepare and payment_prepare, remove DoesNotExist exceptions, and add user feedback for voucher misuse or invalid gift cards.

Bug Fixes:

  • Prevent server errors when looking up gift cards by replacing direct get() calls with case-insensitive filter().first()

Enhancements:

  • Match gift card secrets using case-insensitive lookup consistent with voucher code handling
  • Add warning when a voucher code is entered in place of a gift card and error for unknown gift cards

- Changed from .get() to .filter(secret__iexact=...).first()
- Added explicit null check: if not gc: raise GiftCard.DoesNotExist()
- Case-insensitive matching now works (YSV9AYRKW7HP == ysv9ayrkw7hp)
- More robust querying on annotated querysets
- Consistent with voucher code handling

- GiftCardPayment.checkout_prepare (line ~1257)
- GiftCardPayment.payment_prepare (line ~1338)

-Backward compatible
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Nov 13, 2025

Reviewer's Guide

This PR replaces direct .get() calls on the annotated accepted_gift_cards queryset with case-insensitive .filter().first(), adds unified messaging and early return for missing gift cards or voucher misentries, and removes obsolete DoesNotExist exception blocks in both checkout_prepare and payment_prepare to prevent server errors.

Sequence diagram for improved gift card lookup in checkout_prepare

sequenceDiagram
actor User
participant "GiftCardPayment"
participant "Organizer"
participant "GiftCard"
participant "Voucher"
participant "Messages"
User->>"GiftCardPayment": Submit gift card code
"GiftCardPayment"->>"Organizer": Access accepted_gift_cards
"GiftCardPayment"->>"GiftCard": Filter secret__iexact=code
alt Gift card found
    "GiftCardPayment"->>"GiftCard": Check currency
    alt Currency matches
        "GiftCardPayment"-->>User: Proceed with payment
    else Currency mismatch
        "Messages"-->>User: Error: currency not supported
    end
else Gift card not found
    "GiftCardPayment"->>"Voucher": Filter code__iexact=code
    alt Voucher found
        "Messages"-->>User: Warning: entered voucher instead of gift card
    else Voucher not found
        "Messages"-->>User: Error: gift card not known
    end
end
Loading

Sequence diagram for improved gift card lookup in payment_prepare

sequenceDiagram
actor User
participant "GiftCardPayment"
participant "Organizer"
participant "GiftCard"
participant "Voucher"
participant "Messages"
User->>"GiftCardPayment": Submit gift card code
"GiftCardPayment"->>"Organizer": Access accepted_gift_cards
"GiftCardPayment"->>"GiftCard": Filter secret__iexact=code
alt Gift card found
    "GiftCardPayment"->>"GiftCard": Check currency
    alt Currency matches
        "GiftCardPayment"-->>User: Complete payment
    else Currency mismatch
        "Messages"-->>User: Error: currency not supported
    end
else Gift card not found
    "GiftCardPayment"->>"Voucher": Filter code__iexact=code
    alt Voucher found
        "Messages"-->>User: Warning: entered voucher instead of gift card
    else Voucher not found
        "Messages"-->>User: Error: gift card not known
    end
end
Loading

File-Level Changes

Change Details Files
Update checkout_prepare gift card lookup and handling
  • Switch .get() to .filter(secret__iexact).first()
  • Insert unified voucher vs gift card missing-case messages and early return
  • Remove GiftCard.DoesNotExist exception catcher
app/eventyay/base/payment.py
Update payment_prepare gift card lookup and handling
  • Switch .get() to .filter(secret__iexact).first()
  • Insert unified voucher vs gift card missing-case messages and early return
  • Remove GiftCard.DoesNotExist exception catcher
app/eventyay/base/payment.py

Assessment against linked issues

Issue Objective Addressed Explanation
#1252 Fix the HTTP 500 error when using a gift card received from a refund during checkout.
#1252 Ensure that gift cards received from refunds are accepted and applied correctly during the checkout process.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • The gift card lookup logic is duplicated in both methods – consider extracting it into a shared helper to reduce code repetition.
  • You may want to add an early check for missing or empty giftcard input to provide a more specific error message rather than relying on DoesNotExist.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The gift card lookup logic is duplicated in both methods – consider extracting it into a shared helper to reduce code repetition.
- You may want to add an early check for missing or empty `giftcard` input to provide a more specific error message rather than relying on DoesNotExist.

## Individual Comments

### Comment 1
<location> `app/eventyay/base/payment.py:1259` </location>
<code_context>
-            gc = self.event.organizer.accepted_gift_cards.get(secret=request.POST.get('giftcard'))
+            gc = self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard')).first()
+            if not gc:
+                raise GiftCard.DoesNotExist()
             if gc.currency != self.event.currency:
                 messages.error(request, _('This gift card does not support this currency.'))
</code_context>

<issue_to_address>
**suggestion:** Raising GiftCard.DoesNotExist may not provide user-friendly feedback.

Consider displaying an error message to the user if the gift card is not found, rather than raising an exception, to align with the handling of currency mismatches and enhance user experience.

Suggested implementation:

```python
            gc = self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard')).first()
            if not gc:
                messages.error(request, _('Gift card not found. Please check the code and try again.'))
                return
            if gc.currency != self.event.currency:
                messages.error(request, _('This gift card does not support this currency.'))
                return

```

```python
            gc = self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard')).first()
            if not gc:
                messages.error(request, _('Gift card not found. Please check the code and try again.'))
                return
            if gc.currency != self.event.currency:
                messages.error(request, _('This gift card does not support this currency.'))

```
</issue_to_address>

### Comment 2
<location> `app/eventyay/base/payment.py:1257` </location>
<code_context>
    def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> Union[bool, str, None]:
        for p in get_cart(request):
            if p.item.issue_giftcard:
                messages.error(
                    request,
                    _('You cannot pay with gift cards when buying a gift card.'),
                )
                return

        cs = cart_session(request)
        try:
            gc = self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard')).first()
            if not gc:
                raise GiftCard.DoesNotExist()
            if gc.currency != self.event.currency:
                messages.error(request, _('This gift card does not support this currency.'))
                return
            if gc.testmode and not self.event.testmode:
                messages.error(request, _('This gift card can only be used in test mode.'))
                return
            if not gc.testmode and self.event.testmode:
                messages.error(request, _('Only test gift cards can be used in test mode.'))
                return
            if gc.expires and gc.expires < now():
                messages.error(request, _('This gift card is no longer valid.'))
                return
            if gc.value <= Decimal('0.00'):
                messages.error(request, _('All credit on this gift card has been used.'))
                return
            if 'gift_cards' not in cs:
                cs['gift_cards'] = []
            elif gc.pk in cs['gift_cards']:
                messages.error(request, _('This gift card is already used for your payment.'))
                return
            cs['gift_cards'] = cs['gift_cards'] + [gc.pk]

            total = sum(p.total for p in cart['positions'])
            # Recompute fees. Some plugins, e.g. pretix-servicefees, change their fee schedule if a gift card is
            # applied.
            fees = get_fees(
                self.event,
                request,
                total,
                cart['invoice_address'],
                cs.get('payment'),
                cart['raw'],
            )
            total += sum([f.value for f in fees])
            remainder = total
            if remainder > Decimal('0.00'):
                del cs['payment']
                messages.success(
                    request,
                    _(
                        'Your gift card has been applied, but {} still need to be paid. Please select a payment method.'
                    ).format(money_filter(remainder, self.event.currency)),
                )
            else:
                messages.success(request, _('Your gift card has been applied.'))

            kwargs = {'step': 'payment'}
            if request.resolver_match and 'cart_namespace' in request.resolver_match.kwargs:
                kwargs['cart_namespace'] = request.resolver_match.kwargs['cart_namespace']
            return eventreverse(self.event, 'presale:event.checkout', kwargs=kwargs)
        except GiftCard.DoesNotExist:
            if self.event.vouchers.filter(code__iexact=request.POST.get('giftcard')).exists():
                messages.warning(
                    request,
                    _(
                        'You entered a voucher instead of a gift card. Vouchers can only be entered on the first page '
                        'of the shop below the product selection.'
                    ),
                )
            else:
                messages.error(request, _('This gift card is not known.'))
        except GiftCard.MultipleObjectsReturned:
            messages.error(
                request,
                _(
                    'This gift card can not be redeemed since its code is not unique. '
                    'Please contact the organizer of this event.'
                ),
            )

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Extract code out into method ([`extract-method`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/extract-method/))
- Replace unneeded comprehension with generator ([`comprehension-to-generator`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/comprehension-to-generator/))
</issue_to_address>

### Comment 3
<location> `app/eventyay/base/payment.py:1340` </location>
<code_context>
    def payment_prepare(self, request: HttpRequest, payment: OrderPayment) -> Union[bool, str, None]:
        for p in payment.order.positions.all():
            if p.item.issue_giftcard:
                messages.error(
                    request,
                    _('You cannot pay with gift cards when buying a gift card.'),
                )
                return

        try:
            gc = self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard')).first()
            if not gc:
                raise GiftCard.DoesNotExist()
            if gc.currency != self.event.currency:
                messages.error(request, _('This gift card does not support this currency.'))
                return
            if gc.testmode and not payment.order.testmode:
                messages.error(request, _('This gift card can only be used in test mode.'))
                return
            if not gc.testmode and payment.order.testmode:
                messages.error(request, _('Only test gift cards can be used in test mode.'))
                return
            if gc.expires and gc.expires < now():
                messages.error(request, _('This gift card is no longer valid.'))
                return
            if gc.value <= Decimal('0.00'):
                messages.error(request, _('All credit on this gift card has been used.'))
                return
            payment.info_data = {'gift_card': gc.pk, 'retry': True}
            payment.amount = min(payment.amount, gc.value)
            payment.save()

            return True
        except GiftCard.DoesNotExist:
            if self.event.vouchers.filter(code__iexact=request.POST.get('giftcard')).exists():
                messages.warning(
                    request,
                    _(
                        'You entered a voucher instead of a gift card. Vouchers can only be entered on the first page '
                        'of the shop below the product selection.'
                    ),
                )
            else:
                messages.error(request, _('This gift card is not known.'))
        except GiftCard.MultipleObjectsReturned:
            messages.error(
                request,
                _(
                    'This gift card can not be redeemed since its code is not unique. '
                    'Please contact the organizer of this event.'
                ),
            )

</code_context>

<issue_to_address>
**issue (code-quality):** Extract code out into method ([`extract-method`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/extract-method/))
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Instead of raising GiftCard.DoesNotExist and catching it in except block,
handle the not-found case inline with immediate user feedback. This:
- Provides more direct error handling flow
- Reduces exception overhead
- Improves code readability
- Maintains the voucher vs gift card check logic

Changes:
- Handle gc=None case inline with messages.error/warning
- Remove now-redundant GiftCard.DoesNotExist except blocks
- Keep GiftCard.MultipleObjectsReturned handling
- Preserve voucher detection logic
@ArnavBallinCode
Copy link
Contributor Author

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • You have duplicated the gift card/voucher lookup logic in both checkout_prepare and payment_prepare—consider extracting this into a shared helper method to DRY up the code.
  • Normalize the incoming giftcard input (e.g., strip whitespace or default to an empty string) before filtering to avoid unexpected None or whitespace-only lookups.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- You have duplicated the gift card/voucher lookup logic in both `checkout_prepare` and `payment_prepare`—consider extracting this into a shared helper method to DRY up the code.
- Normalize the incoming `giftcard` input (e.g., strip whitespace or default to an empty string) before filtering to avoid unexpected None or whitespace-only lookups.

## Individual Comments

### Comment 1
<location> `app/eventyay/base/payment.py:1257` </location>
<code_context>
    def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> Union[bool, str, None]:
        for p in get_cart(request):
            if p.item.issue_giftcard:
                messages.error(
                    request,
                    _('You cannot pay with gift cards when buying a gift card.'),
                )
                return

        cs = cart_session(request)
        try:
            gc = self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard')).first()
            if not gc:
                # Check if user entered a voucher code instead
                if self.event.vouchers.filter(code__iexact=request.POST.get('giftcard')).exists():
                    messages.warning(
                        request,
                        _(
                            'You entered a voucher instead of a gift card. Vouchers can only be entered on the first page '
                            'of the shop below the product selection.'
                        ),
                    )
                else:
                    messages.error(request, _('This gift card is not known.'))
                return
            if gc.currency != self.event.currency:
                messages.error(request, _('This gift card does not support this currency.'))
                return
            if gc.testmode and not self.event.testmode:
                messages.error(request, _('This gift card can only be used in test mode.'))
                return
            if not gc.testmode and self.event.testmode:
                messages.error(request, _('Only test gift cards can be used in test mode.'))
                return
            if gc.expires and gc.expires < now():
                messages.error(request, _('This gift card is no longer valid.'))
                return
            if gc.value <= Decimal('0.00'):
                messages.error(request, _('All credit on this gift card has been used.'))
                return
            if 'gift_cards' not in cs:
                cs['gift_cards'] = []
            elif gc.pk in cs['gift_cards']:
                messages.error(request, _('This gift card is already used for your payment.'))
                return
            cs['gift_cards'] = cs['gift_cards'] + [gc.pk]

            total = sum(p.total for p in cart['positions'])
            # Recompute fees. Some plugins, e.g. pretix-servicefees, change their fee schedule if a gift card is
            # applied.
            fees = get_fees(
                self.event,
                request,
                total,
                cart['invoice_address'],
                cs.get('payment'),
                cart['raw'],
            )
            total += sum([f.value for f in fees])
            remainder = total
            if remainder > Decimal('0.00'):
                del cs['payment']
                messages.success(
                    request,
                    _(
                        'Your gift card has been applied, but {} still need to be paid. Please select a payment method.'
                    ).format(money_filter(remainder, self.event.currency)),
                )
            else:
                messages.success(request, _('Your gift card has been applied.'))

            kwargs = {'step': 'payment'}
            if request.resolver_match and 'cart_namespace' in request.resolver_match.kwargs:
                kwargs['cart_namespace'] = request.resolver_match.kwargs['cart_namespace']
            return eventreverse(self.event, 'presale:event.checkout', kwargs=kwargs)
        except GiftCard.MultipleObjectsReturned:
            messages.error(
                request,
                _(
                    'This gift card can not be redeemed since its code is not unique. '
                    'Please contact the organizer of this event.'
                ),
            )

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Extract code out into method ([`extract-method`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/extract-method/))
- Replace unneeded comprehension with generator ([`comprehension-to-generator`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/comprehension-to-generator/))
</issue_to_address>

### Comment 2
<location> `app/eventyay/base/payment.py:1340` </location>
<code_context>
    def payment_prepare(self, request: HttpRequest, payment: OrderPayment) -> Union[bool, str, None]:
        for p in payment.order.positions.all():
            if p.item.issue_giftcard:
                messages.error(
                    request,
                    _('You cannot pay with gift cards when buying a gift card.'),
                )
                return

        try:
            gc = self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard')).first()
            if not gc:
                # Check if user entered a voucher code instead
                if self.event.vouchers.filter(code__iexact=request.POST.get('giftcard')).exists():
                    messages.warning(
                        request,
                        _(
                            'You entered a voucher instead of a gift card. Vouchers can only be entered on the first page '
                            'of the shop below the product selection.'
                        ),
                    )
                else:
                    messages.error(request, _('This gift card is not known.'))
                return
            if gc.currency != self.event.currency:
                messages.error(request, _('This gift card does not support this currency.'))
                return
            if gc.testmode and not payment.order.testmode:
                messages.error(request, _('This gift card can only be used in test mode.'))
                return
            if not gc.testmode and payment.order.testmode:
                messages.error(request, _('Only test gift cards can be used in test mode.'))
                return
            if gc.expires and gc.expires < now():
                messages.error(request, _('This gift card is no longer valid.'))
                return
            if gc.value <= Decimal('0.00'):
                messages.error(request, _('All credit on this gift card has been used.'))
                return
            payment.info_data = {'gift_card': gc.pk, 'retry': True}
            payment.amount = min(payment.amount, gc.value)
            payment.save()

            return True
        except GiftCard.MultipleObjectsReturned:
            messages.error(
                request,
                _(
                    'This gift card can not be redeemed since its code is not unique. '
                    'Please contact the organizer of this event.'
                ),
            )

</code_context>

<issue_to_address>
**issue (code-quality):** Extract code out into method ([`extract-method`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/extract-method/))
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copilot finished reviewing on behalf of mariobehling November 14, 2025 17:49
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a 500 error that occurred when looking up gift cards during checkout and payment processes by replacing the direct .get() call with a more robust .filter().first() approach and implementing case-insensitive matching.

Key changes:

  • Replaced .get(secret=...) with .filter(secret__iexact=...).first() for case-insensitive gift card lookup
  • Moved error handling from exception-based (except GiftCard.DoesNotExist) to explicit null checking (if not gc)
  • Added helpful user messages distinguishing between invalid gift cards and vouchers entered incorrectly
Comments suppressed due to low confidence (2)

app/eventyay/base/payment.py:1328

  • The GiftCard.MultipleObjectsReturned exception will never be raised now that the code uses .filter().first() instead of .get(). The .first() method always returns at most one object and doesn't raise an exception for multiple results - it just returns the first one.

Consider either:

  1. Removing this exception handler entirely since it's now unreachable code, or
  2. If duplicate gift card secrets are a concern, add explicit validation before using the gift card:
gc = self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard')).first()
if not gc:
    # ... existing error handling
    return
# Check for duplicates
if self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard')).count() > 1:
    messages.error(
        request,
        _(
            'This gift card can not be redeemed since its code is not unique. '
            'Please contact the organizer of this event.'
        ),
    )
    return
        except GiftCard.MultipleObjectsReturned:
            messages.error(
                request,
                _(
                    'This gift card can not be redeemed since its code is not unique. '
                    'Please contact the organizer of this event.'
                ),
            )

app/eventyay/base/payment.py:1381

  • The GiftCard.MultipleObjectsReturned exception will never be raised now that the code uses .filter().first() instead of .get(). The .first() method always returns at most one object and doesn't raise an exception for multiple results - it just returns the first one.

Consider either:

  1. Removing this exception handler entirely since it's now unreachable code, or
  2. If duplicate gift card secrets are a concern, add explicit validation before using the gift card:
gc = self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard')).first()
if not gc:
    # ... existing error handling
    return
# Check for duplicates
if self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard')).count() > 1:
    messages.error(
        request,
        _(
            'This gift card can not be redeemed since its code is not unique. '
            'Please contact the organizer of this event.'
        ),
    )
    return
        except GiftCard.MultipleObjectsReturned:
            messages.error(
                request,
                _(
                    'This gift card can not be redeemed since its code is not unique. '
                    'Please contact the organizer of this event.'
                ),
            )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mariobehling mariobehling changed the title (Fix)gift card 500 error Fix: gift card 500 error Nov 20, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

app/eventyay/base/payment.py:1328

  • The GiftCard.MultipleObjectsReturned exception can never be raised by .filter().first() because .first() always returns at most one object. This exception handler is now unreachable code and should be removed or the logic should be updated to explicitly check for duplicates before calling .first().

If duplicate gift card secrets are a concern that needs to be handled, consider checking for this condition explicitly:

gc_queryset = self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard'))
if gc_queryset.count() > 1:
    messages.error(request, _('This gift card can not be redeemed since its code is not unique. Please contact the organizer of this event.'))
    return
gc = gc_queryset.first()
        except GiftCard.MultipleObjectsReturned:
            messages.error(
                request,
                _(
                    'This gift card can not be redeemed since its code is not unique. '
                    'Please contact the organizer of this event.'
                ),
            )

app/eventyay/base/payment.py:1381

  • The GiftCard.MultipleObjectsReturned exception can never be raised by .filter().first() because .first() always returns at most one object. This exception handler is now unreachable code and should be removed or the logic should be updated to explicitly check for duplicates before calling .first().

If duplicate gift card secrets are a concern that needs to be handled, consider checking for this condition explicitly:

gc_queryset = self.event.organizer.accepted_gift_cards.filter(secret__iexact=request.POST.get('giftcard'))
if gc_queryset.count() > 1:
    messages.error(request, _('This gift card can not be redeemed since its code is not unique. Please contact the organizer of this event.'))
    return
gc = gc_queryset.first()
        except GiftCard.MultipleObjectsReturned:
            messages.error(
                request,
                _(
                    'This gift card can not be redeemed since its code is not unique. '
                    'Please contact the organizer of this event.'
                ),
            )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Member

@hongquan hongquan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You must chose one of two ways of error handling: via returning None or via raising exception.

You are mixing them: You replaced GiftCard.DoesNotExist exception with .first() (returning None), but still leave GiftCard.MultipleObjectsReturned, making the code worse: The block of handling GiftCard.MultipleObjectsReturned can not be reach.

@hongquan
Copy link
Member

Do you have any log of this error 500? Because your analysis sounds wrong:

Django's .get() on an annotated queryset can sometimes fail if the secret comparison isn't exact or if there's a case mismatch

This should not cause error 500, because there is already a block to handle GiftCard.DoesNotExist at line 1308:

except GiftCard.DoesNotExist:

@ArnavBallinCode ArnavBallinCode force-pushed the fix/gift-card-500-error-clean branch from 5a3d461 to 636fb90 Compare November 20, 2025 13:59
@ArnavBallinCode
Copy link
Contributor Author

Do you have any log of this error 500? Because your analysis sounds wrong:

Django's .get() on an annotated queryset can sometimes fail if the secret comparison isn't exact or if there's a case mismatch

This should not cause error 500, because there is already a block to handle GiftCard.DoesNotExist at line 1308:

except GiftCard.DoesNotExist:

I did run into a 500 earlier while testing this flow and wrote that comment based on what I thought was happening at the time.

I reproduced right now and its working right now🤷

@ArnavBallinCode ArnavBallinCode force-pushed the fix/gift-card-500-error-clean branch from 636fb90 to 6a63cd0 Compare November 20, 2025 14:10
@ArnavBallinCode
Copy link
Contributor Author

Sorry for the messy commit history with multiple force pushes. I was iterating on 2 different branches to reproduce and messed some things up...

I tested the reproduction and confirmed it's working correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ticket Component: Gift Card Received from Refund Cannot Be Used

3 participants