MAISON CODE .
/ Currency · Caching · Edge · Global · Architecture

Multimoneda: arquitectura para el comercio sin fronteras

Cómo ofrecer 150 monedas sin romper el caché de su CDN. Una guía detallada sobre internacionalización (i18n), matemáticas de punto flotante y enrutamiento perimetral.

AB
Alex B.
Multimoneda: arquitectura para el comercio sin fronteras

Vender globalmente ya no es una característica “empresarial”. Un niño en un garaje de Brooklyn puede vender camisetas a usuarios de Tokio. Pero mostrar “Precio: €25,00” a un usuario japonés es un punto de fricción. Tienen que hacer cálculos mentales (€25 * 150 = ¥3750?). En el momento en que introduces Multimoneda, introduces uno de los problemas más difíciles en ingeniería web: Almacenamiento en caché contextual.

En Maison Code Paris, construimos arquitecturas Global-First. No vemos la moneda como una opción para alternar la interfaz de usuario; lo vemos como una dimensión fundamental del estado de la aplicación, posiblemente tan importante como la propia URL.

Por qué Maison Code habla de esto

En Maison Code Paris, actuamos como la conciencia arquitectónica de nuestros clientes. A menudo heredamos stacks “modernos” construidos sin una comprensión fundamental de la escala.

Discutimos este tema porque representa un punto de inflexión crítico en la madurez de la ingeniería. Implementarlo correctamente diferencia un MVP frágil de una plataforma resistente de nivel empresarial.

La paradoja del almacenamiento en caché

En una aplicación web estándar, su CDN (Cloudflare/Fastly) almacena en caché el HTML de la página de inicio.

  1. El usuario A (EE. UU.) visita example.com.
  2. El servidor muestra HTML: Precio: €100.
  3. CDN almacena en caché este HTML.
  4. El usuario B (Francia) visita example.com 1 minuto después.
  5. CDN ofrece HTML almacenado en caché: Precio: €100.

Esto es un desastre. El usuario B ve dólares. Agregan cosas al carrito y tal vez la caja cambia a euros más tarde, o tal vez simplemente se van porque no quieren pagar tarifas de conversión. Para solucionar este problema, necesitamos que el servidor sepa quién está preguntando. Pero si el servidor lo sabe, la CDN no puede simplemente entregar un archivo estático.

Estrategia 1: La subcarpeta (el estándar de oro)

La solución técnica más sólida es hacer explícita la moneda en la URL.

  • example.com/en-us -> Sirve USD.
  • example.com/fr-fr -> Sirve EUR.
  • example.com/jp-jp -> Sirve JPY.

Por qué esto gana:

  1. Eficiencia de caché: /fr-fr es una URL única. Podemos almacenarlo en caché agresivamente en el borde. Cada usuario francés obtiene un caché.
  2. SEO: el robot de Google sabe exactamente qué moneda corresponde a qué región. Puede configurar etiquetas hreflang fácilmente.
  3. Claridad: El usuario sabe dónde se encuentra.

Implementación en Remix/Hydrogen:

// aplicación/rutas/($locale)._index.jsx
exportar cargador de funciones asíncronas ({solicitud, contexto, parámetros}) {
  const {localización} = parámetros; // "fr-fr"
  moneda constante = getCurrencyFromLocale(locale); // "euros"
  
  // Pasar a Shopify API
  productos constantes = esperar contexto.storefront.query(PRODUCTS_QUERY, {
    variables: { país: getCountryCode(moneda) }
  });
  
  devolver json({productos});
}

Estrategia 2: El encabezado variable (la solución dinámica)

Si debe mantener la URL limpia (example.com para todos), debe indicarle a la CDN que almacene en caché varias versiones de la misma URL. Usamos el encabezado “Vary”.

Variar: CF-IPPaís

Esto le dice a Cloudflare: “Guarde una copia separada de esta página para cada código de país único”.

  • Usuario (EE. UU.) -> CDN busca “Página de inicio + EE. UU.”. Señorita. Obtiene del servidor. Cachés.
  • Usuario (FR) -> CDN busca “Página de inicio + FR”. Señorita. Obtiene del servidor. Cachés.
  • Usuario (EE. UU.) -> CDN busca “Página de inicio + EE. UU.”. Golpear.

La desventaja: Fragmentación de caché. Si admite 200 países, efectivamente divide su índice de aciertos de caché entre 200. Su servidor de origen requiere más carga.

Estrategia 3: Pintura del lado del cliente (el híbrido)

Para páginas de alto rendimiento estrictamente almacenadas en caché, a veces almacenamos en caché el Esqueleto genérico. El HTML devuelto no contiene precio ni marcador de posición. Luego, useEffect en el cliente recupera el precio local.

función Precio({cantidadbase}) {
  const {moneda, tipo de cambio} = useCurrencyContext();
  
  if (!rate) devuelve <Esqueleto />;
  
  return <span>{formatMoney(baseAmount * tasa, moneda)}</span>;
}

Advertencia: Esto provoca un cambio de diseño (CLS) y un “destello de contenido sin precio”. Se siente barato. Úselo sólo como último recurso.

La arquitectura de Shopify Markets

En el ecosistema de Shopify, utilizamos la API Contextual Storefront. Usted no calcula los tipos de cambio usted mismo. Dejas que el backend de Shopify se encargue de ello. ¿Por qué? Porque Shopify permite a los comerciantes establecer Tipos de cambio fijos o Ajustes de precios por mercado. Quizás la camiseta cueste 20 dólares en EE. UU., pero 25 € en Europa (para cubrir el IVA y el envío), no solo una conversión directa de 20 dólares * 0,92.

