import type { Money } from 'fhir/r4'; /** * Money is handled as integer minor units (cents) EVERYWHERE in this codebase. * FHIR `Money.value` is a decimal and Stripe wants integer minor units, so decimal is a * serialization detail - this module is the ONLY place conversion is allowed to happen. * * Why this exists: AddOnsPage.tsx:84 does float arithmetic on whole dollars. That bug class * must never reach money a patient actually pays. */ export const DEFAULT_CURRENCY = 'USD'; /** Formats integer cents for display. 28500 -> "$285.00" */ export const formatMoney = (cents: number, currency: string = DEFAULT_CURRENCY): string => new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(cents / 100); /** Converts integer cents to a FHIR Money. */ export const toFhirMoney = (cents: number, currency: string = DEFAULT_CURRENCY): Money => ({ value: cents / 100, currency, }); /** * Reads a FHIR Money back to integer cents. * Rounds, because `value * 100` carries float representation error (19.99 * 100 === 1998.9999999999998). * Returns 0 for absent/invalid input rather than propagating NaN into an amount. */ export const fromFhirMoney = (money?: Money): number => { if (typeof money?.value !== 'number' || !Number.isFinite(money.value)) { return 0; } return Math.round(money.value * 100); }; /** Sums integer cents. Exact by construction - this is the point of cents. */ export const sumCents = (values: number[]): number => values.reduce((total, value) => total + value, 0); /** * Computes sales tax in integer cents from a taxable subtotal (also integer cents) and a percent rate * (e.g. 8.25 for 8.25%). Rounded to the nearest cent - same convention as fromFhirMoney/parseDollarsToCents. * A 0 rate (or a 0 taxable subtotal) always returns exactly 0. */ export const computeTax = (taxableSubtotalCents: number, ratePercent: number): number => Math.round((taxableSubtotalCents * ratePercent) / 100); /** Parses user-entered dollar text ("$1,285.50") to integer cents. Undefined if not a number. */ export const parseDollarsToCents = (input: string): number | undefined => { const cleaned = input.replace(/[$,\s]/g, ''); if (cleaned === '' || !/^-?\d*\.?\d*$/.test(cleaned)) { return undefined; } const parsed = Number(cleaned); if (!Number.isFinite(parsed)) { return undefined; } return Math.round(parsed * 100); };