Skip to content

feat(card): show settlement adjustments + next-deposit deduction banner#2459

Merged
abalinda merged 4 commits into
devfrom
feat/card-settlement-adjust-visibility
Jul 23, 2026
Merged

feat(card): show settlement adjustments + next-deposit deduction banner#2459
abalinda merged 4 commits into
devfrom
feat/card-settlement-adjust-visibility

Conversation

@abalinda

@abalinda abalinda commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

A restaurant tip settled +$9.18 above its auth (auth $45.91 → capture $55.09, four days later) and silently overdrew a user's card collateral to −$6.31; her next deposit repaid it invisibly and she reported "my deposit was not fully credited" (2026-07-21, user juanacervio). The FE clamped negative spending power to $0 and buried the adjustment inside a week-old receipt — nothing anywhere explained the balance drop.

This is the FE half of the fix (paired BE PR: peanut-api-ts feat/card-settlement-adjust-visibility):

  • Receipt: Initial hold + Adjustment breakdown rows, plus a visible CardAdjustmentNotice info card below the details card (over-captures only) — "The final amount was $X higher than the initial hold. This is common with tips and updated totals. Don't recognize it? Contact the merchant."
  • Feed: · Adjusted flag on settlement-adjusted card rows (refunds excluded).
  • Card screen: \$X will be debited based on your next deposit warning banner derived from negative Rain spendingPower (previously silently clamped to $0).

Risks / breaking changes

  • None breaking. Renders purely from fields the prod BE already serializes (cardAuthAmount / cardSettledAmount / settlementAdjusted / unclamped spendingPower) — safe to merge before the BE PR; FE-first is the recommended order (first adjusted push then deep-links into a receipt that already shows the breakdown).
  • Banner can display transiently while an in-transit auto-balance top-up is about to repay the debt (≤10 min, self-resolving).
  • Known pre-existing nit (BE side, not addressed here): settlementAdjusted stays true if a later re-settle returns to exactly the auth amount — receipt self-heals (delta row and notice both hidden), feed flag would still show.

Design notes / accepted trade-offs