# La Directiva Mágica: @inContext
consulta GetProduct($handle: String!, $country: CountryCode!) 
  @inContext(país: $país) {
  producto(identificador: $identificador) {
    rango de precios {
      minVariantePrecio {
        cantidad
        monedaCode # Devuelve automáticamente la moneda local
      }
    }
  }
}

Al pasar el código de país, descargamos la complejidad de la lógica de precios al motor de la plataforma.

Matemáticas del dinero: pesadillas de punto flotante

Nunca utilices números de JavaScript estándar por dinero. 0,1 + 0,2 === 0,30000000000000004

Si está creando un carrito personalizado, perderá un centavo por cada décimo pedido. Más de un millón de pedidos, son 10.000 dólares perdidos por errores de punto flotante. Regla: Almacene el dinero en Céntimos (Enteros). *€10.00 -> 1000

  • 19,99€ -> 1999

Utilice bibliotecas como Dinero.js o Currency.js para todas las matemáticas del lado del cliente.

importar Dinero desde 'dinero.js';

precio constante = Dinero({ cantidad: 5000, moneda: 'EUR' }); // 50,00 €
impuesto constante = precio.porcentaje(20); // IVA
constante total = precio.añadir(impuesto);

Precios psicológicos y redondeo

La conversión algorítmica es fea.

  • 100,00€ * 0,92 = 92,00€.
  • Pero 92,00 € parece “al azar”.
  • El precio psicológico probablemente debería ser 95,00€ o 89,00€.

Implementamos Estrategias de redondeo en la capa de aplicación (si no las maneja el backend).

función roundToCharmPrice(cantidad: número): número {
    // 92,34 -> 95,00
    // 98,10 -> 99,00
    const heavyPart = Math.floor(cantidad);
    const último dígito = parte pesada % 10;
    
    si (últimoDigit < 5) devuelve heavyPart - lastDigit + 5; // Redondear a 5
    devolver parte pesada - último dígito + 9; // Redondear a 9
}

Nota: Por lo general, recomendamos a los clientes que los configuren explícitamente en Shopify Markets, pero tener una lógica alternativa es útil para la agrupación dinámica.

SEO: Los metadatos del dinero

Google no indexa sus precios si son dinámicos (inyectados con JS). Si utiliza la Estrategia 1 (Subcarpetas), los datos estructurados JSON-LD estándar funcionan perfectamente.

<tipo de script="aplicación/ld+json">
{
  "@context": "https://schema.org/",
  "@tipo": "Producto",
  "nombre": "Reloj de lujo",
  "ofertas": {
    "@type": "Oferta",
    "priceCurrency": "EUR",
    "precio": "5000,00"
  }
}
</script>

Asegúrese de que este bloque coincida con el contenido visible. Si el JSON-LD dice EUR y el texto visible dice USD (debido a un cambio en el lado del cliente), Google Merchant Center prohibirá su cuenta por “Discordancia de precios”.

13. El riesgo cambiario de reembolso

El usuario compra por 100€ (110€). La semana que viene, el dólar se desploma. 100€ ahora son 120€. El usuario solicita reembolso. ¿Reembolsan 100 € (el cliente está contento, pierde 10 €)? ¿O me devuelven 110€ convertidos a euros (92€) (el cliente está furioso)? Respuesta legal: Reembolsar el importe en moneda original (100€). Respuesta financiera: Usted asume el riesgo cambiario. Construimos la lógica de “Reservas de reembolso” en el libro mayor para contabilizar esta fluctuación como “COGS - Pérdida cambiaria”.

14. Gestión de coberturas y tesorería

Si vendes 10 millones de dólares en euros, tienes muchos euros. Si el euro colapsa, perderá dinero antes de poder pagarle a su fábrica estadounidense. Aquí es donde entra en juego la cobertura automatizada. Nos integramos con Wise Business API o Airwallex. Cuando el saldo > 10 000 €, se convierte automáticamente a USD. Esta estrategia de “Micro-Hedging” reduce la exposición a eventos macroeconómicos.

15. Rechazos de pasarela de pago (desfase de moneda)

Stripe te permite cobrar en cualquier moneda. Pero algunos bancos locales (por ejemplo, en Brasil o India) rechazan transacciones en “moneda extranjera” incluso si la tarjeta las admite. La solución: reintentos inteligentes. Si un cargo falla con do_not_honor, ​​vuelva a intentarlo inmediatamente en la moneda emisora ​​de la tarjeta (si se puede detectar a través de BIN). “Intentaste cobrar 100 €. Falló. Vuelve a intentar cobrar 92 €. Éxito”. Esto mejora las tasas de autorización en un 4%.

16. Contra reembolso (COD) y moneda

En Oriente Medio (CCG), el 60% de los pedidos son contra reembolso. El mensajero recoge el efectivo. El mensajero no acepta USD. Sólo aceptan AED/SAR. Si muestra USD en la caja, el mensajero llegará y le pedirá una cantidad diferente (tipo de cambio del día). Regla: Si se selecciona COD, DEBE bloquear el precio en moneda local al momento de pagar e imprimirlo en el manifiesto físico. La discrepancia aquí conduce a “Rechazado en la entrega”.

17. Conclusión

Multidivisa no es un “complemento”. Es una decisión arquitectónica fundamental que afecta al enrutamiento, el almacenamiento en caché, el diseño de bases de datos y el SEO. En Maison Code, creemos que el verdadero Comercio sin fronteras es invisible. El usuario aterriza, ve su moneda, paga con su método local y recibe la mercancía. La complejidad es nuestra carga, no la de ellos.


**[Contrate a nuestros arquitectos](/contact)**.