feat(web): add org credit and invoice page procedures - #5462
Conversation
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryFull review of the unused org credit-transaction and Stripe invoice page procedures, including the keyset ledger cursor and Stripe has_more fix; no high-confidence correctness, security, or runtime issues. Files Reviewed (5 files)
Previous Review Summaries (3 snapshots, latest commit d27fe8b)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit d27fe8b)Status: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of the keyset ledger cursor and Stripe Files Reviewed (5 files)
Previous review (commit 41a8a81)Status: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of the Stripe invoice mapping reuse and test spy isolation; no high-confidence correctness, security, or runtime issues. Files Reviewed (2 files)
Previous review (commit 68211dc)Status: No Issues Found | Recommendation: Merge Executive SummaryReviewed the unused org credit-transaction and Stripe invoice page procedures, matching helpers, and tests; no high-confidence correctness, security, or runtime issues. Files Reviewed (5 files)
Reviewed by grok-4.6 · Input: 136.7K · Output: 11.5K · Cached: 534.7K Review guidance: REVIEW.md from base branch |
| .orderBy(desc(credit_transactions.created_at), desc(credit_transactions.id)) | ||
| .limit(CREDIT_TRANSACTIONS_PAGE_SIZE + 1) | ||
| .offset(cursor), |
There was a problem hiding this comment.
bot: The advertised cursor is an OFFSET, so ledger pages are not stable when credit transactions change between requests.
Suggested fix: Encode the last returned (created_at, id) as the cursor and query for rows strictly after that boundary in the descending order (with the same filters), rather than using .offset(cursor). This keeps later pages disjoint when new transactions arrive or earlier rows are removed. Add a test that fetches page 1, changes the ordered result set, then verifies page 2 does not duplicate or omit the original continuation.
| return { | ||
| entries, | ||
| hasMore: invoices.has_more, | ||
| nextCursor: lastInvoice ? lastInvoice.id : null, |
There was a problem hiding this comment.
bot: A non-empty final Stripe page returns a continuation cursor even when Stripe says there are no more invoices.
Suggested fix: Tie the cursor to Stripe's continuation signal, e.g. nextCursor: invoices.has_more ? lastInvoice?.id ?? null : null, and add a test with non-empty data plus has_more: false that expects nextCursor to be null.
Page the organization credit ledger by keyset ((created_at, id) of the last row) instead of by OFFSET. The ledger grows at the head, so a transaction inserted between two requests shifted every later page and page 2 repeated a row page 1 already showed. Return a Stripe invoice cursor only when Stripe reports has_more, so a full final page no longer advertises a next page that is always empty.
d27fe8b to
69835e4
Compare
Summary
No new behavior. The two new paginated endpoints are not consumed by any page yet, so a user sees the same organization credit transaction and invoice views as before.
Reviewer Notes
The organization credit transaction query gains a cursor-paginated form, the
CreditTransactionsPagecontract.getCreditTransactionsForOrganizationPagereturns 25 entries plus a probe ordered newest first, with anextCursor, ahasMoreflag, and aCreditSummarycomputed by the newgetCreditTransactionsSummaryForOrganization; both helpers excludekpo:consumption:categories exactly like the old query. The oldgetCreditTransactionsForOrganizationarray form stays unchanged and is markedold form, so existing callers keep working until every client migrates to the page form.Files
apps/web/src/lib/creditTransactions.ts— exports theCreditSummarytype; addsgetCreditTransactionsSummaryForOrganizationandgetCreditTransactionsForOrganizationPage; marks the oldgetCreditTransactionsForOrganizationwith anold formcomment.The Stripe invoice listing gains a cursor-paginated form, the
StripeInvoicesPagecontract.getStripeInvoicesPagelists 25 invoices with an optionalstarting_aftercursor, readshas_more, and returns anextCursorequal to the last invoice id. The invoice-to-unified mapping is extracted intomapStripeInvoicesToUnifiedand reused by both functions; the oldgetStripeInvoiceskeeps returning a flat array of up to 100 and is markedold form.Files
apps/web/src/lib/stripe/index.ts— adds theStripeInvoicesPagetype, themapStripeInvoicesToUnifiedhelper, andgetStripeInvoicesPage; marksgetStripeInvoiceswith anold formcomment.Two new tRPC procedures expose the paginated queries.
organizations.creditTransactionsPageserves the member role with a defaulted non-negative numeric cursor, andorganizations.invoicesPageserves the billing role with an optional string cursor, resolving or creating the Stripe customer id first just like the oldinvoicesprocedure. The existingcreditTransactionsandinvoicesprocedures keep their array returns, so no client breaks.Files
apps/web/src/routers/organizations/organization-router.ts— adds theOrganizationTransactionsPageInputSchemaandOrganizationInvoicesPageInputSchemaschemas plus thecreditTransactionsPageandinvoicesPageprocedures beside their array-returning predecessors.Tests: 2 test files changed —
creditTransactions.page.test.ts(new) andstripe/index.test.ts(extended); both DB-backed tests need the test Postgres and run in CI.Generated: none.
Verification
Human steps: none. No new environment values, secrets, or migrations.
Visual Changes
Visual Changes: N/A
Stacked PRs — merge bottom to top. Each level shows only its own diff.
Full verification (E2E, user advocacy, simplify, bot review) runs on the tip PR over every level.
A finding on a level is repaired on that level, then carried upward with stack.sh forward.
audit-w7b-data-contracts-dffd— refactor(mobile): parse raw HTTP and infer review contracts #5460audit-w7b-data-contracts-dffd-s2— feat(web): add org credit and invoice page procedures #5462 ← this PRaudit-w7b-data-contracts-dffd-s3— feat(mobile): owner-keyed credits, ledger paging, list freshness #5468audit-w7b-data-contracts-dffd-s4— refactor: route-scoped registry and live custom mode #5472audit-w7b-data-contracts-dffd-s5— feat(mobile): provider-aware new-session and layout extract #5479 (tip)