Skip to content

Stripe backend: voter limit + checkout session API - #4

Draft
ArendPeter wants to merge 9 commits into
mainfrom
stripe-backend
Draft

ArendPeter wants to merge 9 commits into
mainfrom
stripe-backend

Conversation

@ArendPeter

Copy link
Copy Markdown
Owner

Human Summary

This change builds on the stripe database change in Equal-Vote#1589. It adds API support for checkout sessions.

Summary

  • Merges sandcastle/issue-1585 (voter-limit resolution hierarchy, payment-required response, and voter_limit lockdown) and sandcastle/issue-1586 (Checkout Session creation endpoint — cart validation + Stripe session) into stripe-backend.
  • Adds StripeService and a product catalog for building Stripe line items, plus a createCheckoutSessionController and route wiring.
  • Fixes a type-inference issue in StripeCheckoutSessionsDB.sumVoterLimitPurchases that only surfaced when type-checked under the frontend's non-strict tsconfig.json (which transitively type-checks this backend file via useAPI.ts).
  • Regenerated package-lock.json to reconcile dependency additions from both branches (adds stripe).

Test plan

  • npm run build -w @equal-vote/star-vote-backend passes
  • npm run build -w @equal-vote/star-vote-frontend passes (including the transitive type-check of backend files)
  • npm test -w @equal-vote/star-vote-backend — 228/228 tests pass

ArendPeter and others added 9 commits August 26, 2026 00:02
Per Equal-Vote#1590 the checkout flow moved to a cart model: one Checkout
Session can carry multiple line items, so `product` now stores a
JSON array of line items (each a full Stripe price_data snapshot
tagged with its internal product type) instead of a single varchar
product type. voter_count_granted stays the authoritative aggregate
on the row, so sumVoterLimitPurchases no longer filters by product.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012e7hk5frznvBj8kbB3uu4M
Replaced 'product' and 'amount_cents' columns with 'line_items' column in the stripeCheckoutSessionsDB table.
…e & lock down voter_limit (issue Equal-Vote#1585)

Task: Wire voter-limit resolution hierarchy, payment-required response & lock down voter_limit
PRD: Phase 1 Payment System (Equal-Vote#1566), graduated from Equal-Vote#1568

Key decisions:
- Voter limit now falls back to election.voter_limit (not sharedConfig.FREE_TIER_PRIVATE_VOTER_LIMIT)
  — override dict still wins when present, matching the issue spec
- Limit-exceeded response is 402 with structured body (code, currentVoterLimit, requestedVoterCount,
  blockSize, pricePerBlockCents) — sent directly via res.json(), bypassing errorCatch middleware
- editElectionController overwrites inputElection.voter_limit with req.election.voter_limit before
  calling updateElection(), discarding any client-submitted value
- useFetch suppresses the generic snackbar for PAYMENT_REQUIRED and exposes latestErrorResponse ref
  so the caller can read the raw error body synchronously after makeRequest returns false
- AddElectionRoll.tsx has a stub branch for code === 'PAYMENT_REQUIRED' (modal is separate ticket)
- Mock ElectionsDB now sets voter_limit = pricingConfig.FREE_TIER_LIMIT in createElection(),
  matching the real implementation

Files changed:
- packages/backend/src/Controllers/Roll/addElectionRollController.ts
- packages/backend/src/Controllers/Election/editElectionController.ts
- packages/backend/src/Models/__mocks__/Elections.ts
- packages/frontend/src/hooks/useFetch.ts
- packages/frontend/src/components/Election/Admin/AddElectionRoll.tsx
- packages/backend/src/test/voterLimitRoll.test.ts (new, 5 tests)

Blockers/notes:
- Branch is based on voter-limit-migration-1584 (PR Equal-Vote#1589) which must merge first
- Payment modal (opened on PAYMENT_REQUIRED) is separate frontend work tracked elsewhere

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ripe session) — issue Equal-Vote#1586

Task: POST /API/Election/:id/CheckoutSession

Key decisions:
- Product catalog in Services/Stripe/catalog.ts: pure functions (validateCart,
  buildLineItems, computeTotals) — one entry (voter_limit_block), easily testable
- StripeService wrapper + __mocks__ counterpart wired into ServiceLocator
- Placeholder UUID written to DB first to get row ID for Stripe metadata; real
  session ID swapped in post-creation via new updateStripeSessionId()
- Max voter_limit capped at 5000; 'pending' row inserted before Stripe call
- Shared domain model updated: StripeCheckoutSessionProduct → 'voter_limit_block';
  StripeCheckoutSessionLineItem.product renamed to .type (matches spec JSON)

Files changed:
- packages/backend/src/Services/Stripe/catalog.ts (new)
- packages/backend/src/Services/Stripe/StripeService.ts (new)
- packages/backend/src/Services/Stripe/__mocks__/StripeService.ts (new)
- packages/backend/src/Controllers/Election/createCheckoutSessionController.ts (new)
- packages/backend/src/test/checkoutSession.test.ts (new — 9 tests, all pass)
- packages/backend/src/Models/StripeCheckoutSessions.ts (add updateStripeSessionId)
- packages/backend/src/Models/__mocks__/StripeCheckoutSessions.ts (same)
- packages/backend/src/ServiceLocator.ts (add stripeCheckoutSessionsDb, stripeService)
- packages/backend/src/__mocks__/ServiceLocator.ts (same)
- packages/backend/src/Controllers/Election/index.ts (re-export)
- packages/backend/src/Routes/elections.routes.ts (add route)
- packages/shared/src/domain_model/StripeCheckoutSession.ts (type fix)
- packages/backend/package.json + package-lock.json (add stripe dependency)

Builds on voter-limit-migration-1584 branch (merged in); requires STRIPE_SECRET_KEY.
All 223 backend tests pass; tsc clean.

Blockers: STRIPE_SECRET_KEY must be configured for production use.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
frontend/tsconfig.json has strict:false, which changes how tsc infers
the callback param type in kysely's .select(eb => ...) here, causing
"Untyped function calls may not accept type arguments" during the
frontend build (which type-checks this file transitively via
useAPI.ts's import of a backend controller).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant