Use Cases (Atualizado: 02/06/2026)

Formatação de moeda com Claude Code: guia prático de Intl.NumberFormat

Implemente moedas com Intl.NumberFormat, minor units, arredondamento, formato contábil e testes JPY/USD/EUR.

Formatação de moeda com Claude Code: guia prático de Intl.NumberFormat

Formatação de moeda também é parte do billing

Mostrar $19.99, ¥1,980 ou R$ 1.234,56 parece detalhe visual, mas em SaaS, ecommerce, nota fiscal ou painel financeiro isso afeta reembolso, imposto, exportação e métricas de receita. JPY normalmente aparece sem casas decimais, USD e EUR usam duas, inglês da Índia agrupa como12,34,567, e valores negativos podem precisar de formato contábil como($10.00).

O padrão seguro é armazenar dinheiro como inteiro em minor units, calcular com inteiros e formatar somente na borda de exibição usandoIntl.NumberFormat. Minor unit é a menor unidade usada no cálculo: centavos para USD, ienes inteiros para JPY.

Este guia mostra uma implementação que o Claude Code consegue gerar e que ainda é fácil de revisar: preços SaaS multi-moeda, arredondamento, formato contábil, regra de não salvar strings formatadas, testes para JPY/USD/EUR/BRL/INR/IDR e prompts de revisão. Use como referência oficial o MDN deIntl.NumberFormat, MDN deformatToParts e a especificaçãoECMA-402 NumberFormat.

flowchart LR
  A[DB: minor unit integer] --> B[Domain math]
  B --> C[Tax, discount, refund]
  C --> D[Intl.NumberFormat]
  D --> E[UI, invoice, email]

Separe valor salvo e texto exibido

Não salve"R$ 19,99" como fonte da verdade. SalveamountMinor ecurrency: 1999 USD, 1980 JPY, 123456 IDR. Assim você consegue ordenar, somar, reembolsar, auditar e traduzir sem parsear texto localizado.

AbordagemExemploVantagemRisco
String formatada"R$ 19,99"Rápida em página estáticaQuebra relatórios, reembolsos e localização
Número decimal19.99Fácil no começoErros de ponto flutuante e regras espalhadas
Inteiro minor unit1999Cálculo e testes estáveisPrecisa conversão nas bordas

Intl.NumberFormat formata. Ele não decide câmbio, imposto, regra do provedor de pagamento ou quando uma fatura deve arredondar.

Implementação executável

Salve comocurrency-format-demo.mjs e rodenode currency-format-demo.mjs.

// currency-format-demo.mjs
import assert from "node:assert/strict";

const minorUnitDigits = Object.freeze({
  JPY: 0,
  USD: 2,
  EUR: 2,
  BRL: 2,
  INR: 2,
  IDR: 0,
});

function assertCurrency(currency) {
  if (!(currency in minorUnitDigits)) {
    throw new Error(`Unsupported currency: ${currency}`);
  }
}

function roundHalfAwayFromZero(value) {
  return value < 0 ? -Math.round(Math.abs(value)) : Math.round(value);
}

export function moneyFromMajor(amount, currency) {
  assertCurrency(currency);
  if (!Number.isFinite(amount)) {
    throw new Error(`Invalid amount: ${amount}`);
  }
  const digits = minorUnitDigits[currency];
  return {
    minor: roundHalfAwayFromZero(amount * 10 ** digits),
    currency,
  };
}

export function toMajor(money) {
  assertCurrency(money.currency);
  return money.minor / 10 ** minorUnitDigits[money.currency];
}

export function addMoney(left, right) {
  if (left.currency !== right.currency) {
    throw new Error(`Currency mismatch: ${left.currency} vs ${right.currency}`);
  }
  return { minor: left.minor + right.minor, currency: left.currency };
}

export function multiplyMoney(money, factor) {
  if (!Number.isFinite(factor)) {
    throw new Error(`Invalid factor: ${factor}`);
  }
  return {
    minor: roundHalfAwayFromZero(money.minor * factor),
    currency: money.currency,
  };
}

export function formatMoney(
  money,
  {
    locale = "en-US",
    accounting = false,
    currencyDisplay = "symbol",
    roundingMode = "halfExpand",
  } = {},
) {
  assertCurrency(money.currency);
  return new Intl.NumberFormat(locale, {
    style: "currency",
    currency: money.currency,
    currencyDisplay,
    currencySign: accounting ? "accounting" : "standard",
    roundingMode,
  }).format(toMajor(money));
}

