Add voter_limit column & stripeCheckoutSessionsDB table - #1589
ArendPeter wants to merge 4 commits into
Conversation
✅ Deploy Preview for bettervoting ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Flagging a needed follow-up before merge (or as a quick amendment here): the checkout flow was redesigned around a cart — see #1590. |
|
|
||
| export async function up(db: Kysely<any>): Promise<void> { | ||
| await db.schema.alterTable('electionDB') | ||
| .addColumn('voter_limit', 'integer') |
There was a problem hiding this comment.
I thought about if the voter_limit should be it's own column or part of the election settings.
It could technically be part of the settings, but on principle I think items under the settings should be assumed to by editable by the admin, and anything that requires more careful state control should be surfaced as a column.
| // today becomes retroactively unable to keep its current voters. | ||
| await sql` | ||
| UPDATE "electionDB" e | ||
| SET voter_limit = GREATEST(100, ( |
There was a problem hiding this comment.
It makes sense to hardcode this rather than use the variable from the shared package. If the variable in the shared package ever changes, then that would require an additional migration.
|
|
||
| // Backfill: no election that already has more than 100 voters on its roll | ||
| // today becomes retroactively unable to keep its current voters. | ||
| await sql` |
There was a problem hiding this comment.
This is written using sql instead of the kysely ORM functions because kysely would have been more awkward. Here's an example of what that would have looked like
await db
.updateTable("electionDB")
.set((eb) => ({
voter_limit: eb.fn("greatest", [
eb.val(100),
eb
.selectFrom("electionRollDB")
.select(eb.fn.countAll().$castTo<number>().as("count"))
.whereRef("electionRollDB.election_id", "=", "electionDB.election_id")
.where("head", "=", true),
]),
}))
.execute();
|
Pushed a fixup addressing the follow-up above: |
d14017f to
02cafae
Compare
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.
02cafae to
f6cb070
Compare
| .addColumn('id', 'serial', (col) => col.primaryKey()) | ||
| .addColumn('election_id', 'varchar', (col) => col.notNull()) | ||
| .addColumn('user_id', 'varchar', (col) => col.notNull()) | ||
| .addColumn('line_items', 'jsonb', (col) => col.notNull()) |
There was a problem hiding this comment.
Line items will be a json blob matching what was passed to stripe. Here's an example for adding 600 voters to the vote limit
line_items: [
{
price_data: {
currency: "usd",
unit_amount: 500,
product_data: {
name: "Voter Batch",
description: "Add a batch of voters to the vote limit (200 per batch)",
images: [
"https://example.com/images/product.jpg",
],
},
},
quantity: 3,
}
]
This is written to be generic so that the transaction could also theoretically include both a voter limit increase and an add-on, such as adding a custom slug.
|
Warning Review limit reachedNext included review available in 56 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (16)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| await db.schema | ||
| .createTable('stripeCheckoutSessionsDB') | ||
| .addColumn('id', 'serial', (col) => col.primaryKey()) | ||
| .addColumn('election_id', 'varchar', (col) => col.notNull()) |
There was a problem hiding this comment.
Do we need amount_cents here as well?
There was a problem hiding this comment.
That can be derived from the itemized info in line_items
| election.update_date = Date.now().toString()// Use now() because it doesn't change with time zone | ||
| election.head = true | ||
| election.create_date = new Date().toISOString() | ||
| election.voter_limit = pricingConfig.FREE_TIER_LIMIT |
There was a problem hiding this comment.
We set voter_limit here to free tier limit. But I think they can edit it later with a suitable devious CURL using the updateElection endpoint. (like POST {"Election": {..., "voter_limit": 1000000}} to /API/Election/:id/edit)
There was a problem hiding this comment.
Agreed, I've got it on the roadmap for the next PRs
The automated ticket generation got really messy, but it's tracked at #1585
Human Summary
This is the first in a series of PRs adding stripe integration and payment support. I'm starting by storing the voter limit per election, and setting up a stripe table for managing tracking the transactions.
Summary
voter_limit(integer, not null) toelectionDB, backfilled toGREATEST(100, current head roll count)so no election that already has more voters loses them.voter_limit = FREE_TIER_LIMIT(100), set server-side inElectionsDB.createElection, matching the existing pattern forcreate_date/update_date/head.stripeCheckoutSessionsDB(Stripe checkout bookkeeping + webhook idempotency key), with a thin-wrapper model class (StripeCheckoutSessionsDB) and mock, mirroringEmailEvents.ts's style.FREE_TIER_LIMIT(100),BLOCK_SIZE(200),PRICE_PER_BLOCK_CENTS(1000).packages/shared's"./config"export map, which pointed directly atSharedConfig.js/.d.tsand so silently bypassedconfig/index.ts(and anything re-exported from it, including the new pricing config) for any consumer importing@equal-vote/star-vote-shared/config.This is the first of 4 graduated build tickets off Design backend data model & Stripe integration flow for Phase 1 payments, part of the Phase 1 Payment System map. Resolves #1584.
Test plan
npm run build -ws(shared, backend, frontend) — all greennpm test -w @equal-vote/star-vote-backend— 27 suites / 211 tests passup/downround-tripped against a disposable Postgres container🤖 Generated with Claude Code