Skip to content

✨ server: support many cards in the statement#893

Open
nfmelendez wants to merge 1 commit intomainfrom
statement
Open

✨ server: support many cards in the statement#893
nfmelendez wants to merge 1 commit intomainfrom
statement

Conversation

@nfmelendez
Copy link
Contributor

@nfmelendez nfmelendez commented Mar 16, 2026

Summary by CodeRabbit

  • New Features

    • Statements now support multiple cards with per-card purchase lists, per-card totals, and a separate payments section; exports (JSON/PDF) group purchases by card and include card identifiers (last four).
  • UI

    • Redesigned statement layout: account header, period and due dates, summary totals, per-card purchase tables, Payments section, and overall due balance with formatted currency.
  • Tests

    • Updated tests to cover multiple cards, purchases, and payments scenarios.

Open with Devin

@changeset-bot
Copy link

changeset-bot bot commented Mar 16, 2026

🦋 Changeset detected

Latest commit: 558ff97

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@exactly/server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Switches statement generation from a single-card, item-centric model to a purchases-by-card model: Activity API now queries credential cards, attaches cardId to items, groups purchases per card and separates payments; Statement component and tests updated to accept/render account, multiple cards with purchases, maturity, and payments for JSON/PDF output.

Changes

Cohort / File(s) Summary
Changeset Entry
.changeset/soft-trains-dig.md
Adds a patch changeset for @exactly/server noting "✨ support multiple cards in statement".
Activity API
server/api/activity.ts
Replaces card-centric flow with purchases-based aggregation: queries credential cards (id, lastFour), builds hash→cardId lookup, injects cardId into response items, groups purchases by card, and extracts payments for statement construction (JSON and PDF).
Statement Component
server/utils/Statement.tsx
Public signature changed to accept account, cards[] (each with lastFour and purchases[]), maturity, and payments[]; adds currency/date formatting, per-card purchase tables, totals (totalSpent, totalPayments, dueBalance), and layout updates for multi-card rendering.
Tests / Fixtures
server/test/api/activity.test.ts, server/test/utils/statement.test.ts
Test seeds adjusted to two-card fixtures, purchases moved under each card, payments added; assertions updated for per-card headers, purchases listing, payments section, summary totals, and due balance text.
Other / Internal
server/...
PDF construction and internal data querying/aggregation refactored to use purchases-by-card grouping; statement JSON kept compatible but now includes per-card purchases and payments.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant API as Activity API
    participant DB as Database
    participant Statement as Statement Component
    participant Output as PDF/JSON Output

    Client->>API: Request statement (account, format)
    API->>DB: Query credential cards (id, lastFour)
    DB-->>API: Cards list
    API->>DB: Fetch transactions/items for account
    DB-->>API: Transactions/items
    API->>API: Build purchases (attach cardId, group by card)
    API->>API: Extract payments
    API->>Statement: Send account, cards[{lastFour,purchases[]}], maturity, payments
    Statement->>Statement: Compute totals, format dates/currency
    Statement->>Output: Render/serialize PDF or return JSON
    Output-->>Client: Statement (PDF or JSON)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • cruzdanilo
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main change: adding support for multiple cards in statement rendering, which is the core objective of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch statement
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enables the generation of comprehensive user statements that can include transactions from multiple cards. The changes involve updating the data retrieval process in the API to fetch all relevant card data and refactoring the PDF rendering component to display this multi-card information in a structured and user-friendly format. This enhancement provides users with a more complete overview of their financial activity across all their associated cards.

Highlights

  • Multi-Card Statement Support: The server-side logic and PDF generation for user statements have been updated to support displaying transactions from multiple cards within a single statement.
  • API Refactoring: The activity.ts API endpoint was refactored to correctly query and aggregate transaction data for all cards associated with a credential, removing a previous limitation to a single card for PDF statements.
  • Enhanced PDF Statement Layout: The Statement.tsx component was significantly redesigned to present a clear, organized view of purchases grouped by individual cards, along with a summary of total spending and payments.
  • Comprehensive Testing: API and statement rendering tests were expanded to include scenarios with multiple cards, ensuring the new functionality works as expected.