const samples = [
  ["ja-JP", { minor: 123456, currency: "JPY" }],
  ["en-US", { minor: 123456, currency: "USD" }],
  ["de-DE", { minor: 123456, currency: "EUR" }],
  ["pt-BR", { minor: 123456, currency: "BRL" }],
  ["en-IN", { minor: 123456789, currency: "INR" }],
  ["id-ID", { minor: 123456, currency: "IDR" }],
];

for (const [locale, money] of samples) {
  const formatter = new Intl.NumberFormat(locale, {
    style: "currency",
    currency: money.currency,
  });
  const options = formatter.resolvedOptions();
  const parts = formatter.formatToParts(toMajor(money));

  assert.equal(options.maximumFractionDigits, minorUnitDigits[money.currency]);
  assert.ok(parts.some((part) => part.type === "currency"));
  console.log(`${locale} ${money.currency}: ${formatMoney(money, { locale })}`);
}

assert.equal(
  addMoney(moneyFromMajor(19.99, "USD"), moneyFromMajor(5, "USD")).minor,
  2499,
);
assert.equal(multiplyMoney(moneyFromMajor(1980, "JPY"), 1.1).minor, 2178);
assert.match(
  formatMoney({ minor: -129900, currency: "USD" }, { locale: "en-US", accounting: true }),
  /^\(\$/,
);
assert.throws(
  () => addMoney(moneyFromMajor(10, "USD"), moneyFromMajor(10, "JPY")),
  /Currency mismatch/,
);

console.log("currency formatting checks passed");

Casos reais

Uma tabela de preços SaaS multi-moeda deve receber dados estruturados, não labels já traduzidas.

type CurrencyCode = "JPY" | "USD" | "EUR" | "BRL" | "INR" | "IDR";

type PlanPrice = {
  planId: "starter" | "pro" | "team";
  currency: CurrencyCode;
  amountMinor: number;
};

const prices: PlanPrice[] = [
  { planId: "pro", currency: "JPY", amountMinor: 1980 },
  { planId: "pro", currency: "USD", amountMinor: 1999 },
  { planId: "pro", currency: "EUR", amountMinor: 1899 },
];

Segundo caso: faturas e reembolsos. currencySign: "accounting" pode deixar valores negativos mais naturais, mas cada locale tem suas próprias regras. Para PDF de fatura, escreva testes de renderização.

Terceiro caso: imposto, desconto e pro rata. 1999 * 10 / 31 não fecha exato. Defina se o arredondamento ocorre por linha ou só no total, e coloque essa política no nome do teste.

Quarto caso: admin e CSV. Exporteamount_minor, currency eamount_display. Pessoas leem o display; BI calcula MRR, LTV, reembolso e ROAS com os campos numéricos.

Falhas comuns

Não salve strings monetárias formatadas. Não confunda locale com currency: uma interface em português pode cobrar em USD. Não assuma duas casas decimais para todas as moedas. roundingMode não é uma política completa de billing, é opção de exibição. Para separar símbolo e número, useformatToParts, não regex.

Prompt de revisão para Claude Code

Review this repository's money formatting and billing math.
Requirements:
- Check that DB/API models do not store formatted currency strings
- Make JPY/USD/EUR/BRL/INR/IDR minor units explicit
- Use Intl.NumberFormat while keeping locale and currency separate
- Decide whether refunds and discounts need currencySign: "accounting"
- Make rounding policy visible in test names
- Add Node-runnable tests for currency formatting behavior

Leituras relacionadas e CTA

Para internacionalização completa, vejai18n com Claude Code. Para datas e fusos, leiatratamento de data e hora. Para monetização recorrente, combine comStripe subscription.

ClaudeCodeLab publica checklists e prompts para áreas que IA costuma simplificar demais: billing, auth, segurança e revisão de release. Antes de pedir ao Claude Code para alterar uma página de preços, confirme que o produto separaamountMinor, currency elocale.

Testei o script localmente com Node.js 24. Ele valida casas decimais e partes de moeda para JPY/USD/EUR/BRL/INR/IDR, display contábil em USD, aritmética inteira e erro ao misturar moedas. Strings exatas podem variar conforme dados ICU, então testes de produção devem validar a política, não só um snapshot.

#Claude Code #moeda #formatação #Intl #internacionalização
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.