Wire tournament checkout URLs into blast query for per-player payment links #463

Closed
opened 2026-04-12 23:30:42 +00:00 by forgejo_admin · 1 comment

Type

Feature

Lineage

Discovered during Utah Invitational blast prep (2026-04-12). #456 (blast system) and #457 (tournament checkout) were built as independent features. The blast query returns checkout_url: "" because it has no access to Stripe session data. This ticket is the integration glue.

Repo

forgejo_admin/basketball-api

User Story

As an admin
I want tournament fee emails to include each player's unique Stripe payment link
So that parents can pay directly from the email without any manual URL distribution

Context

The blast endpoint (POST /admin/email/blast) resolves {{checkout_url}} per recipient, but the tournament_committed query in email_queries.py returns checkout_url: "" with a comment "populated by body.data override." Problem: body.data is shared across ALL recipients — it can't provide per-player URLs. The Stripe checkout sessions were created (21 sessions for Utah Invitational) and Order records exist with status=pending, but the Stripe session URL is not stored on the Order. The query needs to join through Tournament → TournamentProduct → Product → Order to resolve checkout URLs per player.

File Targets

Files the agent should modify or create:

  • src/basketball_api/models.py — Add stripe_checkout_url column (VARCHAR 500, nullable) to Order model.
  • alembic/versions/NNN_add_stripe_checkout_url_to_orders.py — NEW. Determine next migration slot from remote main HEAD (currently 046). Add column.
  • src/basketball_api/services/email_queries.py — Update query_tournament_committed(): accept tournament_id in query_params, join Order table to find pending tournament orders per player, populate checkout_url from Order.stripe_checkout_url. Fall back to empty string if no order exists.
  • src/basketball_api/routes/admin.py — Update tournament checkout-links endpoint (POST /admin/tournaments/{id}/checkout-links): save session.url to Order.stripe_checkout_url when creating the Stripe session.
  • src/basketball_api/routes/checkout.py — Update create_player_checkout_session() helper: save session.url to the Order record.

Files the agent should NOT touch:

  • Blast endpoint logic in admin.py — the per-recipient placeholder resolution already works
  • build_action_email() — email builder is fine
  • brand.py — no changes needed

Acceptance Criteria

  • Order.stripe_checkout_url is populated when checkout links are generated via POST /admin/tournaments/{id}/checkout-links
  • tournament_committed query accepts tournament_id in query_params and returns per-player checkout_url from Order records
  • Full end-to-end: create tournament → generate checkout links → blast with test_email → email contains unique Stripe URL for the test player
  • Players without a pending order for the tournament get checkout_url: "" (graceful fallback, not error)
  • Existing Order records (from our Utah Invitational session creation) can be backfilled with URLs

Test Expectations

  • Unit test: Order model includes stripe_checkout_url after migration
  • Unit test: query_tournament_committed with tournament_id returns checkout_url from Order
  • Unit test: checkout-links endpoint saves URL to Order record
  • Integration test: blast with tournament_committed query resolves per-player checkout_url in email
  • Run command: pytest tests/ -k "tournament"

Constraints

  • Migration number must be determined from remote main HEAD, not hardcoded
  • Existing 21 pending Orders for Utah Invitational need backfill — the Stripe session URLs exist but weren't saved. Agent should include a backfill script or migration data step.
  • Follow existing Order model patterns in models.py

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • project-westside-basketball — parent project
  • story:WS-S33 — user story (tournament billing)
  • #456 — blast system (upstream)
  • #457 — tournament checkout (upstream)
  • sop-email-send — blast gate for actual sends
