main content
< Volver a blog sobre aplicaciones móviles

Pagos multidivisa en una app web a medida

Vender en varias monedas sin romper la contabilidad: cómo implementar pagos multidivisa en una aplicación web a medida

Cuando un producto empieza a vender fuera de España, el equipo de producto suele pensar que añadir más monedas es "mostrar el símbolo $ en vez de €". Y entonces alguien guarda un importe como float, otro convierte al vuelo para sacar un informe trimestral y, tres meses después, el cierre contable no cuadra por unos céntimos que nadie sabe de dónde salen.

Te lo cuento como se lo explicaría a tu equipo en una pizarra: cómo implementar pagos multidivisa en una aplicación web a medida sin que la divisa se convierta en una fuente perpetua de bugs. La idea central cabe en una frase: separa lo que el usuario ve de lo que tú guardas, y nunca dejes que un tipo de cambio se calcule dos veces para el mismo hecho económico.

Tres problemas distintos que la gente mezcla en uno

Hablar de "soporte multidivisa" en realidad esconde tres decisiones independientes. Conviene desacoplarlas desde el día uno porque cada una tiene su propio dueño y su propio riesgo.

  1. Presentación: en qué moneda le enseñas los precios al usuario. Es una decisión de UX y de mercado.
  2. Cobro: en qué moneda cargas la tarjeta y en qué moneda te liquida la pasarela. Es una decisión financiera y de pasarela.
  3. Contabilidad: en qué moneda registras el ingreso para tu contabilidad y tus impuestos. En una empresa española, casi siempre el euro.

El error clásico es asumir que las tres coinciden. Puedes mostrar precios en libras, cobrar en libras y, aun así, tener que contabilizar en euros al tipo de cambio del día de la operación. Las tres capas se tocan, pero no son la misma cosa.

El precio de mostrar y el precio de cobrar

Mostrar 19,99 £ es marketing: tú decides si rebajas la conversión a un número "bonito" o si dejas la cifra que salga del cambio. Cobrar 19,99 £ es un hecho contractual: ese es el importe que el cliente acepta y el que aparecerá en su extracto. No los confundas en el código. El precio mostrado puede tener lógica de pricing psicológico; el precio cobrado tiene que ser exacto y trazable.

Guarda siempre el importe en moneda base más el tipo de cambio del momento

Esta es la regla que evita el 80% de los problemas. Para cada transacción guardas, como mínimo:

  • El importe original en la divisa en la que cobraste (lo que el cliente vio y aceptó).
  • La divisa de esa transacción (código ISO 4217: EUR, GBP, USD, JPY…).
  • El tipo de cambio aplicado en el instante de la transacción, hacia tu moneda base.
  • La moneda base y el importe ya convertido, si lo necesitas materializado para informes.

¿Por qué guardar el tipo de cambio congelado y no recalcularlo cuando haga falta? Porque una venta es un hecho histórico. Si en marzo cobraste 100 USD a un cambio de 0,92 €/USD, ese ingreso fueron 92 € para siempre, aunque hoy el dólar esté a 0,95. Si recalculas "al vuelo" con el tipo de hoy, estás reescribiendo el pasado y tu contabilidad deja de cuadrar con lo que de verdad entró en tu cuenta.

La frase que tienes que clavar en el equipo: convertir al vuelo está bien para mostrar precios; está prohibido para contabilidad. La conversión que vale es la que ocurrió en el momento de la transacción, y se queda escrita en piedra.

Importes en enteros, nunca en float

Aquí no hay debate. El dinero no se guarda en coma flotante. 0,1 + 0,2 no es 0,3 en aritmética de punto flotante, y eso es exactamente el tipo de céntimo fantasma que te hace perder una tarde de conciliación.

La práctica estándar es trabajar con la unidad menor de cada divisa (amount_minor): céntimos para el euro, peniques para la libra. Guardas enteros (integer/bigint) y conviertes a la unidad mayor solo para mostrar. La propia Stripe expone los importes así, en la unidad mínima, precisamente por esto.

Si necesitas decimales por algún cálculo intermedio (prorrateos, descuentos), usa un tipo decimal exacto (DECIMAL/NUMERIC en la base de datos, BigDecimal o equivalente en la app), nunca float ni double.

El modelo de datos mínimo