Revised after design review (2026-07-23) — the first cut used "Card balance due" + an info-icon tooltip; both were rejected:

  • Event framing, not debt framing. "Card balance due" imported credit-card-bill semantics into a prepaid mental model (deposit → spend), and warned without offering an action. The banner now narrates the observable consequence — "$X will be debited based on your next deposit" — in checkout vocabulary ("amount held at checkout"), not auth/capture jargon.
  • Explanation is visible, not behind a tooltip. The merchant-recourse sentence is the receipt's only action; info-icon tooltips go mostly unopened (and don't announce themselves on mobile). It now renders as a standard notice card (same pattern as LocalRailNudge / CardUsdAbroadNotice), over-captures only — under-captures return money and need no warning.
  • "Initial hold" over "Authorized" — the one card-hold term consumers already know (hotels, gas stations).
  • Copy names example causes ("tips and updated totals") without asserting one — Rain doesn't report why capture ≠ auth (tip / FX true-up / incidentals look identical).
  • Home total keeps showing spendable (clamped) — netting the debt into the balance would be wrong (it collects from the next deposit; today's balance is genuinely spendable), so the card screen banner is where the debt is explained. Every spend/withdraw path already clamps ≤0 → 0.

Screenshots

Card screen — deposit-deduction banner Receipt — breakdown + visible notice Feed — Adjusted flag

Captured on the local sandbox stack (seeded $45.91 → $55.09 settle, user abtest; banner from the incident's −$6.31 spending power). Assets live on the pr-assets-2459 branch — delete after merge.

QA

  • Unit: 97 tests on touched suites — receipt breakdown rows, CardAdjustmentNotice gating (over-capture only, refund/equal/missing-settled excluded) incl. exact copy with the incident's $9.18, feed flag incl. refund exclusion, cardBalanceDueCents edges with the incident's −631. Full suite green except routes.test.ts, which fails only on machines carrying the local-only dev/negative-demo scaffold (git-excluded, not in the PR).
  • Visual: local stack with seeded settles on a test account — normal $68.31 row unchanged; 45.91→55.09 row shows the flag, breakdown, and notice.
  • Adversarial review: 3 independent subagent passes on the first cut (verdict SHIP), then a 4-point design review drove this revision (banner reframe, hold vocabulary, tooltip → visible notice).

A restaurant tip settled $9.18 above its auth and silently overdrew the
user's collateral to -$6.31; her next deposit repaid it invisibly and she
reported 'my deposit was not fully credited' (2026-07-21). The FE clamped
negative spending power to $0 and buried the adjustment in a week-old
receipt, so nothing anywhere explained the balance drop.

- receipt: Authorized + Adjustment breakdown (cause-neutral copy — Rain
  does not say whether it was a tip, FX true-up, or incidentals)
- feed: 'Adjusted' flag on settlement-adjusted card rows
- card screen: 'Card balance due' banner from negative spendingPower
  instead of the silent $0 clamp
@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jul 23, 2026 2:14pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

YourCardScreen now shows card debt warnings from negative spending power. Transaction details identify adjusted non-refund card payments, while payment rows explain authorized and settled amount differences with adjustment values and tooltips.

Changes

Card balance and settlement adjustments

Layer / File(s) Summary
Balance due calculation and warning
src/utils/balance.utils.ts, src/utils/__tests__/balance.utils.test.ts, src/components/Card/YourCardScreen.tsx
Adds cardBalanceDueCents and conditionally displays a warning when card spending power indicates an amount due.
Adjusted spend indicator
src/components/TransactionDetails/TransactionCard.tsx, src/components/TransactionDetails/__tests__/TransactionCard.test.tsx
Labels non-refund card payments with settlement adjustments as · Adjusted, with presence and exclusion tests.
Settlement adjustment breakdown
src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx, src/components/TransactionDetails/provider-rows/__tests__/CardPaymentRows.settlement-adjustment.test.tsx
Displays authorized and adjustment rows with explanatory text for over-capture, partial capture, and missing settled amounts.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: jjramirezn, kushagrasarathe

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the two main changes: settlement adjustments and the new card balance due banner.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/card-settlement-adjust-visibility

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

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6231.29 → 6240.07 (+8.78)
Findings: +3 net (+25 new, -22 resolved)

🆕 New findings (25)

  • critical complexity — src/components/TransactionDetails/TransactionDetailsReceipt.tsx — CC 161, MI 52.87, SLOC 352
  • critical method-complexity — src/components/TransactionDetails/TransactionDetailsReceipt.tsx:75 — CC 109 SLOC 170
  • critical complexity — src/components/TransactionDetails/TransactionCard.tsx — CC 55, MI 50.06, SLOC 167
  • critical complexity — src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx — CC 52, MI 49.27, SLOC 131
  • high method-complexity — src/components/TransactionDetails/TransactionCard.tsx:65 — CC 43 SLOC 92
  • high hotspot — src/components/TransactionDetails/TransactionDetailsReceipt.tsx — 40 commits, +475/-1073 lines since 6 months ago
  • medium high-mdd — src/components/TransactionDetails/TransactionDetailsReceipt.tsx:75 — TransactionDetailsReceipt: MDD 177.7 (uses across many lines from declarations)
  • medium high-dlt — src/components/TransactionDetails/TransactionDetailsReceipt.tsx:75 — TransactionDetailsReceipt: DLT 81 (calls 81 distinct functions — high context load)
  • medium high-mdd — src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx:101 — CardPaymentRows: MDD 61.1 (uses across many lines from declarations)
  • medium high-mdd — src/components/TransactionDetails/TransactionCard.tsx:65 — TransactionCard: MDD 59.7 (uses across many lines from declarations)
  • medium high-mdd — src/components/Card/YourCardScreen.tsx:30 — YourCardScreen: MDD 52.1 (uses across many lines from declarations)
  • medium hotspot — src/components/TransactionDetails/TransactionCard.tsx — 28 commits, +222/-150 lines since 6 months ago
  • medium complexity — src/utils/balance.utils.ts — CC 28, MI 62.21, SLOC 80
  • medium method-complexity — src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx:101 — CardPaymentRows CC 24 SLOC 81
  • medium complexity — src/components/Card/YourCardScreen.tsx — CC 23, MI 62.37, SLOC 55
  • medium complexity — src/components/TransactionDetails/transaction-details.utils.ts — CC 20, MI 64.2, SLOC 57
  • medium complexity — src/components/TransactionDetails/provider-rows/CardAdjustmentNotice.tsx — CC 7, MI 63.73, SLOC 16
  • low high-dlt — src/components/TransactionDetails/TransactionCard.tsx:65 — TransactionCard: DLT 29 (calls 29 distinct functions — high context load)
  • low high-dlt — src/components/Card/YourCardScreen.tsx:30 — YourCardScreen: DLT 18 (calls 18 distinct functions — high context load)
  • low structural-dup — components/Send/link/views/Success.link.send.view.tsx:102 — 14 duplicate lines / 73 tokens with components/TransactionDetails/TransactionDetailsReceipt.tsx:837

…and 5 more.

✅ Resolved (22)

  • src/components/TransactionDetails/TransactionDetailsReceipt.tsx — CC 160, MI 52.89, SLOC 352
  • src/components/TransactionDetails/TransactionDetailsReceipt.tsx:74 — CC 108 SLOC 170
  • src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx — CC 52, MI 51.55, SLOC 128
  • src/components/TransactionDetails/TransactionCard.tsx — CC 52, MI 50.35, SLOC 164
  • src/components/TransactionDetails/TransactionCard.tsx:65 — CC 40 SLOC 89
  • src/components/TransactionDetails/TransactionDetailsReceipt.tsx — 39 commits, +468/-1073 lines since 6 months ago
  • src/components/TransactionDetails/TransactionDetailsReceipt.tsx:74 — TransactionDetailsReceipt: MDD 176.5 (uses across many lines from declarations)
  • src/components/TransactionDetails/TransactionDetailsReceipt.tsx:74 — TransactionDetailsReceipt: DLT 81 (calls 81 distinct functions — high context load)
  • src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx:109 — CardPaymentRows: MDD 62.9 (uses across many lines from declarations)
  • src/components/TransactionDetails/TransactionCard.tsx:65 — TransactionCard: MDD 55.2 (uses across many lines from declarations)
  • src/components/Card/YourCardScreen.tsx:29 — YourCardScreen: MDD 47.6 (uses across many lines from declarations)
  • src/components/TransactionDetails/TransactionCard.tsx — 27 commits, +211/-150 lines since 6 months ago
  • src/utils/balance.utils.ts — CC 24, MI 62.32, SLOC 73
  • src/components/Card/YourCardScreen.tsx — CC 22, MI 63.55, SLOC 52
  • src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx:109 — CardPaymentRows CC 21 SLOC 71
  • src/components/TransactionDetails/transaction-details.utils.ts — CC 17, MI 64.41, SLOC 50
  • src/components/TransactionDetails/TransactionCard.tsx:65 — TransactionCard: DLT 28 (calls 28 distinct functions — high context load)
  • src/components/Card/YourCardScreen.tsx:29 — YourCardScreen: DLT 16 (calls 16 distinct functions — high context load)
  • components/Send/link/views/Success.link.send.view.tsx:102 — 14 duplicate lines / 73 tokens with components/TransactionDetails/TransactionDetailsReceipt.tsx:830
  • src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx:109 — CardPaymentRows: exported fn missing return type annotation

…and 2 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/components/TransactionDetails/provider-rows/CardAdjustmentNotice.tsx 0.0 6.0 +6.0
src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx 11.8 12.8 +1.0
src/components/Card/YourCardScreen.tsx 7.7 8.4 +0.8

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2039 ran, 0 failed, 0 skipped, 31.6s

📊 Coverage (unit)

metric %
statements 60.1%
branches 43.9%
functions 49.1%
lines 60.4%
⏱ 10 slowest test cases
time test
3.4s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.0s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.4s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.4s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.2s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.2s src/components/Badges/__tests__/useBadgeEarnToast.test.ts › does not resurface badges already in the seen-set
0.2s src/utils/__tests__/demo-balance.test.ts › starts at the full balance on a fresh install and stamps a timestamp
0.2s src/utils/__tests__/demo-balance.test.ts › debits and floors at zero
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@abalinda

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx`:
- Around line 34-35: The condition in CardPaymentRows must not show “differed”
copy when settledCents equals authCents. Split the equality case from the
null-settlement case, returning appropriate unchanged/no-adjustment behavior for
equal amounts while preserving the existing explanation for an actual settlement
difference.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b6e4a675-113d-4c9c-92d7-d653d0e7a70f

📥 Commits

Reviewing files that changed from the base of the PR and between 2caa1af and 693b780.

📒 Files selected for processing (7)
  • src/components/Card/YourCardScreen.tsx
  • src/components/TransactionDetails/TransactionCard.tsx
  • src/components/TransactionDetails/__tests__/TransactionCard.test.tsx
  • src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx
  • src/components/TransactionDetails/provider-rows/__tests__/CardPaymentRows.settlement-adjustment.test.tsx
  • src/utils/__tests__/balance.utils.test.ts
  • src/utils/balance.utils.ts

Comment thread src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx Outdated
A re-settle back to exactly the auth amount leaves settlementAdjusted
stuck true; the tooltip claimed the charge 'differed' while the receipt
showed equal amounts. Split the copy: matched vs unknown vs delta.
@abalinda
abalinda marked this pull request as ready for review July 21, 2026 14:27
@abalinda
abalinda requested review from Hugo0 and Copilot July 21, 2026 14:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@abalinda
abalinda requested a review from jjramirezn July 21, 2026 14:36
- Banner: "$X will come off your next deposit" event framing instead of
  "Card balance due" — a prepaid card must not grow a credit-card bill,
  and "settled above its authorized amount" was auth/capture jargon the
  affected users don't have. Copy names example causes (tips, updated
  totals) without asserting one; Rain doesn't report the cause.
- Receipt: "Authorized" row renamed "Initial hold" (the one hold term
  consumers already know) and the explanation moved from the row's
  info-icon tooltip to a visible CardAdjustmentNotice below the details
  card — the merchant-recourse sentence is the receipt's only action, so
  it can't hide behind a tap most users never make. Over-captures only;
  under-captures return money and need no warning.
- parseCents moved to transaction-details.utils, shared by rows + notice.
@abalinda abalinda changed the title feat(card): show settlement adjustments + card balance due feat(card): show settlement adjustments + next-deposit deduction banner Jul 23, 2026

@kushagrasarathe kushagrasarathe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

APPROVE

Clean FE half of the settlement-visibility pair. Renders purely from fields prod BE already serializes, so it's safe to merge in either order (FE-first preferred).

  • Proper reuse: parseCents extracted into shared transaction-details.utils.ts and consumed by both CardPaymentRows and CardAdjustmentNotice — de-dup, not copy.
  • Defensive throughout: every gate rejects NaN / missing / ≤0 and self-heals the known settlementAdjusted-stuck-true case (equal amounts → no Adjustment row, notice hidden). cardBalanceDueCents handles null/undefined/NaN/-Inf → 0.
  • Prop-contract verified safe: overview was already required and passed (card/page.tsxoverview={overview!}, state-machine-guarded); overview.balance?.spendingPower + the helper defend against a missing balance.
  • Display-only, no security surface.

Non-blocking cleanup: inline $${(cents/100).toFixed(2)} money formatting is repeated ~4× (banner, notice, both breakdown rows) and the over-capture gate is duplicated between CardAdjustmentNotice and CardPaymentRows — a shared formatCentsUsd + a small over-capture helper would consolidate it.

Minor acknowledged asymmetry: the feed · Adjusted flag doesn't self-heal on revert-to-auth the way the receipt does.

CI green apart from the pre-existing repo-wide no-explicit-any eslint failure (unrelated to this PR).

@abalinda
abalinda merged commit 10ebd4f into dev Jul 23, 2026
18 of 20 checks passed
@abalinda

Copy link
Copy Markdown
Contributor Author

mistakenly merged this one instead of another one, reopened pr at #2497

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.

3 participants