First monthly payment email + Stripe checkout for signed contracts #366

Closed
opened 2026-04-07 01:05:53 +00:00 by forgejo_admin · 1 comment

Type

Feature

Lineage

Standalone — scoped during 2026-04-06 session. Prorated first payment is due per signed contracts. Spec and plan written in pal-e-platform.

Repo

forgejo_admin/basketball-api

User Story

As a parent who signed a Westside contract
I want to receive an email with a one-click payment link
So that I can pay my prorated first monthly fee before the first practice

Context

First practice is April 7 (Tuesday). Contracts specify a prorated first monthly fee due around April 6-8. Each player has a per-player monthly_fee stored on the Player model ($200 standard, $180 LCA, $160 girls/Cyprus). The proration formula is round(fee * 25/30 / 5) * 5, matching the formula in westside-contracts +page.svelte:9.

The approach is a direct API redirect: parent clicks CTA in email → GET /checkout/first-payment?token={contract_token} → endpoint creates Stripe Checkout Session → 302 redirect to Stripe hosted page → webhook marks Order paid. No frontend intermediary page needed.

Email tone: friendly-firm (Marcus voice). "First practice tomorrow, here's what's due."

File Targets

Files to create:

  • alembic/versions/031_add_monthly_category_and_first_payment_email.py — migration: enum values + product seed
  • tests/test_first_payment.py — checkout endpoint tests
  • tests/test_first_payment_email.py — email function tests
  • tests/test_first_payment_blast.py — blast endpoint tests

Files to modify:

  • src/basketball_api/models.py — add monthly to ProductCategory, first_payment to EmailType
  • src/basketball_api/routes/checkout.py — add GET /checkout/first-payment endpoint
  • src/basketball_api/services/email.py — add send_first_payment_email()
  • src/basketball_api/routes/admin.py — add POST /admin/email/first-payment blast endpoint

Files NOT to touch:

  • src/basketball_api/routes/webhooks.py — existing _handle_generic_order_completed already handles this
  • src/basketball_api/routes/subscriptions.py ��� recurring subscriptions are out of scope

Acceptance Criteria

  • Migration applies cleanly: adds monthly ProductCategory, first_payment EmailType, seeds "Monthly Fee — Prorated April" product
  • GET /checkout/first-payment?token={contract_token} returns 302 redirect to Stripe Checkout
  • Invalid/unsigned tokens return 404
  • Duplicate orders return 409
  • Proration: $200→$165, $180→$150, $160→$135, null→$165 (defaults to $200)
  • send_first_payment_email() produces branded HTML with player name, prorated amount, and checkout CTA
  • POST /admin/email/first-payment sends to all contract_status=signed players
  • test_email param on blast endpoint restricts to one parent
  • EmailLog entry written with email_type=first_payment

Test Expectations

  • Unit test: proration formula for all tiers ($200, $180, $160) and null default
  • Unit test: valid token creates Stripe session + redirect
  • Unit test: invalid token 404, unsigned contract 404, duplicate order 409
  • Unit test: email function sends with correct content, logs to DB
  • Unit test: blast sends to signed players only, test_email filters correctly
  • Run: pytest tests/test_first_payment.py tests/test_first_payment_email.py tests/test_first_payment_blast.py -v

Constraints

  • Follow existing checkout.py patterns for Stripe Customer create/reuse
  • Follow existing admin.py blast pattern (jersey-reminder) for test_email safety
  • Follow existing email.py _brand_wrapper() pattern for branded HTML
  • Use contract_token (Player model) not registration_token (Parent model)
  • Proration formula must match westside-contracts exactly
  • CTA links to {base_url}/checkout/first-payment?token=X (API, not frontend_url)
  • Include -lock=false if any tofu commands needed (N/A for this ticket)

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • westside-basketball — project this affects
  • Spec: ~/pal-e-platform/docs/superpowers/specs/2026-04-06-first-payment-email-design.md
  • Plan: ~/pal-e-platform/docs/superpowers/plans/2026-04-06-first-payment-email.md
