feat: dynamic Stripe Checkout Sessions for tryout registration #112

Closed
opened 2026-03-19 01:40:40 +00:00 by forgejo_admin · 0 comments
Contributor

Type

Feature

Lineage

plan-wkq → Phase 4 (Dynamic Stripe Checkout Sessions)

Repo

forgejo_admin/basketball-api

User Story

As a parent paying for tryout registration online
I want my payment to be reliably linked to my registration
So that my account is auto-created and I get login credentials

Context

PR #111 introduced card payment save-before-redirect with email-based webhook matching. This is fragile — email matching can fail if the parent uses a different email at checkout. The jersey flow (routes/jersey.py:147-166) already uses dynamic stripe.checkout.Session.create() with metadata for deterministic matching. This issue replaces the static Payment Link redirect with the same Checkout Session pattern, using registration_id in metadata.

Jersey Checkout Session pattern to follow (jersey.py:147-166):

checkout_session = stripe.checkout.Session.create(
    mode="payment",
    customer=customer_id,  # optional for registration
    line_items=[{
        "price_data": {
            "currency": "usd",
            "product_data": {"name": "..."},
            "unit_amount": price_cents,
        },
        "quantity": 1,
    }],
    metadata={"player_id": str(player.id), "...": "..."},
    success_url=_SUCCESS_URL,
    cancel_url=_CANCEL_URL,
)

File Targets

Files to modify:

  • src/basketball_api/routes/register.py — Replace settings.stripe_tryout_link redirect with stripe.checkout.Session.create(). Add metadata: registration_id, player_id, player_name, type: "tryout_registration". Use settings.frontend_url for success/cancel URLs.
  • src/basketball_api/routes/webhooks.py — Replace email-based pending registration matching (lines ~198-237) with metadata-based matching: check metadata.type == "tryout_registration" and look up by metadata.registration_id. After marking paid, trigger Keycloak auto-account creation.
  • tests/test_promo_registration.py — Update card test to mock stripe.checkout.Session.create. Add webhook test with metadata-based matching.

Files NOT to touch:

  • routes/jersey.py — reference only, don't modify
  • config.pystripe_tryout_link setting can stay (backward compat), just stop using it
  • services/registration.py — old Payment Link flow, not part of this change

Acceptance Criteria

  • Card registration creates stripe.checkout.Session with registration_id and type: "tryout_registration" in metadata
  • Checkout Session uses $30.00 (3000 cents) as amount
  • success_url: {frontend_url}/register?payment=success
  • cancel_url: {frontend_url}/register?payment=cancelled
  • Returns {"redirect_url": session.url} to frontend
  • Webhook matches by metadata.type == "tryout_registration" + metadata.registration_id
  • Webhook updates registration to paid with Stripe IDs
  • Webhook triggers Keycloak auto-account creation after marking paid
  • Old email-based matching code removed
  • All tests pass

Test Expectations

  • Unit test: card registration mocks stripe.checkout.Session.create, asserts metadata and amount
  • Unit test: webhook with type: tryout_registration metadata marks registration paid
  • Unit test: webhook triggers Keycloak account creation (mock)
  • Unit test: webhook without matching metadata falls through
  • Run command: pytest tests/test_promo_registration.py -v

Constraints

  • Follow jersey.py Checkout Session pattern exactly
  • Amount: 3000 cents ($30.00) — hardcode for now, config later
  • customer_email param on Session: use parent email (Stripe pre-fills checkout form)
  • Keycloak auto-account creation: import and call the same logic used for promo registrations
  • Must work with the existing signup_method="stripe" and payment_status="pending" flow

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • westside-basketball — project
  • basketball-api PR #111 — the email-based interim this replaces
  • westside-app issue (Phase 5) will handle success/cancel pages on the SPA side
### Type Feature ### Lineage `plan-wkq` → Phase 4 (Dynamic Stripe Checkout Sessions) ### Repo `forgejo_admin/basketball-api` ### User Story As a parent paying for tryout registration online I want my payment to be reliably linked to my registration So that my account is auto-created and I get login credentials ### Context PR #111 introduced card payment save-before-redirect with email-based webhook matching. This is fragile — email matching can fail if the parent uses a different email at checkout. The jersey flow (`routes/jersey.py:147-166`) already uses dynamic `stripe.checkout.Session.create()` with metadata for deterministic matching. This issue replaces the static Payment Link redirect with the same Checkout Session pattern, using `registration_id` in metadata. **Jersey Checkout Session pattern to follow** (jersey.py:147-166): ```python checkout_session = stripe.checkout.Session.create( mode="payment", customer=customer_id, # optional for registration line_items=[{ "price_data": { "currency": "usd", "product_data": {"name": "..."}, "unit_amount": price_cents, }, "quantity": 1, }], metadata={"player_id": str(player.id), "...": "..."}, success_url=_SUCCESS_URL, cancel_url=_CANCEL_URL, ) ``` ### File Targets Files to modify: - `src/basketball_api/routes/register.py` — Replace `settings.stripe_tryout_link` redirect with `stripe.checkout.Session.create()`. Add metadata: `registration_id`, `player_id`, `player_name`, `type: "tryout_registration"`. Use `settings.frontend_url` for success/cancel URLs. - `src/basketball_api/routes/webhooks.py` — Replace email-based pending registration matching (lines ~198-237) with metadata-based matching: check `metadata.type == "tryout_registration"` and look up by `metadata.registration_id`. After marking paid, trigger Keycloak auto-account creation. - `tests/test_promo_registration.py` — Update card test to mock `stripe.checkout.Session.create`. Add webhook test with metadata-based matching. Files NOT to touch: - `routes/jersey.py` — reference only, don't modify - `config.py` — `stripe_tryout_link` setting can stay (backward compat), just stop using it - `services/registration.py` — old Payment Link flow, not part of this change ### Acceptance Criteria - [ ] Card registration creates `stripe.checkout.Session` with `registration_id` and `type: "tryout_registration"` in metadata - [ ] Checkout Session uses `$30.00` (3000 cents) as amount - [ ] success_url: `{frontend_url}/register?payment=success` - [ ] cancel_url: `{frontend_url}/register?payment=cancelled` - [ ] Returns `{"redirect_url": session.url}` to frontend - [ ] Webhook matches by `metadata.type == "tryout_registration"` + `metadata.registration_id` - [ ] Webhook updates registration to `paid` with Stripe IDs - [ ] Webhook triggers Keycloak auto-account creation after marking paid - [ ] Old email-based matching code removed - [ ] All tests pass ### Test Expectations - [ ] Unit test: card registration mocks `stripe.checkout.Session.create`, asserts metadata and amount - [ ] Unit test: webhook with `type: tryout_registration` metadata marks registration paid - [ ] Unit test: webhook triggers Keycloak account creation (mock) - [ ] Unit test: webhook without matching metadata falls through - Run command: `pytest tests/test_promo_registration.py -v` ### Constraints - Follow jersey.py Checkout Session pattern exactly - Amount: 3000 cents ($30.00) — hardcode for now, config later - `customer_email` param on Session: use parent email (Stripe pre-fills checkout form) - Keycloak auto-account creation: import and call the same logic used for promo registrations - Must work with the existing `signup_method="stripe"` and `payment_status="pending"` flow ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `westside-basketball` — project - basketball-api PR #111 — the email-based interim this replaces - westside-app issue (Phase 5) will handle success/cancel pages on the SPA side
forgejo_admin 2026-03-19 01:57: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
ldraney/basketball-api#112
No description provided.