### Type Feature ### Lineage Discovered during Utah Invitational blast prep (2026-04-12). #456 (blast system) and #457 (tournament checkout) were built as independent features. The blast query returns `checkout_url: ""` because it has no access to Stripe session data. This ticket is the integration glue. ### Repo `forgejo_admin/basketball-api` ### User Story As an admin I want tournament fee emails to include each player's unique Stripe payment link So that parents can pay directly from the email without any manual URL distribution ### Context The blast endpoint (`POST /admin/email/blast`) resolves `{{checkout_url}}` per recipient, but the `tournament_committed` query in `email_queries.py` returns `checkout_url: ""` with a comment "populated by body.data override." Problem: `body.data` is shared across ALL recipients — it can't provide per-player URLs. The Stripe checkout sessions were created (21 sessions for Utah Invitational) and Order records exist with `status=pending`, but the Stripe session URL is not stored on the Order. The query needs to join through Tournament → TournamentProduct → Product → Order to resolve checkout URLs per player. ### File Targets Files the agent should modify or create: - `src/basketball_api/models.py` — Add `stripe_checkout_url` column (VARCHAR 500, nullable) to Order model. - `alembic/versions/NNN_add_stripe_checkout_url_to_orders.py` — NEW. Determine next migration slot from remote main HEAD (currently 046). Add column. - `src/basketball_api/services/email_queries.py` — Update `query_tournament_committed()`: accept `tournament_id` in `query_params`, join Order table to find pending tournament orders per player, populate `checkout_url` from `Order.stripe_checkout_url`. Fall back to empty string if no order exists. - `src/basketball_api/routes/admin.py` — Update tournament checkout-links endpoint (`POST /admin/tournaments/{id}/checkout-links`): save `session.url` to `Order.stripe_checkout_url` when creating the Stripe session. - `src/basketball_api/routes/checkout.py` — Update `create_player_checkout_session()` helper: save `session.url` to the Order record. Files the agent should NOT touch: - Blast endpoint logic in admin.py — the per-recipient placeholder resolution already works - `build_action_email()` — email builder is fine - brand.py — no changes needed ### Acceptance Criteria - [ ] `Order.stripe_checkout_url` is populated when checkout links are generated via `POST /admin/tournaments/{id}/checkout-links` - [ ] `tournament_committed` query accepts `tournament_id` in `query_params` and returns per-player `checkout_url` from Order records - [ ] Full end-to-end: create tournament → generate checkout links → blast with `test_email` → email contains unique Stripe URL for the test player - [ ] Players without a pending order for the tournament get `checkout_url: ""` (graceful fallback, not error) - [ ] Existing Order records (from our Utah Invitational session creation) can be backfilled with URLs ### Test Expectations - [ ] Unit test: Order model includes stripe_checkout_url after migration - [ ] Unit test: query_tournament_committed with tournament_id returns checkout_url from Order - [ ] Unit test: checkout-links endpoint saves URL to Order record - [ ] Integration test: blast with tournament_committed query resolves per-player checkout_url in email - Run command: `pytest tests/ -k "tournament"` ### Constraints - Migration number must be determined from remote main HEAD, not hardcoded - Existing 21 pending Orders for Utah Invitational need backfill — the Stripe session URLs exist but weren't saved. Agent should include a backfill script or migration data step. - Follow existing Order model patterns in models.py ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `project-westside-basketball` — parent project - `story:WS-S33` — user story (tournament billing) - #456 — blast system (upstream) - #457 — tournament checkout (upstream) - `sop-email-send` — blast gate for actual sends
Author
Owner

Scope Review: READY

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

All template sections present, all 5 file targets verified against live codebase, upstream dependencies (#456, #457) both merged. Story WS-S33 confirmed in project-westside-basketball user-stories section.

[SCOPE] recommendations (non-blocking):

  • Create architecture note arch-email for the email subsystem
  • Create architecture note arch-checkout for the checkout/payment subsystem

These are documentation debts already tracked by board items #990 and #991. The ticket itself is well-scoped and ready for dispatch.

## Scope Review: READY Review note: `review-998-2026-04-12` All template sections present, all 5 file targets verified against live codebase, upstream dependencies (#456, #457) both merged. Story WS-S33 confirmed in project-westside-basketball user-stories section. **[SCOPE] recommendations (non-blocking):** - Create architecture note `arch-email` for the email subsystem - Create architecture note `arch-checkout` for the checkout/payment subsystem These are documentation debts already tracked by board items #990 and #991. The ticket itself is well-scoped and ready for dispatch.
forgejo_admin 2026-04-12 23:44:03 +00:00
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#463
No description provided.