Skip to content
Insights

La columna llamada amount_cents cobrará 100x de más en Japón

5 min de lecturaPagos

Casi todo esquema de pagos empieza igual. Alguien aprende a no guardar dinero en float — correctamente — y escribe:

amount_cents integer not null

Esto es correcto para dólares, euros, libras y para casi todo lo que facturarás. Es incorrecto la primera vez que cobras a un cliente japonés, y lo es por un factor de cien.

Monedas sin decimales

El yen japonés no tiene subunidad. ¥1.000 son mil yenes, no diez yenes y pico. El won coreano, el dong vietnamita, el peso chileno, el guaraní paraguayo y varios más se comportan igual. Stripe y la mayoría de las APIs de pago lo manejan bien — reciben el importe en la unidad más pequeña de la moneda, y para el JPY la unidad más pequeña ES el yen.

Así que una columna llamada amount_cents con 100000 significa mil dólares, y cien mil yenes. Si tu código multiplica el número humano por 100 a la entrada, acabas de facturar ¥100.000 por un trabajo de ¥1.000.

También existen monedas de tres decimales

El dinar bahreiní, el kuwaití, el jordano y el rial omaní usan tres decimales. La unidad más pequeña es una milésima. Un esquema que asume dos decimales cobra diez veces menos en estas monedas.

La solución es nomenclatura y una función

Guarda el importe en la unidad menor de la propia moneda, y nombra la columna de forma que nadie pueda leerla mal. Luego centraliza la conversión para que exista exactamente un lugar que conozca el exponente:

const ZERO_DECIMAL = new Set(["JPY", "KRW", "VND", "CLP", "PYG", "ISK"]);
const THREE_DECIMAL = new Set(["BHD", "KWD", "JOD", "OMR", "TND"]);

function exponent(currency: string): 0 | 2 | 3 {
  const c = currency.toUpperCase();
  if (ZERO_DECIMAL.has(c)) return 0;
  if (THREE_DECIMAL.has(c)) return 3;
  return 2;
}

export function toMinorUnits(amount: number, currency: string): number {
  return Math.round(amount * 10 ** exponent(currency));
}

Dos reglas lo sostienen: la columna cruda nunca se lee directamente fuera de este módulo, y ningún código de presentación divide por 100. Si alguna plantilla contiene un 100 literal, ahí está el fallo esperando.

Además: rechaza lo que no puedes representar

Si alguien escribe ¥1.000,50, ese importe no existe. Redondear en silencio es una decisión que heredará tu contabilidad. Valida a la entrada y recházalo — un error al capturar sale más barato que una discrepancia en la conciliación.

Nada de esto es difícil. Solo es invisible hasta la primera factura en una moneda que nadie probó, y para entonces el dinero ya se movió.

¿Construyendo algo donde estas decisiones importan?

Iniciar proyecto