Skip to content

Add voter_limit column & stripeCheckoutSessionsDB table (migration + shared pricing config) #1584

Description

@ArendPeter

Question / Task

Part of the Phase 1 Payment System: Design & Build Map, graduated from Design backend data model & Stripe integration flow for Phase 1 payments (see that ticket's resolution comment for full design rationale).

Add the foundational schema and shared pricing constants everything else in this map builds on.

Migration (follow packages/backend/src/Migrations/2026_03_19_email_events.ts's style):

  • Add voter_limit (integer, not null) to electionDB. Backfill existing rows: voter_limit = GREATEST(100, current roll count for that election) — so no election that already has more than 100 voters on its roll today becomes retroactively unable to keep its current voters (it just can't add more without paying, same as any other election over the free limit). New elections get voter_limit = FREE_TIER_LIMIT (100) at creation.
  • Add voter_limit to the Election interface (packages/shared/src/domain_model/Election.ts) and to electionValidation().
  • Create stripeCheckoutSessionsDB: id (serial PK), election_id (varchar, not null), user_id (varchar, not null), product (varchar, not null — 'voter_limit' for now), amount_cents (integer, not null), voter_count_granted (integer, nullable), stripe_checkout_session_id (varchar, not null, unique — the webhook idempotency key), stripe_customer_id (varchar), status (varchar, not null, 'pending' | 'paid'), created_date (timestamptz, not null). Index on election_id.
  • Model class StripeCheckoutSessionsDB in packages/backend/src/Models/, following EmailEvents.ts's thin-wrapper style: insert(), getByStripeSessionId(), markPaid(), sumVoterLimitPurchases(election_id) (sums voter_count_granted where product='voter_limit' AND status='paid' — useful for audit/support, not for computing voter_limit itself, which is authoritative on the election row).

Shared pricing config (packages/shared, alongside SharedConfig.ts or a new small config module): FREE_TIER_LIMIT = 100, BLOCK_SIZE = 200, PRICE_PER_BLOCK_CENTS = 1000.

No Stripe dashboard Product/Price objects need to be created — pricing uses dynamic price_data per Checkout Session (confirmed in the closed research ticket).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions