Replies: 3 comments
|
so the frontend will be needing these endpoints from the backend yall POST /events/{event_id}/coupons/ - Create coupon for event |
|
Tasks for Frontend guys Coupon Feature — Task DistributionSamkiel: Architecture, organizer-side coupon setup (event create/edit), API contract definition API Contract the frontend needs from the BackendSAMKIELOrganizer Coupon Setup in Event Create/Edit TOBICheckout Coupon Input & Discount Application ABRAHAMCoupon UI Components — Badges & Display States ARIGATO |
Frontend guide: ticket couponsThis document describes how the ticket coupon feature behaves on the API so web/mobile clients can integrate organizers creating codes and attendees applying them at checkout. Base URL prefix (same as the rest of the app):
Authentication uses Concepts
Important: Coupon checkout is implemented on student self-booking ( Organizer: create a couponEndpoint: Request body (JSON)
Response —
|
| Param | Required | Description |
|---|---|---|
event_id |
yes | Same event id string used elsewhere. |
Response — 200 OK
{
"coupons": [
{
"code": "TC-4821",
"event_id": "event:TE-12345",
"category_id": "category:ABC12-XYZ34",
"discount_type": "percent",
"discount_value": "15.00",
"is_active": true,
"starts_at": null,
"ends_at": null,
"max_redemptions": 100,
"redemptions_count": 12,
"created_at": "...",
"updated_at": "..."
}
],
"count": 1
}Use redemptions_count / max_redemptions for organizer dashboards. There is no deactivate/update endpoint in this feature slice; only listing + create are exposed.
Student: apply coupon at checkout
Endpoint: POST /tickets/book/
Auth: Student JWT (same as normal booking).
Extra body field
| Field | Type | Required | Description |
|---|---|---|---|
coupon_code |
string | no | Public code (case-insensitive on server). Omit or blank for normal booking. |
Whitespace-only values are treated as “no coupon.”
Existing booking fields (unchanged)
event_id(required)items:[{ "category_name": string, "quantity": number }]referral(optional)payment_method:"paystack"|"manual_bank_transfer"when the event allows multiple methods (same rules as before)
Success responses
Behavior matches existing booking, plus optional coupon metadata when a coupon was applied.
Coupon object (when present):
| Field | Type | Meaning |
|---|---|---|
code |
string | Applied coupon code. |
discount_amount |
number | Discount applied to the eligible portion of the cart (base currency, before platform/Paystack fees). |
subtotal_after_discount |
number | Total ticket base subtotal for this booking after discount (sum of stored ticket line amounts). |
Paid — Paystack (200 OK):
- Same as today:
payment_url,payment_reference,tickets,booking_id, etc. - Each ticket in
ticketsincludestotal_pricealready discounted per unit logic used server-side. - Optional top-level
couponobject as above.
Paid — manual bank transfer (201 Created):
total_amount: amount the user must transfer (includes platform piece per existing rules; no Paystack fee).- Optional
coupon.
Free / zero total base (201 Created):
- Tickets created confirmed immediately when
pricing_typeis free or discounted base is 0. - Server records coupon redemption in this path without Paystack.
Payment not configured (201 Created):
- Fallback payload includes
base_ticket_price,total_amount, etc.; discounted base is reflected there when coupon applied.
Errors — 400 Bad Request
Typical shapes:
{ "error": "Invalid coupon code for this event." }{ "error": "You have already used this coupon." }Other messages cover expiry, inactive coupon, wrong event/category mix in cart, global max redemptions reached, pending booking conflict, validation failures on items, payment method rules, etc.
Serializer validation errors use DRF’s usual { "field": ["…"] } shape where applicable.
How pricing works for the UI
- Authoritative totals after apply only exist after a successful
POST /tickets/book/(or you duplicate server rules client-side for estimates only). tickets[].total_pricein the booking response is the discounted per-ticket line used for payment aggregation (each row is quantity1in this API).category_priceon ticket serializers (if exposed in your client types) may still reflect the catalog category price from the related category; usetotal_pricefor “what this line costs in this booking.”- Fees: Final Paystack charge is computed server-side from the discounted ticket base plus platform/Paystack rules—do not assume fees are calculated on pre-discount list prices.
There is no dedicated “preview coupon” endpoint yet. Options:
- Call
POST /tickets/book/only when the user confirms (simplest). - Or add a future
POST /tickets/coupons/validate/(not implemented) if product needs live preview without reserving inventory.
UX checklist
- Organizer: After create, show
code, discount summary, optional category scope, and validity window. - Checkout: Input for optional promo code; on submit include
coupon_code; surface{ error }from API clearly. - Paid flow: Redirect/open
payment_urlas today; amount matches discounted totals. - Already used: Expect clear error; disable re-entry for that code for that logged-in user.
- Mixed cart + category-scoped coupon: Discount applies only to matching categories;
discount_amountdescribes savings on the eligible portion—avoid implying “X off entire cart” unless coupon is event-wide.
Quick reference
| Action | Method & path |
|---|---|
| Create coupon | POST /tickets/organizer/coupons/ |
| List coupons | GET /tickets/organizer/coupons/?event_id=… |
| Book with coupon | POST /tickets/book/ + coupon_code |
Related backend modules
- Models:
radar.ticket.models(TicketCoupon,CouponRedemption,Ticket.applied_coupon) - Logic:
radar.ticket.coupon_service - Views:
TicketBookingView,OrganizerTicketCouponListCreateView
Uh oh!
There was an error while loading. Please reload this page.
new feature alert
All reactions