Review: Jersey checkout "load failed" error on payment click
Verdict: READY
Template Completeness
- [x] Type — Bug
- [x] Lineage — Standalone, discovered via user email report (Daniel Niyitanga, 2026-04-04)
- [x] Repo — basketball-api (backend) + westside-app (frontend) identified
- [x] What Broke — Clear symptom: "load failed" on POST /jersey/checkout, GET endpoints work
- [x] Repro Steps — Specific token URL, step-by-step with observable outcome
- [x] Expected Behavior — POST returns Stripe URL, redirect to Stripe hosted payment
- [x] Environment — Cluster/namespace, service, frontend URL documented
- [x] Acceptance Criteria — 4 criteria, all testable
- [x] Related — Project, key files with line numbers, investigation priority listed
Traceability
- [x] story:WS-S18 label — "As a parent, I want to receive a branded email with jersey ordering link so that I can order without bringing cash"
- [x] story note verified — found in project-westside-basketball user-stories section under Parent stories
- [x] arch:basketball-api label — core API component
- [ ] arch note MISSING — [SCOPE] Create architecture note arch-basketball-api for component basketball-api. Non-blocking: this is a platform-wide gap, not specific to this ticket.
- [x] Forgejo issue — forgejo_admin/basketball-api#340, open
File Targets
- [x]
basketball-api/src/basketball_api/routes/jersey.py:172-338— verified: POST /checkout endpoint exists at lines 172-338, handles token validation, jersey option/size/number validation, Stripe session creation, and response - [x]
basketball-api/src/basketball_api/main.py(CORS) — verified: CORSMiddleware configured at lines 53-64 with allow_origins including westsidekingsandqueens.tail5b443a.ts.net, allow_methods=["*"], allow_headers=["*"] - [x]
westside-app/src/routes/(app)/jersey/+page.svelte:248-289— verified: fetch POST to /jersey/checkout at line 269 with JSON body, error displayed at line 286 via err.message
Repo Placement
Issue body correctly identifies both basketball-api (backend CORS/Stripe) and westside-app (frontend fetch). Forgejo issue is filed on basketball-api which is the likely root cause repo (CORS preflight or Stripe key). The investigation priority order (1. CORS preflight on POST, 2. Stripe API key in deployed env, 3. stale deployment) is sound and all three are backend concerns. If the fix turns out to be frontend-only, a separate westside-app issue would be needed, but that is unlikely given GET works fine.
Dependencies
- [x] #718 [qa] Remove opt-out from jersey ordering options — opt_out code still present in jersey.py. If this merges first, checkout endpoint behavior changes. Not a blocker (opt_out is not the reported failure path).
- [x] #719 [qa] Stripe webhook not syncing payment to order status — related Stripe plumbing but independent concern (webhook vs checkout session creation).
- [x] #694 [qa] Add player_id param to jersey/checkout for multi-player parents — adds new auth path to same endpoint. Not a blocker.
- [x] #696 [qa] Update jersey/checkout pages to support session auth — frontend dual-auth changes. Reported bug uses token auth (email link), so independent.
- [x] #733 [in_progress] Fix 9 failing jersey/checkout tests — CI test fixes. May affect CI gate for the fix PR but not a blocker for investigation.
No hard blockers. Dependencies documented here; acceptable for a bug investigation ticket to not enumerate these in the issue body.
Acceptance Criteria
All 4 criteria are agent-verifiable:
- "Root cause identified" — agent documents findings in PR
- "POST /jersey/checkout returns valid Stripe URL" — testable via curl with token
- "Daniel can complete jersey purchase" — verifiable via the specific token URL in repro steps
- "Verified on Safari/iOS" — requires manual check post-fix, but agent can verify the underlying API behavior
No missing criteria. All are specific and testable.
Blast Radius
- If root cause is CORS: the CORSMiddleware in main.py is global — any fix applies to all routes. However, since GET requests work and only POST fails, the issue is likely CORS preflight (OPTIONS) specific. The allow_methods=["*"] config should cover POST, so the issue may be more subtle (e.g., missing preflight response from Tailscale funnel/ingress layer, not FastAPI itself).
- If root cause is Stripe key: only payment endpoints affected (jersey/checkout, checkout/create-session). No blast to other services.
- If root cause is stale deployment: affects all basketball-api endpoints, but GET working narrows this down.
- No similar "load failed" pattern found in sibling services. Rollback is straightforward (revert single PR).
Decomposition Assessment
Apply the three-thing limit and five-minute rule:
- 2 file targets across 2 repos, but investigation is backend-focused — likely 1 repo touched
- 4 acceptance criteria — within limits
- Estimated agent work: <5 minutes (investigate CORS/Stripe config, apply targeted fix)
- No independent subtasks to parallelize — this is a single investigation with one fix
No decomposition needed.
Recommendation
No action needed. Ticket is well-scoped for a single agent pass.
Non-blocking discovered scope:
- [SCOPE] Create architecture note arch-basketball-api (platform-wide gap, not specific to this ticket)