Changelog
  • .changeset/soft-trains-dig.md
    • Added a new changeset entry for multi-card statement support.
  • server/api/activity.ts
    • Modified the cards query to explicitly select id and lastFour.
    • Refactored the logic for fetching card purchases to support multiple cards and their associated transactions.
    • Updated the PDF generation to process and group purchases by card.
    • Removed the restriction that prevented PDF generation for statements with more than one card.
  • server/test/api/activity.test.ts
    • Added a second card to the test setup.
    • Updated transaction assignments to distribute them across multiple cards for testing.
  • server/test/utils/statement.test.ts
    • Updated existing test cases to match the new statement data structure.
    • Added a new test case to verify rendering of statements with multiple cards.
    • Modified assertions to reflect changes in the rendered PDF text.
  • server/utils/Statement.tsx
    • Updated the Statement component's props to accept account, an array of cards, maturity, and payments.
    • Implemented new rendering logic to display purchases grouped by individual cards.
    • Introduced a summary section for total spent and due balance.
    • Refactored date formatting functions.
    • Updated styling for a more structured and multi-card friendly layout.
Activity
  • The pull request was created by nfmelendez.
  • A new changeset file was added, indicating a patch release for @exactly/server.
  • The core logic for generating activity statements was updated to handle multiple cards.
  • Corresponding test files were modified to reflect and validate the new multi-card statement functionality.
  • The UI component responsible for rendering PDF statements was significantly refactored to display the new multi-card data structure.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

gemini-code-assist[bot]

This comment was marked as resolved.

@sentry
Copy link

sentry bot commented Mar 16, 2026

Codecov Report

❌ Patch coverage is 86.90476% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.92%. Comparing base (1e5616a) to head (558ff97).
⚠️ Report is 39 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
server/api/activity.ts 79.41% 1 Missing and 6 partials ⚠️
server/utils/Statement.tsx 92.00% 0 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #893      +/-   ##
==========================================
+ Coverage   71.47%   71.92%   +0.45%     
==========================================
  Files         212      212              
  Lines        8470     8582     +112     
  Branches     2776     2829      +53     