### Type Feature ### Lineage Standalone — scoped during 2026-04-06 session. Prorated first payment is due per signed contracts. Spec and plan written in pal-e-platform. ### Repo `forgejo_admin/basketball-api` ### User Story As a parent who signed a Westside contract I want to receive an email with a one-click payment link So that I can pay my prorated first monthly fee before the first practice ### Context First practice is April 7 (Tuesday). Contracts specify a prorated first monthly fee due around April 6-8. Each player has a per-player `monthly_fee` stored on the Player model ($200 standard, $180 LCA, $160 girls/Cyprus). The proration formula is `round(fee * 25/30 / 5) * 5`, matching the formula in westside-contracts `+page.svelte:9`. The approach is a direct API redirect: parent clicks CTA in email → `GET /checkout/first-payment?token={contract_token}` → endpoint creates Stripe Checkout Session → 302 redirect to Stripe hosted page → webhook marks Order paid. No frontend intermediary page needed. Email tone: friendly-firm (Marcus voice). "First practice tomorrow, here's what's due." ### File Targets Files to create: - `alembic/versions/031_add_monthly_category_and_first_payment_email.py` — migration: enum values + product seed - `tests/test_first_payment.py` — checkout endpoint tests - `tests/test_first_payment_email.py` — email function tests - `tests/test_first_payment_blast.py` — blast endpoint tests Files to modify: - `src/basketball_api/models.py` — add `monthly` to ProductCategory, `first_payment` to EmailType - `src/basketball_api/routes/checkout.py` — add `GET /checkout/first-payment` endpoint - `src/basketball_api/services/email.py` — add `send_first_payment_email()` - `src/basketball_api/routes/admin.py` — add `POST /admin/email/first-payment` blast endpoint Files NOT to touch: - `src/basketball_api/routes/webhooks.py` — existing `_handle_generic_order_completed` already handles this - `src/basketball_api/routes/subscriptions.py` ��� recurring subscriptions are out of scope ### Acceptance Criteria - [ ] Migration applies cleanly: adds `monthly` ProductCategory, `first_payment` EmailType, seeds "Monthly Fee — Prorated April" product - [ ] `GET /checkout/first-payment?token={contract_token}` returns 302 redirect to Stripe Checkout - [ ] Invalid/unsigned tokens return 404 - [ ] Duplicate orders return 409 - [ ] Proration: $200→$165, $180→$150, $160→$135, null→$165 (defaults to $200) - [ ] `send_first_payment_email()` produces branded HTML with player name, prorated amount, and checkout CTA - [ ] `POST /admin/email/first-payment` sends to all `contract_status=signed` players - [ ] `test_email` param on blast endpoint restricts to one parent - [ ] EmailLog entry written with `email_type=first_payment` ### Test Expectations - [ ] Unit test: proration formula for all tiers ($200, $180, $160) and null default - [ ] Unit test: valid token creates Stripe session + redirect - [ ] Unit test: invalid token 404, unsigned contract 404, duplicate order 409 - [ ] Unit test: email function sends with correct content, logs to DB - [ ] Unit test: blast sends to signed players only, test_email filters correctly - Run: `pytest tests/test_first_payment.py tests/test_first_payment_email.py tests/test_first_payment_blast.py -v` ### Constraints - Follow existing checkout.py patterns for Stripe Customer create/reuse - Follow existing admin.py blast pattern (jersey-reminder) for test_email safety - Follow existing email.py `_brand_wrapper()` pattern for branded HTML - Use `contract_token` (Player model) not `registration_token` (Parent model) - Proration formula must match westside-contracts exactly - CTA links to `{base_url}/checkout/first-payment?token=X` (API, not frontend_url) - Include `-lock=false` if any tofu commands needed (N/A for this ticket) ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `westside-basketball` — project this affects - Spec: `~/pal-e-platform/docs/superpowers/specs/2026-04-06-first-payment-email-design.md` - Plan: `~/pal-e-platform/docs/superpowers/plans/2026-04-06-first-payment-email.md`
Author
Owner

Scope Review: NEEDS_REFINEMENT

Review note: review-872-2026-04-06
Well-scoped feature with all template sections present, verified file targets, and concrete AC -- but too large for a single agent pass.

  • [SCOPE] Architecture notes arch-basketball-api and arch-email do not exist in pal-e-docs. Create them.
  • [DECOMPOSE] 9 AC across 8 files (4 create + 4 modify). Exceeds 3-file and 5-AC thresholds. Route to skill-decompose-ticket for 3-ticket split: (A) migration+models, (B) checkout endpoint+tests, (C) email+blast+tests. A is prerequisite; B and C can parallelize.
## Scope Review: NEEDS_REFINEMENT Review note: `review-872-2026-04-06` Well-scoped feature with all template sections present, verified file targets, and concrete AC -- but too large for a single agent pass. - **[SCOPE]** Architecture notes `arch-basketball-api` and `arch-email` do not exist in pal-e-docs. Create them. - **[DECOMPOSE]** 9 AC across 8 files (4 create + 4 modify). Exceeds 3-file and 5-AC thresholds. Route to `skill-decompose-ticket` for 3-ticket split: (A) migration+models, (B) checkout endpoint+tests, (C) email+blast+tests. A is prerequisite; B and C can parallelize.
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#366
No description provided.