'use client'; import { FunctionComponent } from 'react'; import { useLocale } from 'next-intl'; import { Divider, Stack, Typography } from '@mui/material'; import Money from '@/components/common/Money'; import SurfaceCard from '@/components/common/SurfaceCard'; import { formatIrrToToman, parseIrr } from '@/utils'; export interface PriceBreakdownRow { /** Stable row key (e.g. `service_cost`) — also exposed as `data-row` for tests/automation. */ key: string; /** Display label — already translated by the caller (labels are i18n keys, never derived from codes). */ label: string; /** IRR digit-string, straight off the wire. */ amountIrr: string; } export interface PriceBreakdownProps { rows: PriceBreakdownRow[]; totalLabel: string; /** IRR digit-string. Must equal the integer sum of `rows` — see the reconciliation guard below. */ totalAmountIrr: string; } /** * The reconciling money breakdown (C6 checkout, invoice; f10/f11 reuse it for refunds/BNPL). Rows and * total are **served amounts** — this component only formats (BigInt-safe, Toman display via the money * util) and never computes a figure. The one thing it enforces is the phase's hard rule: the displayed * rows must sum to the displayed total. A mismatch is a data bug upstream, so it is surfaced loudly in * dev (console.error) rather than silently rendered. * @component PriceBreakdown */ const PriceBreakdown: FunctionComponent = ({ rows, totalLabel, totalAmountIrr }) => { const locale = useLocale(); if (process.env.NODE_ENV !== 'production') { const sum = rows.reduce((acc, row) => acc + parseIrr(row.amountIrr), BigInt(0)); if (sum !== parseIrr(totalAmountIrr)) { console.error( `PriceBreakdown: rows sum to ${sum} but total is ${totalAmountIrr} — a breakdown must reconcile to the rial.`, ); } } return ( {rows.map((row) => ( {row.label} {formatIrrToToman(row.amountIrr, locale)} ))} {totalLabel} ); }; export default PriceBreakdown;