feat(events): promo codes — organizer editor and server-validated checkout discount #153
No reviewers
Labels
No labels
app:activities
app:chat
app:chatelet
app:events
app:forum
app:libra
app:market
app:restaurant
app:tasks
app:wallet
app:webapp
bug
enhancement
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
aiolabs/webapp!153
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/events-promo-codes"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Webapp half of promo codes (backend: aiolabs/events#45,
v1.6.1-aio.12). Either side can ship first: the extension keeps stored codes when an update omits them, and this UI degrades against an older extension (validate route missing → "could not verify, applied at checkout", code still sent).Organizer
PromoCodesEditor.vueinside the create/edit dialog as a second collapsible ("Promo codes"): rows of code (uppercased as you type), discount % (1–100), max uses (blank = unlimited), active switch, remove; per-row validation (required, unique, ranges) via the purelib/promoCodes.ts;Used ncaption from the server-derivedused_count.extra.promo_codesis always sent explicitly (empty editor = clear).useMyEventsnow lets the organizer's own authenticated record win over the public one on merge — required because public records no longer carry codes.applied_promo_codefrom stats).Buyer
usePromoCode→POST /events/api/v1/promo/validate/{event_id}({codes, quantity}); states idle / checking / applied / invalid / unverified; debounced re-validation on quantity change; stale responses ignored. Applied: struck-through original + server total +CODE −N%badge; the discounted total feeds the header, price row, fiat conversion preview and invoice amount.paid: truegoes straight to the success state. Zero total after a code routes through the free path; a guest whose total becomes zero gets an inline login prompt (events#29 posture) instead of the whole form swapping out. Promo-related 400s from purchase trigger a re-validate.TicketApiService.request()now attachesstatusto thrown errors (additive).i18n / tests
events.create.promo.*(newcreatenamespace, only promo — the rest of the dialog stays hardcoded English as before) andevents.purchase.promo.*in en/es/fr.vue-tsc -bclean; vitest 46/46 (20 new: helpers, validate body/URL,promo_codeon requestTicket,extra.promo_codeson createEvent, 404 → status).Manual verification after both deploy
Editor round-trip incl. max uses; Lightning +
HALF→ 500 sat invoice; Card +HALF→ halved Stripe session withmetadata.promo_code; thirdHALF→ invalid on Apply / "fully redeemed" on forced purchase; guest +FREE100→ inline login prompt; logged-in +FREE100on Card → immediate success; scanner badge.🤖 Generated with Claude Code
https://claude.ai/code/session_01ByAwHU4pRnyE58YocQvAas
Types follow upstream v2 names (PromoCode.max_uses / used_count, PromoValidateRequest, BasketDiscount, BasketTotals) as served by the events extension >= v1.6.1-aio.12; EventExtra.promo_codes becomes optional because public event responses no longer carry it. TicketApiService.validatePromoCode posts the upper-cased code + quantity to POST /events/api/v1/promo/validate/{event_id}; request() now attaches the HTTP status to thrown errors so a 404 from an older extension can be told apart from a 400. lib/promoCodes.ts holds the pure helpers shared by the organizer editor and the buyer dialog (normalise, editor validation returning i18n keys, wire <-> row mapping, display-only percent rounding). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ByAwHU4pRnyE58YocQvAas