==========================================
+ Hits         6054     6173     +119     
+ Misses       2134     2132       -2     
+ Partials      282      277       -5     
Flag Coverage Δ
e2e 71.81% <86.90%> (+0.33%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
server/utils/Statement.tsx (2)

99-111: 🧹 Nitpick | 🔵 Trivial

Duplicate cardTotal calculation; consider extracting.

The same cardTotal reduction appears at lines 100-103 and 125-127. Pre-compute card totals once to avoid duplication and improve maintainability.

♻️ Suggested refactor
+  const cardsWithTotals = cards.map((card) => ({
+    ...card,
+    total: card.purchases.reduce((s, p) => s + p.installments.reduce((a, i) => a + i.amount, 0), 0),
+  }));
+  const totalSpent = cardsWithTotals.reduce((sum, card) => sum + card.total, 0);
-  const totalSpent = cards.reduce(
-    (sum, card) =>
-      sum +
-      card.purchases.reduce((s, p) => s + p.installments.reduce((a, installment) => a + installment.amount, 0), 0),
-    0,
-  );

Then use cardsWithTotals in both the summary and per-card sections, referencing card.total directly.

Also applies to: 124-128


105-105: ⚠️ Potential issue | 🟡 Minor

Consider a more unique React key.

Using card.lastFour as the key could cause issues if two cards share the same last four digits. Since the component doesn't receive a unique card ID, consider using the array index as a fallback:

-<View key={card.lastFour} style={styles.summaryRow}>
+<View key={`${card.lastFour}-${index}`} style={styles.summaryRow}>

Apply similarly at line 130.

Also applies to: 130-130


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 85ea069e-c24d-4dbc-919a-71cb4c2744bf

📥 Commits

Reviewing files that changed from the base of the PR and between cb5448d and ec562a5.

📒 Files selected for processing (5)
  • .changeset/soft-trains-dig.md
  • server/api/activity.ts
  • server/test/api/activity.test.ts
  • server/test/utils/statement.test.ts
  • server/utils/Statement.tsx

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
server/utils/Statement.tsx (1)

99-128: 🧹 Nitpick | 🔵 Trivial

Duplicate cardTotal calculation.

The same calculation appears at lines 100-103 (Summary section) and lines 125-128 (per-card section). Consider computing card totals once during initial data processing.

Additionally, using card.lastFour as the React key (lines 105, 130) could cause issues if two cards share the same last four digits.

♻️ Suggested refactor
+  const cardsWithTotals = cards.map((card) => ({
+    ...card,
+    total: card.purchases.reduce((s, p) => s + p.installments.reduce((a, i) => a + i.amount, 0), 0),
+  }));
+  const totalSpent = cardsWithTotals.reduce((sum, card) => sum + card.total, 0);
-  const totalSpent = cards.reduce(
-    (sum, card) =>
-      sum +
-      card.purchases.reduce((s, p) => s + p.installments.reduce((a, installment) => a + installment.amount, 0), 0),
-    0,
-  );

Then reference cardsWithTotals and card.total directly in both sections.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 17e1f6dc-adbd-449f-9f30-770174c13e19

📥 Commits

Reviewing files that changed from the base of the PR and between ec562a5 and a733910.

📒 Files selected for processing (5)
  • .changeset/soft-trains-dig.md
  • server/api/activity.ts
  • server/test/api/activity.test.ts
  • server/test/utils/statement.test.ts
  • server/utils/Statement.tsx

@cruzdanilo cruzdanilo marked this pull request as ready for review March 18, 2026 17:25
@cruzdanilo cruzdanilo self-requested a review as a code owner March 18, 2026 17:25
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d85aefd0f2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +516 to +518
cards: purchases.map(({ id, lastFour }) => ({
lastFour,
purchases: (purchasesByCard.get(id) ?? []).map(({ cardId: _, ...rest }) => rest),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter out cards without matching purchases

In the maturity/PDF path, purchases is populated from a card query whose relation filter only trims each card's transactions; it does not remove parent cards with no matching activity. Because this mapper renders every returned card, any account with multiple cards but purchases on only one of them gets extra Card **** … purchases sections and zero-dollar summary rows for the idle cards, which makes the statement misleading and unnecessarily long.

Useful? React with 👍 / 👎.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

0,
);
return (
<View key={card.lastFour} style={styles.summaryRow}>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 React key collision using non-unique card.lastFour in Statement component

The Statement component uses card.lastFour as the React key in two separate .map() calls (lines 105 and 130). The lastFour field is not guaranteed to be unique — two cards can share the same last four digits (e.g., a reissued card, or cards from different issuers). When keys collide, @react-pdf/renderer's reconciler will silently drop or merge sibling elements, causing card sections and summary rows to be missing or incorrect in the generated PDF.

The root cause is that server/api/activity.ts:516-518 has access to the unique card id but only passes lastFour to the Statement component, making it impossible to use a unique key downstream.

Prompt for agents
In server/api/activity.ts at line 516-519, pass through the card id alongside lastFour:

cards: purchases.map(({ id, lastFour }) => ({
  id,
  lastFour,
  purchases: (purchasesByCard.get(id) ?? []).map(({ cardId: _, ...rest }) => rest),
})),

Then update server/utils/Statement.tsx to include `id: string` in the cards type definition (around line 13-21), and use `card.id` instead of `card.lastFour` as the React key at lines 105 and 130:

  <View key={card.id} style={styles.summaryRow}>   (line 105)
  <View key={card.id} style={styles.section}>      (line 130)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +516 to +519
cards: purchases.map(({ id, lastFour }) => ({
lastFour,
purchases: (purchasesByCard.get(id) ?? []).map(({ cardId: _, ...rest }) => rest),
})),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Cards with zero purchases still rendered in the PDF statement

When purchases is built via the database query (server/api/activity.ts:273-282), ALL cards for the credential are returned regardless of whether they have matching transactions. Cards with no purchases for the maturity period will still appear in the PDF as empty sections with $0.00 totals in both the summary and detail views. The old code only tracked cards that actually had matching transactions (statementCards was derived from transaction card IDs). This is likely a deliberate design choice for the new multi-card layout, but could produce noisy PDFs if a user has many cards but only uses one.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +516 to +519
cards: purchases.map(({ id, lastFour }) => ({
lastFour,
purchases: (purchasesByCard.get(id) ?? []).map(({ cardId: _, ...rest }) => rest),
})),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Cards with no purchases for the maturity period appear as empty $0.00 sections in the PDF statement

The query at server/api/activity.ts:273-282 fetches ALL cards for the credential via eq(cards.credentialId, credentialId). The nested where: arrayOverlaps(transactions.hashes, hashes) only filters the child transactions, not the parent cards. Cards that have no matching transactions are returned with transactions: []. These empty cards flow unfiltered into the statement at line 516-519 and appear in the PDF as empty card sections with $0.00 totals and table headers with no rows. The old code derived the card list from actual matching transactions (statementCards = [...new Set(statementTransactions.map(({ cardId }) => cardId))] at the old line 283), which naturally excluded cards without purchases.

Suggested change
cards: purchases.map(({ id, lastFour }) => ({
lastFour,
purchases: (purchasesByCard.get(id) ?? []).map(({ cardId: _, ...rest }) => rest),
})),
cards: purchases
.filter(({ id }) => purchasesByCard.has(id))
.map(({ id, lastFour }) => ({
lastFour,
purchases: (purchasesByCard.get(id) ?? []).map(({ cardId: _, ...rest }) => rest),
})),
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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