Implement discount (promo) codes#798
Conversation
julianweng
commented
Mar 26, 2025
- Implement auto-generation of discount code
- Fix implementation of group discounts
- Misc mobile responsivity improvements



…ode discounts to summary
aviupadhyayula
left a comment
There was a problem hiding this comment.
Took a look at the backend and left a couple comments. At a high level: I'm not a huge fan of moving pricing-related stuff (group discounts, promo codes) under the Ticket class. It feels crowded and sort of against the principles of OOP.
The way I was envisioning we'd implement this is as a separate model (e.g. TicketPromoCode), with a link back to the ticket type. When a user checks out (or when we call the cart route), they apply as many promo codes as they want through the frontend. We check if the promo codes are valid, and then use them in the cart total calculation.
This feels cleaner to me, and also unlocks more capability for event owners. With the current approach, event owners can't create multiple promo codes per ticket.
I think the work Rohan was doing with the Ticket and TicketClass models is kind of what we want. I don't know what the best course of action here is: should we wait until the checkout flow rewrite's done, and then migrate this work over to that branch?
Let me know what you think. (One last nit: we'll be merging this PR into ticketing-v2, so we'll need to change the target branch in the PR.)
| blank=True, | ||
| ) | ||
| group_size = models.PositiveIntegerField(null=True, blank=True) | ||
| code_discount = models.DecimalField( |
There was a problem hiding this comment.
Nit: should this be discount_code?
There was a problem hiding this comment.
Also, I think we should delineate between promo codes and group discounts throughout the codebase. Let's refer to these as promo codes instead.
| if discount_code: | ||
| valid_discount_code = tickets.first().discount_code | ||
| if discount_code != valid_discount_code: | ||
| return Response( | ||
| { | ||
| "detail": f"Invalid discount code for {type}", | ||
| "success": False, | ||
| }, | ||
| status=status.HTTP_400_BAD_REQUEST, | ||
| ) | ||
| tickets.update(discount_code_applied=True) |
There was a problem hiding this comment.
Will the promo code be applied when the user adds tickets to their cart? I was under the impression that we'd let users add promo codes before checkout (e.g. on the cart page).
| tickets = Ticket.objects.filter(event=event) | ||
| # if for purchasing purposes, non-buyable tickets are unavailable | ||
| # otherwise, all non-owned/held tickets are available | ||
| for_purchasing = self.request.query_params.get("for_purchasing", "true") |
There was a problem hiding this comment.
Nit: let's do validation here (has to be true or false) and then convert to a boolean.
| for ticket_type in tickets.values_list("type", flat=True).distinct(): | ||
| ticket = tickets.filter(type=ticket_type).first() |
There was a problem hiding this comment.
Can't we just get a ticket of each type in a single query? This looks expensive.
| available_list = [] | ||
| for ticket_type in tickets.values_list("type", flat=True).distinct(): | ||
| ticket = tickets.filter(type=ticket_type).first() | ||
| if ticket: |
There was a problem hiding this comment.
Nit: use guard clause pattern instead.
|
|
||
| # Group discounts must be between 0 and 1 | ||
| if item.get("group_discount", 0) < 0 or item.get("group_discount", 0) > 1: | ||
| if item.get("group_discount", 0) is not None and ( |
There was a problem hiding this comment.
Shouldn't this be if item.get("group_discount", 0" is None or ? We want to check that if the group_discount is specified, it's non-null.
| ) | ||
|
|
||
| # Code discounts must be between 0 and 1 | ||
| if item.get("code_discount", 0) is not None and ( |