Tournament product creation + per-player Stripe checkout links #457

Closed
opened 2026-04-12 19:22:25 +00:00 by forgejo_admin · 3 comments

Type

Feature

Lineage

Standalone — discovered during Utah Invitational tournament fee billing (2026-04-12). Related to generic blast system (sibling ticket #456). Product model supports category='tournament' but no admin endpoints or per-player checkout orchestration exists.

Repo

forgejo_admin/basketball-api

User Story

As an admin
I want to bill players for tournament entry fees at team-specific rates via Stripe
So that event costs are collected before registration deadlines

Context

ProductCategory.tournament exists (models.py L126) and ProductType.one_time exists (L117-120), but there's no admin endpoint to create tournament products with team-specific pricing. Current checkout flow (routes/checkout.py L103 POST /create-session) is self-service — parent clicks a generic product link. Tournament billing needs admin-driven: create products per team/price tier, generate unique Stripe checkout URLs per player, embed those URLs in blast emails via {{checkout_url}} placeholder. First consumer: Utah Invitational (17U Elite $55, 17U Select $65, 16U Elite $55).

File Targets

Files the agent should modify or create:

  • src/basketball_api/models.py — Add Tournament model (id, name, event_date, description, created_at) and TournamentProduct join table (tournament_id, product_id, team_id).
  • alembic/versions/NNN_add_tournament_tables.py — NEW. Determine the next available migration number from remote main HEAD (do NOT hardcode — check alembic/versions/ for the latest slot).
  • src/basketball_api/routes/admin.py — Add POST /admin/tournaments (create tournament with team-specific products in one call) and POST /admin/tournaments/{id}/checkout-links (generate per-player Stripe checkout URLs).
  • src/basketball_api/routes/checkout.py — Add create_player_checkout_session(player, product) helper extracted from existing create-session logic.

Files the agent should NOT touch:

  • services/email.py — email blast is separate ticket #456
  • services/email_queries.py — query registry is separate ticket #456
  • Existing POST /create-session endpoint — keep self-service flow untouched

Acceptance Criteria

  • POST /admin/tournaments creates tournament + N products (one per team/price) in one call
  • POST /admin/tournaments/{id}/checkout-links returns {player_id: checkout_url} map
  • Checkout URLs are valid Stripe sessions with correct amount per team
  • Order records created when parent completes payment via checkout URL
  • Checkout success/cancel URLs work correctly
  • Tournament products have correct category='tournament', type='one_time'
  • GET /admin/tournaments/{id} with invalid ID returns 404
  • POST /admin/tournaments/{id}/checkout-links for player not on any tournament team returns 400 with clear message
  • Duplicate checkout-link generation for same player+tournament returns existing session URL (idempotent) or 409

Test Expectations

  • Unit test: Tournament model creation with products and team linkage
  • Unit test: checkout-links endpoint returns valid URL structure per player
  • Unit test: 404 for nonexistent tournament, 400 for player without matching team product
  • Integration test: full flow — create tournament, generate links, mock Stripe session
  • Run command: pytest tests/ -k "tournament"

Constraints

  • ProductCategory.tournament already exists — no enum migration needed
  • Stripe account is the same one used for jerseys/contracts
  • Checkout sessions should include tournament name + team name in metadata for reconciliation
  • Per-player URLs must be unique (one session per player per product)
  • Migration number must be determined at dev time from remote main HEAD, not hardcoded

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • project-westside-basketball — parent project
  • story:WS-S33 — user story
  • Generic blast system (#456) — provides tournament_committed query and {{checkout_url}} placeholder
  • sop-email-send — blast gate for actual sends
### Type Feature ### Lineage Standalone — discovered during Utah Invitational tournament fee billing (2026-04-12). Related to generic blast system (sibling ticket #456). Product model supports `category='tournament'` but no admin endpoints or per-player checkout orchestration exists. ### Repo `forgejo_admin/basketball-api` ### User Story As an admin I want to bill players for tournament entry fees at team-specific rates via Stripe So that event costs are collected before registration deadlines ### Context `ProductCategory.tournament` exists (models.py L126) and `ProductType.one_time` exists (L117-120), but there's no admin endpoint to create tournament products with team-specific pricing. Current checkout flow (`routes/checkout.py` L103 `POST /create-session`) is self-service — parent clicks a generic product link. Tournament billing needs admin-driven: create products per team/price tier, generate unique Stripe checkout URLs per player, embed those URLs in blast emails via `{{checkout_url}}` placeholder. First consumer: Utah Invitational (17U Elite $55, 17U Select $65, 16U Elite $55). ### File Targets Files the agent should modify or create: - `src/basketball_api/models.py` — Add `Tournament` model (id, name, event_date, description, created_at) and `TournamentProduct` join table (tournament_id, product_id, team_id). - `alembic/versions/NNN_add_tournament_tables.py` — NEW. Determine the next available migration number from remote main HEAD (do NOT hardcode — check `alembic/versions/` for the latest slot). - `src/basketball_api/routes/admin.py` — Add `POST /admin/tournaments` (create tournament with team-specific products in one call) and `POST /admin/tournaments/{id}/checkout-links` (generate per-player Stripe checkout URLs). - `src/basketball_api/routes/checkout.py` — Add `create_player_checkout_session(player, product)` helper extracted from existing create-session logic. Files the agent should NOT touch: - `services/email.py` — email blast is separate ticket #456 - `services/email_queries.py` — query registry is separate ticket #456 - Existing `POST /create-session` endpoint — keep self-service flow untouched ### Acceptance Criteria - [ ] `POST /admin/tournaments` creates tournament + N products (one per team/price) in one call - [ ] `POST /admin/tournaments/{id}/checkout-links` returns `{player_id: checkout_url}` map - [ ] Checkout URLs are valid Stripe sessions with correct amount per team - [ ] Order records created when parent completes payment via checkout URL - [ ] Checkout success/cancel URLs work correctly - [ ] Tournament products have correct `category='tournament'`, `type='one_time'` - [ ] `GET /admin/tournaments/{id}` with invalid ID returns 404 - [ ] `POST /admin/tournaments/{id}/checkout-links` for player not on any tournament team returns 400 with clear message - [ ] Duplicate checkout-link generation for same player+tournament returns existing session URL (idempotent) or 409 ### Test Expectations - [ ] Unit test: Tournament model creation with products and team linkage - [ ] Unit test: checkout-links endpoint returns valid URL structure per player - [ ] Unit test: 404 for nonexistent tournament, 400 for player without matching team product - [ ] Integration test: full flow — create tournament, generate links, mock Stripe session - Run command: `pytest tests/ -k "tournament"` ### Constraints - `ProductCategory.tournament` already exists — no enum migration needed - Stripe account is the same one used for jerseys/contracts - Checkout sessions should include tournament name + team name in metadata for reconciliation - Per-player URLs must be unique (one session per player per product) - Migration number must be determined at dev time from remote main HEAD, not hardcoded ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `project-westside-basketball` — parent project - `story:WS-S33` — user story - Generic blast system (#456) — provides `tournament_committed` query and `{{checkout_url}}` placeholder - `sop-email-send` — blast gate for actual sends
Author
Owner

Scope Review: NEEDS_REFINEMENT

Review note: review-994-2026-04-12

Ticket is well-structured with all template sections present, verified user story (WS-S33), and clear file targets. Three issues need fixing before this moves to next_up:

  • [BODY] Migration number 032 is wrong. Local repo has up to 030, but merged PRs reference 040-044 on remote main. Replace with instruction to use next available migration slot from remote main HEAD.
  • [LABEL] arch:email is a mislabel. This ticket is about Stripe checkout + tournament product models, not email. The email blast is explicitly the sibling ticket (#456). Change to arch:basketball-api or arch:checkout on board item #994.
  • [BODY] Add error-path acceptance criteria: invalid tournament ID (404), player not on team's product (400), duplicate link generation behavior (idempotent or error).
## Scope Review: NEEDS_REFINEMENT Review note: `review-994-2026-04-12` Ticket is well-structured with all template sections present, verified user story (WS-S33), and clear file targets. Three issues need fixing before this moves to next_up: - **[BODY]** Migration number `032` is wrong. Local repo has up to 030, but merged PRs reference 040-044 on remote main. Replace with instruction to use next available migration slot from remote main HEAD. - **[LABEL]** `arch:email` is a mislabel. This ticket is about Stripe checkout + tournament product models, not email. The email blast is explicitly the sibling ticket (#456). Change to `arch:basketball-api` or `arch:checkout` on board item #994. - **[BODY]** Add error-path acceptance criteria: invalid tournament ID (404), player not on team's product (400), duplicate link generation behavior (idempotent or error).
Author
Owner

Scope refinement from review-994-2026-04-12 (NEEDS_REFINEMENT):

Three fixes applied to issue body:

  1. Migration number — Changed from hardcoded 032 to NNN with instruction to determine next available slot from remote main HEAD. Remote main has migrations up to 044; hardcoding would cause collision.

  2. Arch label — Changed board item from arch:email to arch:checkout. This ticket creates Tournament models and Stripe checkout orchestration, not email infrastructure (that's #456).

  3. Error-path acceptance criteria — Added three edge cases: 404 for invalid tournament ID, 400 for player without matching team product, and idempotent/409 behavior for duplicate checkout-link generation.

**Scope refinement from review-994-2026-04-12 (NEEDS_REFINEMENT):** Three fixes applied to issue body: 1. **Migration number** — Changed from hardcoded `032` to `NNN` with instruction to determine next available slot from remote main HEAD. Remote main has migrations up to 044; hardcoding would cause collision. 2. **Arch label** — Changed board item from `arch:email` to `arch:checkout`. This ticket creates Tournament models and Stripe checkout orchestration, not email infrastructure (that's #456). 3. **Error-path acceptance criteria** — Added three edge cases: 404 for invalid tournament ID, 400 for player without matching team product, and idempotent/409 behavior for duplicate checkout-link generation.
Author
Owner

Scope Review: NEEDS_REFINEMENT (re-review v2)

Review note: review-994-2026-04-12-v2

Re-review of board item #994. Previous review flagged 3 issues — only 1 was fixed.

Previous Findings Status

  • [LABEL] arch:email -> arch:checkout — FIXED
  • [BODY] Migration number 032 — NOT FIXED. Issue still hardcodes 032. Remote main has migrations up to 044; next available slot is at least 045. Replace with instruction to use next available slot from remote main HEAD.
  • [BODY] Missing error-path AC — NOT FIXED. Still 6 happy-path criteria only. Add: invalid tournament ID -> 404, player not on team -> 400, duplicate link generation -> idempotent/existing session.

Additional Finding

  • [SCOPE] No arch-checkout note exists in pal-e-docs. Create architecture note for the checkout/commerce component (non-blocking).

Two [BODY] fixes remain before this ticket is READY.

## Scope Review: NEEDS_REFINEMENT (re-review v2) Review note: `review-994-2026-04-12-v2` Re-review of board item #994. Previous review flagged 3 issues — only 1 was fixed. ### Previous Findings Status - [x] **[LABEL] arch:email -> arch:checkout** — FIXED - [ ] **[BODY] Migration number 032** — NOT FIXED. Issue still hardcodes `032`. Remote main has migrations up to 044; next available slot is at least 045. Replace with instruction to use next available slot from remote main HEAD. - [ ] **[BODY] Missing error-path AC** — NOT FIXED. Still 6 happy-path criteria only. Add: invalid tournament ID -> 404, player not on team -> 400, duplicate link generation -> idempotent/existing session. ### Additional Finding - [SCOPE] No `arch-checkout` note exists in pal-e-docs. Create architecture note for the checkout/commerce component (non-blocking). Two `[BODY]` fixes remain before this ticket is READY.
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#457
No description provided.