# Currency handling is consistent

- **Pattern:** `ab-002284` (`saas-billing.financial-data.currency-handling`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002284
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002284)

## Why it matters

Floating-point arithmetic on monetary amounts produces silent rounding errors — `0.1 + 0.2` is `0.30000000000000004` in JavaScript. When tax, discounts, or proration are calculated using float dollars instead of integer cents, the errors compound across transactions. CWE-681 (Incorrect Conversion Between Numeric Types) covers this, and ISO 25010 functional-correctness requires that calculations produce accurate results. Formatting currency with string concatenation (`"$" + amount`) produces locale-incorrect output (wrong decimal separators, missing grouping) and breaks for non-USD currencies.

## Severity rationale

Low because floating-point rounding errors in typical SaaS pricing are small per transaction, but compound at scale and can produce incorrect invoice totals.

## Remediation

Store all monetary amounts as integers in the smallest currency unit (cents for USD). Use `Intl.NumberFormat` for display and integer arithmetic for calculations.

```ts
// Store amounts as cents (integer)
const amountCents = 2999 // $29.99 — never store as 29.99

// Display with locale-aware formatting
const display = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
}).format(amountCents / 100) // "$29.99"

// Arithmetic stays in integer cents
const taxCents = Math.round(amountCents * 0.08) // 240 — not 2.392...
const totalCents = amountCents + taxCents        // 3239 — exact
```

Stripe's API returns amounts in the smallest currency unit by default — work in those units end-to-end and only convert to display format at the UI layer.

## Detection

- **ID:** `currency-handling`
- **Severity:** `low`
- **What to look for:** Check how currency amounts are handled throughout the codebase. Look for: (a) amounts stored as integers in the smallest unit (cents for USD, pence for GBP) vs. floats or decimals, (b) currency display formatting (is `Intl.NumberFormat` or a formatting library used, or are amounts formatted with string concatenation), (c) whether the currency is hardcoded or configurable, (d) any arithmetic on monetary amounts (addition, multiplication for tax, etc.) that could introduce floating-point precision errors.
- **Pass criteria:** Count every location where monetary amounts are stored or displayed — at least 1 must use integer storage. Monetary amounts are stored as integers (smallest currency unit), displayed using a proper currency formatter (`Intl.NumberFormat` with `style: 'currency'`), and arithmetic on amounts uses integer math or a decimal library. The currency code is consistent throughout the codebase.
- **Fail criteria:** Monetary amounts are stored or processed as floats/decimals (risk of floating-point errors). Currency amounts are formatted with string concatenation (`"$" + amount`). Different currency codes or amount units are used inconsistently.
- **Skip (N/A) when:** No monetary amounts displayed or calculated in the application code (amounts are displayed exclusively through Stripe-hosted UI elements).
- **Detail on fail:** `"Amount stored as float in database — floating-point arithmetic may cause rounding errors in tax or discount calculations"` or `"Currency formatted as '$ ' + (amount / 100) without locale-aware formatting"`
- **Remediation:** Handle currency correctly from the start:

  ```ts
  // Store amounts in cents (integer)
  const amountCents = 2999 // $29.99

  // Display using Intl.NumberFormat
  const formatted = new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD',
  }).format(amountCents / 100) // "$29.99"

  // For arithmetic, keep everything in integer cents
  const tax = Math.round(amountCents * 0.08) // 240 cents = $2.40
  const total = amountCents + tax // 3239 cents = $32.39
  ```

  Never use floating-point arithmetic directly on dollar amounts.

## External references

- cwe CWE-681
- iso-25010 functional-correctness

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-002284
