Feature: Promo code for free registration + fix SPA submission #106

Closed
opened 2026-03-18 22:37:48 +00:00 by forgejo_admin · 0 comments

Type

Feature

Lineage

plan-wkq → Phase 11 (Girls Tryout — March 24)

Repo

forgejo_admin/basketball-api

User Story

As a coach or tester
I want to enter a promo code during registration
So that the tryout fee is waived and registration completes without Stripe

Context

Registration for the March 24 girls tryout needs to work end-to-end. Currently the SPA at westsidekingsandqueens.tail5b443a.ts.net/register has a beautiful 4-step form but its submit handler sends JSON while the API expects multipart FormData — registration never actually reaches the database. The basketball-api has a working server-rendered form at /register but it lacks a promo/coupon mechanism.

We need two things:

  1. A promo code that bypasses Stripe payment (for testing and comp'd spots)
  2. A JSON registration endpoint so the SPA can submit properly (the existing POST /register returns HTML)

Decision: promo codes stored as a simple env var (TRYOUT_PROMO_CODES=TESTFREE,COMP2026) checked server-side. No database table needed for v1.

File Targets

Files to modify:

  • src/basketball_api/routes/register.py — add POST /api/register JSON endpoint accepting SPA payload, add promo code validation
  • src/basketball_api/core/config.py — add TRYOUT_PROMO_CODES setting (comma-separated env var)
  • src/basketball_api/routes/upload.py — ensure photo upload works as a separate call (SPA uploads photo first, then sends URL)

Files NOT to touch:

  • src/basketball_api/routes/admin.py — no admin changes needed
  • Existing HTML POST /register — keep working as-is for direct access

Acceptance Criteria

  • POST /api/register accepts JSON with fields: player_name, division, graduating_class, parent_first, parent_last, parent_email, parent_phone, waiver_accepted, signature_name, payment_method, promo_code, photo_url
  • Valid promo code → Registration.payment_status = paid, Registration.signup_method = promo
  • Invalid promo code → 400 with {"error": "Invalid promo code"}
  • No promo code + payment_method = cashRegistration.payment_status = pending
  • No promo code + payment_method = card → return Stripe checkout URL
  • Auto-creates Keycloak account when payment is confirmed (promo = immediate)
  • Returns JSON {"success": true, "email": "...", "password": "..."}

Test Expectations

  • Unit test: valid promo code marks registration paid
  • Unit test: invalid promo code returns 400
  • Unit test: missing promo code falls through to normal payment flow
  • Integration test: full registration with promo code creates parent + player + registration records
  • Run command: pytest tests/ -k test_promo

Constraints

  • Match existing route patterns in routes/register.py
  • Promo codes are case-insensitive
  • Reuse existing Parent, Player, Registration model creation logic from submit_registration()
  • Photo upload stays as a separate POST /upload/photo call — SPA uploads first, passes photo_url in registration payload
  • No Stripe dependency when promo code is used

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • westside-basketball — project
  • forgejo_admin/westside-app #46 — SPA false-success bug (frontend counterpart)
  • plan-wkq Phase 11 — Girls Tryout March 24
### Type Feature ### Lineage `plan-wkq` → Phase 11 (Girls Tryout — March 24) ### Repo `forgejo_admin/basketball-api` ### User Story As a coach or tester I want to enter a promo code during registration So that the tryout fee is waived and registration completes without Stripe ### Context Registration for the March 24 girls tryout needs to work end-to-end. Currently the SPA at `westsidekingsandqueens.tail5b443a.ts.net/register` has a beautiful 4-step form but its submit handler sends JSON while the API expects multipart FormData — registration never actually reaches the database. The basketball-api has a working server-rendered form at `/register` but it lacks a promo/coupon mechanism. We need two things: 1. A promo code that bypasses Stripe payment (for testing and comp'd spots) 2. A JSON registration endpoint so the SPA can submit properly (the existing `POST /register` returns HTML) Decision: promo codes stored as a simple env var (`TRYOUT_PROMO_CODES=TESTFREE,COMP2026`) checked server-side. No database table needed for v1. ### File Targets Files to modify: - `src/basketball_api/routes/register.py` — add `POST /api/register` JSON endpoint accepting SPA payload, add promo code validation - `src/basketball_api/core/config.py` — add `TRYOUT_PROMO_CODES` setting (comma-separated env var) - `src/basketball_api/routes/upload.py` — ensure photo upload works as a separate call (SPA uploads photo first, then sends URL) Files NOT to touch: - `src/basketball_api/routes/admin.py` — no admin changes needed - Existing HTML `POST /register` — keep working as-is for direct access ### Acceptance Criteria - [ ] `POST /api/register` accepts JSON with fields: `player_name`, `division`, `graduating_class`, `parent_first`, `parent_last`, `parent_email`, `parent_phone`, `waiver_accepted`, `signature_name`, `payment_method`, `promo_code`, `photo_url` - [ ] Valid promo code → `Registration.payment_status = paid`, `Registration.signup_method = promo` - [ ] Invalid promo code → 400 with `{"error": "Invalid promo code"}` - [ ] No promo code + `payment_method = cash` → `Registration.payment_status = pending` - [ ] No promo code + `payment_method = card` → return Stripe checkout URL - [ ] Auto-creates Keycloak account when payment is confirmed (promo = immediate) - [ ] Returns JSON `{"success": true, "email": "...", "password": "..."}` ### Test Expectations - [ ] Unit test: valid promo code marks registration paid - [ ] Unit test: invalid promo code returns 400 - [ ] Unit test: missing promo code falls through to normal payment flow - [ ] Integration test: full registration with promo code creates parent + player + registration records - Run command: `pytest tests/ -k test_promo` ### Constraints - Match existing route patterns in `routes/register.py` - Promo codes are case-insensitive - Reuse existing `Parent`, `Player`, `Registration` model creation logic from `submit_registration()` - Photo upload stays as a separate `POST /upload/photo` call — SPA uploads first, passes `photo_url` in registration payload - No Stripe dependency when promo code is used ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `westside-basketball` — project - `forgejo_admin/westside-app #46` — SPA false-success bug (frontend counterpart) - `plan-wkq` Phase 11 — Girls Tryout March 24
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/basketball-api#106
No description provided.