Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 1 addition & 12 deletions src/components/Card/YourCardScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import LockCardModal from '@/components/Card/LockCardModal'
import { shouldShowAutoRenewBanner, daysUntilExpiry } from '@/components/Card/cardExpiry.utils'
import { useCardReveal } from '@/hooks/useCardReveal'
import { useWalletPlatform } from '@/hooks/useWalletPlatform'
import { cardBalanceDueCents } from '@/utils/balance.utils'
import { copyTextToClipboardWithFallback } from '@/utils/general.utils'
import type { RainCardOverview, RainCardSummary } from '@/services/rain'

Expand All @@ -27,7 +26,7 @@ interface Props {
onPrev?: () => void
}

const YourCardScreen: FC<Props> = ({ overview, card, onPrev }) => {
const YourCardScreen: FC<Props> = ({ card, onPrev }) => {
const [autoRenewDismissed, setAutoRenewDismissed] = useState(false)
const [action, setAction] = useQueryState('action', parseAsStringEnum<CardAction>(['lock', 'unlock', 'cancel']))
const { revealed, isLoading: isRevealing, error: revealError, toggle } = useCardReveal({ cardId: card.id })
Expand All @@ -41,7 +40,6 @@ const YourCardScreen: FC<Props> = ({ overview, card, onPrev }) => {
const closeAction = () => void setAction(null)
const showAutoRenew = !autoRenewDismissed && shouldShowAutoRenewBanner(card.expiryMonth, card.expiryYear)
const daysLeft = daysUntilExpiry(card.expiryMonth, card.expiryYear)
const balanceDueCents = cardBalanceDueCents(overview.balance?.spendingPower)

const handleCopy = useCallback(
(value: string, field: 'pan' | 'cvv') => {
Expand Down Expand Up @@ -90,15 +88,6 @@ const YourCardScreen: FC<Props> = ({ overview, card, onPrev }) => {
</div>
)}

{balanceDueCents > 0 && (
<InfoCard
variant="warning"
icon="credit-card"
title={`$${(balanceDueCents / 100).toFixed(2)} will be debited based on your next deposit`}
description="A recent card payment ended up higher than the amount held at checkout. This happens with tips or updated totals. We'll cover the difference automatically."
/>
)}

<InfoCard
variant="info"
icon="credit-card"
Expand Down
11 changes: 0 additions & 11 deletions src/components/TransactionDetails/TransactionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,6 @@ const TransactionCard: React.FC<TransactionCardProps> = ({
const isDeclinedCardSpend =
status === 'failed' && isCardPaymentEntry(transaction) && !transaction.extraDataForDrawer?.cardPayment?.isRefund

// Settlement cleared at a different amount than authorized (tip / FX
// true-up) — flag the row so the balance impact isn't invisible in the
// feed; the receipt carries the authorized/adjustment breakdown. Refunds
// excluded like isDeclinedCardSpend above — a refund-auth that clears at
// a different amount would otherwise render "Refund · Adjusted".
const isAdjustedCardSpend =
isCardPaymentEntry(transaction) &&
Boolean(transaction.extraDataForDrawer?.cardPayment?.settlementAdjusted) &&
!transaction.extraDataForDrawer?.cardPayment?.isRefund

return (
<>
{/* the clickable card */}
Expand Down Expand Up @@ -254,7 +244,6 @@ const TransactionCard: React.FC<TransactionCardProps> = ({
: getActionText(type, status)}
</span>
{status && <StatusPill status={status} />}
{isAdjustedCardSpend && <span>· Adjusted</span>}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ import { buildSplitBillRequestUrl } from './splitBill.utils'
import { CardPaymentRows } from './provider-rows/CardPaymentRows'
import { LocalRailNudge } from './provider-rows/LocalRailNudge'
import { CardUsdAbroadNotice } from './provider-rows/CardUsdAbroadNotice'
import { CardAdjustmentNotice } from './provider-rows/CardAdjustmentNotice'
import { MantecaDepositInfo } from './provider-rows/MantecaDepositInfo'
import { BridgeDepositInstructions } from './provider-rows/BridgeDepositInstructions'
import { CancelDepositActions } from './provider-actions/CancelDepositActions'
Expand Down Expand Up @@ -639,12 +638,6 @@ export const TransactionDetailsReceipt = ({
</div>
</Card>

{/* Over-capture explainer — the words for the Initial hold /
Adjustment rows in the details card and the merchant-recourse
path. First of the notices: it explains THIS receipt's numbers;
the others nudge future behavior. */}
{!isPublic && <CardAdjustmentNotice transaction={transaction} />}

{/* Local-rail nudge — card spends in a country with a cheaper
first-party rail (AR → QR, BR → Pix). Self-gates: renders
nothing for other countries / non-card-spend transactions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,35 +130,3 @@ describe('TransactionCard — clickable counterparty name', () => {
expect(openTransactionDetails).toHaveBeenCalledTimes(1)
})
})

/** A Rain card spend row; `cardPayment` overrides shape the flag cases. */
function cardSpendTx(cardPayment: Record<string, unknown>): TransactionDetails {
const tx = eligibleTx()
;(tx.extraDataForDrawer as Record<string, unknown>).cardPayment = {
merchantName: 'Savannah Taphouse',
isRefund: false,
settlementAdjusted: false,
...cardPayment,
}
return tx
}

// The '· Adjusted' feed flag — settlement cleared at a different amount than
// authorized. Refunds are excluded even when the BE forwards the flag on a
// negative-auth refund clear (they'd read "Refund · Adjusted" otherwise).
describe('TransactionCard — settlement-adjusted flag', () => {
it('shows · Adjusted for an adjusted card spend', () => {
renderCard(cardSpendTx({ settlementAdjusted: true }))
expect(screen.getByText('· Adjusted')).toBeInTheDocument()
})

it('hides it for a non-adjusted card spend', () => {
renderCard(cardSpendTx({ settlementAdjusted: false }))
expect(screen.queryByText('· Adjusted')).not.toBeInTheDocument()
})

it('hides it for an adjusted card REFUND', () => {
renderCard(cardSpendTx({ settlementAdjusted: true, isRefund: true }))
expect(screen.queryByText('· Adjusted')).not.toBeInTheDocument()
})
})

This file was deleted.

29 changes: 12 additions & 17 deletions src/components/TransactionDetails/provider-rows/CardPaymentRows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { PaymentInfoRow } from '@/components/Payment/PaymentInfoRow'
import { type DisputeStatus, type TransactionDetails } from '@/components/TransactionDetails/transactionTransformer'
import { friendlyDeclineReason } from '@/utils/cardDeclineReason'
import { getFlagUrl } from '@/constants/countryCurrencyMapping'
import { extractMerchantIso2, parseCents } from '@/components/TransactionDetails/transaction-details.utils'
import { extractMerchantIso2 } from '@/components/TransactionDetails/transaction-details.utils'

/** Strings from Rain's sandbox arrive whitespace-padded (" ", " - ") and
* legacy intents in the DB pre-date the backend cleanField pass — treat any
Expand All @@ -19,6 +19,14 @@ function nonBlank(value: string | null | undefined): string | null {
return trimmed
}

/** Parse a cents amount and reject NaN / Infinity / null up-front so the
* drawer never renders "Charged in NaN EUR". */
function parseCents(value: string | null | undefined): number | null {
if (value == null) return null
const n = Number(value)
return Number.isFinite(n) ? n : null
}

/**
* Friendly copy for the dispute status row. The drawer shows ONE row labeled
* "Dispute" with the status-mapped text. Keep terminal-state copy actionable:
Expand Down Expand Up @@ -142,28 +150,15 @@ export function CardPaymentRows({
}

// Spec §4.6 — settled amount differs from the original auth (overcapture,
// tip, partial capture). Break out the hold vs the delta so the math is
// explicit. "Initial hold" over "Authorized": hold is the one card term
// users already know (hotels, gas stations). The words — examples and the
// merchant-recourse path — live in CardAdjustmentNotice below the details
// card, visible instead of behind an info-icon tooltip nobody opens.
// tip, partial capture). Show the original auth amount as a hint.
if (card.settlementAdjusted) {
const authCents = parseCents(card.authAmount)
const settledCents = parseCents(card.settledAmount)
if (authCents != null) {
subRows.push({
key: 'initialHold',
label: 'Initial hold',
key: 'adjustedFrom',
label: 'Original amount',
value: `$${(authCents / 100).toFixed(2)}`,
})
if (settledCents != null && settledCents !== authCents) {
const deltaCents = settledCents - authCents
subRows.push({
key: 'settlementAdjustment',
label: 'Adjustment',
value: `${deltaCents > 0 ? '+' : '-'}$${(Math.abs(deltaCents) / 100).toFixed(2)}`,
})
}
}
}

Expand Down

This file was deleted.

Loading
Loading