feat(card): show settlement adjustments + next-deposit deduction banner#2459
Conversation
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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthrough
ChangesCard balance and settlement adjustments
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Code-analysis diffPainscore total: 6231.29 → 6240.07 (+8.78) 🆕 New findings (25)
…and 5 more. ✅ Resolved (22)
…and 2 more. 📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
src/components/Card/YourCardScreen.tsxsrc/components/TransactionDetails/TransactionCard.tsxsrc/components/TransactionDetails/__tests__/TransactionCard.test.tsxsrc/components/TransactionDetails/provider-rows/CardPaymentRows.tsxsrc/components/TransactionDetails/provider-rows/__tests__/CardPaymentRows.settlement-adjustment.test.tsxsrc/utils/__tests__/balance.utils.test.tssrc/utils/balance.utils.ts
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.
- 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.
… banner, 'final amount' in adjustment notice
kushagrasarathe
left a comment
There was a problem hiding this comment.
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:
parseCentsextracted into sharedtransaction-details.utils.tsand consumed by bothCardPaymentRowsandCardAdjustmentNotice— 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).cardBalanceDueCentshandles null/undefined/NaN/-Inf → 0. - Prop-contract verified safe:
overviewwas already required and passed (card/page.tsx→overview={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).
|
mistakenly merged this one instead of another one, reopened pr at #2497 |
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):Initial hold+Adjustmentbreakdown rows, plus a visibleCardAdjustmentNoticeinfo 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."· Adjustedflag on settlement-adjusted card rows (refunds excluded).\$X will be debited based on your next depositwarning banner derived from negative RainspendingPower(previously silently clamped to $0).Risks / breaking changes
cardAuthAmount/cardSettledAmount/settlementAdjusted/ unclampedspendingPower) — 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).settlementAdjustedstays 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:
LocalRailNudge/CardUsdAbroadNotice), over-captures only — under-captures return money and need no warning.Screenshots
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 thepr-assets-2459branch — delete after merge.QA
CardAdjustmentNoticegating (over-capture only, refund/equal/missing-settled excluded) incl. exact copy with the incident's $9.18, feed flag incl. refund exclusion,cardBalanceDueCentsedges with the incident's −631. Full suite green exceptroutes.test.ts, which fails only on machines carrying the local-onlydev/negative-demoscaffold (git-excluded, not in the PR).