Lo bonito de esto es que el modelo es pequeño. Aquí tienes el esqueleto de cómo guardarías una transacción. Fíjate en que el importe es entero en unidad menor y el tipo de cambio queda fijado en la fila:

Tabla: transaction
  id              UUID
  amount_minor    BIGINT      -- importe en la unidad menor de 'currency' (ej. 1999 = 19,99)
  currency        CHAR(3)     -- ISO 4217: EUR, GBP, USD, JPY...
  fx_rate         DECIMAL(18,8) -- tipo de cambio hacia la moneda base, congelado
  base_currency   CHAR(3)     -- tu moneda contable, p.ej. EUR
  base_amount_minor BIGINT    -- amount_minor convertido y redondeado a base, materializado
  captured_at     TIMESTAMP   -- instante real del cobro (define el fx_rate aplicable)

Regla de conversion (pseudocodigo):
  base_amount = round_for_currency(amount_minor * fx_rate, base_currency)

Invariantes:
  - amount_minor es SIEMPRE entero
  - fx_rate NUNCA se recalcula tras captured_at
  - nunca se suman amount_minor de currencies distintas sin convertir antes

Con esto puedes reconstruir cualquier informe sin volver a pedir tipos de cambio a nadie. El histórico está completo dentro de cada fila.

De dónde sacas los tipos de cambio y cada cuánto los refrescas

Los tipos de cambio salen de una fuente FX externa. Las opciones razonables:

  • Tu pasarela: si cobras con Stripe o Adyen y dejas que ellos conviertan, te devuelven el tipo aplicado. Es la fuente más fiable porque es la real de esa transacción.
  • APIs de tipos de cambio: hay servicios comerciales (fixer, Open Exchange Rates, currencylayer y similares) y fuentes oficiales. El BCE publica tipos de referencia diarios, útiles como base contable estable y auditable.
  • Tu banco, si liquidas conversiones tú mismo.

La frecuencia de refresco depende del uso:

  • Para mostrar precios, refrescar una o varias veces al día suele bastar; cachea el resultado y evita pegarle a la API en cada render.
  • Para el cobro real, el tipo que cuenta es el del instante de la captura, no el cacheado para la web.
  • Para contabilidad, muchas empresas fijan el tipo de referencia del BCE del día de la operación, porque es defendible ante una auditoría y ante la AEAT.

Un consejo operativo: guarda también qué fuente y de qué momento es cada tipo. Cuando alguien pregunte por qué una venta se contabilizó a 0,9187 y no a 0,9201, querrás poder responder con datos, no con suposiciones.

Redondeo y decimales: cada divisa tiene sus reglas

Asumir que todas las monedas tienen dos decimales es uno de los fallos más comunes y más silenciosos. ISO 4217 define cuántos decimales (el minor unit) tiene cada divisa:

  • EUR, USD, GBP: 2 decimales. La unidad menor es el céntimo/penique.
  • JPY (yen): 0 decimales. No hay "céntimos de yen". 1.000 ¥ son 1.000, no 100.000 unidades menores.
  • BHD (dinar bahreiní), KWD (dinar kuwaití): 3 decimales.

Si tu amount_minor asume siempre dos posiciones, en cuanto cobres un yen tendrás importes inflados por cien. La regla de redondeo y la cantidad de decimales deben venir de una tabla por divisa, no de una constante global.

Dónde y cuándo redondear

