-
Notifications
You must be signed in to change notification settings - Fork 135
Fix: gift card 500 error #1260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: enext
Are you sure you want to change the base?
Fix: gift card 500 error #1260
Conversation
- 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
Reviewer's GuideThis 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_preparesequenceDiagram
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
Sequence diagram for improved gift card lookup in payment_preparesequenceDiagram
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
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this 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
giftcardinput 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>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
|
@sourcery-ai review |
There was a problem hiding this 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_prepareandpayment_prepare—consider extracting this into a shared helper method to DRY up the code. - Normalize the incoming
giftcardinput (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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this 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.MultipleObjectsReturnedexception 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:
- Removing this exception handler entirely since it's now unreachable code, or
- 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.MultipleObjectsReturnedexception 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:
- Removing this exception handler entirely since it's now unreachable code, or
- 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.
There was a problem hiding this 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.MultipleObjectsReturnedexception 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.MultipleObjectsReturnedexception 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.
hongquan
left a comment
There was a problem hiding this 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.
|
Do you have any log of this error 500? Because your analysis sounds wrong:
This should not cause error 500, because there is already a block to handle eventyay/app/eventyay/base/payment.py Line 1308 in 4c92d7e
|
5a3d461 to
636fb90
Compare
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🤷 |
636fb90 to
6a63cd0
Compare
|
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. |
(Fix)gift card 500 error
The issue is in the
GiftCardPaymentclass in/app/eventyay/base/payment.pyat 2 places:1:
checkout_preparemethod (line ~1257)2:
payment_preparemethod (line ~1338)accepted_gift_cardsis a@propertythat returns an annotated queryset:When calling
.get()directly on this property, there might be issues with:.all()before.get()Django's
.get()on an annotated queryset can sometimes fail if the secret comparison isn't exact or if there's a case mismatchSolution
Use
.filter()with.first()instead of.get()for more robust handling, or useiexactfor case-insensitive matching similar to how vouchers are handled elsewhere in the codebase.Actually, looking at line 1309:
The code already uses
code__iexactfor vouchers, suggesting that case-insensitive matching is expected. The gift card lookup should use the same pattern!The Fix:
Change from:
To:
Summary by Sourcery
Fix gift card lookup in checkout_prepare and payment_prepare to prevent server errors and support case-insensitive matching
Bug Fixes:
Enhancements:
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:
Enhancements: