Regenerate Utah Invitational orders via proper helper + validated e2e webhook round-trip #484
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api#484
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Type
Feature
Lineage
Discovered during Utah Invitational blast prep (2026-04-13). 21 pending Orders were created via ad-hoc Python scripts that bypassed
create_player_checkout_session(), resulting in Stripe Checkout Sessions missing theorder_idmetadata field required by the webhook handler. Sessions have since expired. Zero phantom payments (all sessionsexpired/unpaid).Repo
forgejo_admin/basketball-apiUser Story
As an admin
I want tournament fee checkout links generated through the committed API path with validated webhook round-trip
So that parent payments reliably flip Orders to paid and we never ship broken links again
Context
The webhook handler at
src/basketball_api/routes/webhooks.py:197matches incomingcheckout.session.completedevents to Orders via theorder_idfield in session metadata. The propercreate_player_checkout_session()helper atsrc/basketball_api/routes/checkout.py:529-533sets this field correctly. Ad-hoc session creation (directstripe.checkout.Session.create()calls in admin scripts) omitsorder_idand breaks the reconciliation loop — parents can pay and our Order.status stayspendingforever with no indication of the payment.Current state of 21 tournament orders (product_id 5/6/7):
Order.status = pendingin DBexpired/unpaidorder_idmetadataFile Targets
Files the agent should modify or create:
scripts/regenerate_tournament_orders.py— NEW. One-shot admin script that: (1) queries stale pending Orders for a given tournament_id, (2) deletes them, (3) callscreate_player_checkout_session()for each committed player through the proper helper, (4) reports new order IDs + checkout URLs. Idempotent.tests/test_tournament_webhook_roundtrip.py— NEW. Integration test that creates a test-mode Order via the helper, synthesizes acheckout.session.completedevent signed with the webhook secret, POSTs to/webhooks/stripe, asserts Order.status flips topaid. Must pass against TEST mode Stripe key.docs/tournament-billing-runbook.md— NEW. Operator runbook: the exact sequence to create a tournament, generate checkout links, validate e2e, and blast emails. Calls out the "always use the admin endpoint, never ad-hoc sessions" rule.Files the agent should NOT touch:
routes/webhooks.py— handler is correct; bug was in session creation, not in the handlerroutes/checkout.py—create_player_checkout_session()is correct; don't modifyAcceptance Criteria
order_idin metadata (verified via Stripe API after script run)checkout.session.completedevent → Order flips frompendingtopaidcreate_player_checkout_session()Test Expectations
test_tournament_webhook_roundtrip— creates Order via helper, POSTs signed test event, assertsOrder.status == paidandOrder.stripe_payment_intent_idpopulatedpytest tests/ -k "tournament_webhook or regenerate"Constraints
~/secrets/stripe/test-webhook-secret)Checklist
Related
project-westside-basketball— parent projectstory:WS-S33— user story (tournament billing)sop-email-send— updated 2026-04-13 with ONE APPROVAL = ONE SEND ruleScope Review: READY
Review note:
review-1013-2026-04-15Scope is solid. Template complete, traceability confirmed (WS-S33 story verified on project-westside-basketball), all file target references in webhooks.py and checkout.py verified against the live repo, 3 NEW files confirmed non-existent. Fits in a single agent pass (3 files, 5 AC, single repo). No decomposition needed.
Non-blocking nits for dev:
stripe.Webhook.construct_eventpattern used in tests/test_webhooks.py. Dev can pick either; flagging to preempt review churn.stripe.checkout.Session.createsites in src/ (admin.py, jersey.py, register.py x2, checkout.py x3) that are per-product helpers, not ad-hoc.arch-checkoutorarch-emailbacking notes exist in pal-e-docs. Not a blocker; worth standing up so future arch-labeled tickets have referents.Line 529-533 cite in the Context section points to the metadata block inside
create_player_checkout_session()(def is at line 467). Functional claim is correct; noting the offset for clarity.Cleared to advance to
next_up.