Redondea lo más tarde posible y solo cuando produces un importe que el usuario o la contabilidad van a ver. Si encadenas conversiones y descuentos redondeando en cada paso, los errores se acumulan. Mantén la precisión completa en los cálculos intermedios y aplica el redondeo de la divisa de destino al final, con una política consistente (lo habitual: round half up o banker's rounding, pero elige una y documéntala).

Pasarela que liquida en varias divisas o conversión propia

Aquí hay una decisión de arquitectura con consecuencias reales:

Opción A — La pasarela hace el trabajo. Stripe y Adyen permiten presentar precios y liquidar en varias divisas. Tú cobras en la moneda del cliente y ellos te devuelven euros (o te liquidan en varias cuentas por divisa). Ventajas: menos código, menos riesgo de cumplimiento, el tipo de cambio aplicado te lo dan ellos. Inconveniente: pagas su spread de conversión y tienes menos control sobre el tipo.

Opción B — Conversión propia. Tú gestionas los tipos, conviertes y cobras. Ganas control y, potencialmente, mejor tipo. Pero asumes el riesgo de cambio (entre que muestras el precio y liquidas, el mercado se mueve), la complejidad de la conciliación y más superficie para errores.

Para la mayoría de proyectos a medida que arrancan internacionalización, empezar con la liquidación multidivisa de la pasarela es lo sensato: delegas el riesgo y la complejidad, y guardas igualmente el modelo de datos correcto por tu lado. La conversión propia tiene sentido cuando los volúmenes justifican optimizar el spread o cuando necesitas monedas que la pasarela no cubre.

Facturación, IVA y la ventanilla única OSS

Aquí es donde la divisa deja de ser un problema técnico y pasa a ser fiscal. Algunos puntos para una empresa española:

  • La factura se emite en la moneda de la operación, pero el IVA y los registros contables suelen llevarse en euros. Necesitas el tipo de cambio del momento del devengo para expresar las bases en euros.
  • En ventas B2C dentro de la UE (servicios digitales y bienes a distancia), por encima de cierto umbral aplica el IVA del país del cliente. El régimen OSS (One-Stop-Shop / ventanilla única) te permite declarar ese IVA de todos los países de la UE en una sola declaración en España, sin registrarte en cada Estado miembro.
  • Eso implica que tu app tiene que determinar el país del cliente, aplicar el tipo de IVA correcto de ese país y guardarlo por transacción, además de la divisa. País de tributación, tipo de IVA aplicado y divisa son tres campos distintos.

Los tipos de IVA, umbrales y reglas del OSS cambian y conviene confirmarlos con tu asesoría o con la AEAT antes de cablearlos en el código: trátalos como configuración por país, no como constantes en el código fuente. Lo que sí es estructural es el principio: guarda lo suficiente en cada transacción como para reconstruir la factura y la declaración sin adivinar nada después.

Conciliación: cuadrar lo que cobraste con lo que entró

La conciliación es el momento de la verdad. Comparas lo que tu sistema dice que cobraste con lo que la pasarela y el banco dicen que ingresaron. Con multidivisa hay matices:

  • La pasarela cobra en la divisa del cliente pero te liquida en euros (o en varias divisas) menos sus comisiones y su spread. El neto que recibes no es la suma bruta de las ventas.
  • Las comisiones y el spread de conversión hay que registrarlos como gasto, no como una diferencia "que ya cuadrará".
  • Si guardaste amount_minor, currency y fx_rate por transacción, la conciliación es restar y comparar. Si guardaste floats y conversiones al vuelo, será una arqueología de céntimos.

Por eso el modelo de datos correcto no es un capricho de ingeniería: es lo que hace que cerrar el mes sea un proceso de diez minutos en vez de un día de sufrimiento.

Los errores que más caros salen

Resumo los que veo una y otra vez, para que tu equipo los marque en rojo:

  • Guardar dinero en float/double. Usa enteros en unidad menor o decimal exacto. Siempre.
  • Convertir al vuelo para contabilidad. El tipo de cambio se congela en el momento de la transacción y no se recalcula nunca.
  • Asumir dos decimales para todas las divisas. El yen tiene cero, algunos dinares tienen tres. Tabla por divisa.
  • Sumar importes de divisas distintas sin convertir antes. Un SUM(amount_minor) mezclando EUR y USD es un número sin significado.
  • No guardar la fuente ni el momento del tipo de cambio. Sin trazabilidad no hay auditoría posible.
  • Olvidar comisiones y spread en la conciliación y achacarlos a "diferencias de redondeo".
  • Tratar el IVA por país como un detalle posterior. Determínalo y guárdalo en el momento del cobro.

Por dónde empezar sin sobreingeniería

Si estás arrancando, el camino pragmático es: define tu moneda base, monta el modelo de datos con amount_minor + currency + fx_rate desde el primer commit, apóyate en la liquidación multidivisa de tu pasarela y deja la conversión propia para cuando los números la justifiquen. El sobrecoste de hacer bien el modelo al principio es de unas horas; el sobrecoste de arreglarlo con datos históricos sucios se mide en semanas.

Si quieres que revisemos tu caso concreto, qué mercados y divisas vas a abrir, qué pasarela encaja mejor y cómo dejar el modelo de datos preparado para el OSS, podemos verlo juntos y planificarlo sin compromiso. Mejor diseñar bien la primera moneda que reescribir la contabilidad con la quinta.

Contacta con nosotros
Fila 1