Multi-devises : architecture pour un commerce sans frontières
Comment servir 150 devises sans casser votre cache CDN. Un guide approfondi sur l'internationalisation (i18n), les mathématiques en virgule flottante et le routage Edge.
Vendre à l’échelle mondiale n’est plus une fonctionnalité « Entreprise ». Un enfant dans un garage de Brooklyn peut vendre des T-shirts à des utilisateurs de Tokyo. Mais afficher « Prix : 25,00 € » à un utilisateur japonais est un point de friction. Ils doivent faire du calcul mental (25 € * 150 = 3 750 ¥ ?). Dès que vous introduisez Multi-Currency, vous introduisez l’un des problèmes les plus difficiles de l’ingénierie Web : la Context-Aware Caching.
Chez Maison Code Paris, nous construisons des architectures Global-First. Nous ne considérons pas la devise comme une bascule de l’interface utilisateur ; nous le considérons comme une dimension fondamentale de l’état de l’application, sans doute aussi importante que l’URL elle-même.
Pourquoi Maison Code en parle
Chez Maison Code Paris, nous agissons comme la conscience architecturale de nos clients. Nous héritons souvent de stacks “modernes” construites sans compréhension fondamentale de l’échelle.
Nous abordons ce sujet car il représente un point de pivot critique dans la maturité de l’ingénierie. Une mise en œuvre correcte différencie un MVP fragile d’une plateforme résiliente de niveau entreprise.
Le paradoxe de la mise en cache
Dans une application web standard, votre CDN (Cloudflare/Fastly) met en cache le HTML de la page d’accueil.
- L’utilisateur A (États-Unis) visite « exemple.com ».
- Le serveur affiche le HTML : « Prix : 100 € ».
- CDN met en cache ce code HTML.
- L’utilisateur B (France) visite « exemple.com » 1 minute plus tard.
- CDN sert du HTML en cache : « Prix : 100 € ».
C’est un désastre. L’utilisateur B voit des dollars. Ils ajoutent au panier, et peut-être que le paiement passe en euros plus tard, ou peut-être qu’ils partent simplement parce qu’ils ne veulent pas payer de frais de conversion. Pour résoudre ce problème, nous avons besoin que le serveur sache qui demande. Mais si le serveur le sait, le CDN ne peut pas simplement servir un fichier statique.
Stratégie 1 : Le sous-dossier (The Gold Standard)
La solution technique la plus robuste consiste à rendre la devise explicite dans l’URL.
example.com/en-us-> Sert les USD.example.com/fr-fr-> Sert EUR.example.com/jp-jp-> Sert le JPY.
Pourquoi c’est gagnant :
- Efficacité du cache :
/fr-frest une URL unique. Nous pouvons le mettre en cache de manière agressive en périphérie. Chaque utilisateur français reçoit un accès au cache. - SEO : Googlebot sait exactement quelle devise correspond à quelle région. Vous pouvez facilement définir les balises
hreflang. - Clarté : l’utilisateur sait où il se trouve.
Implémentation dans Remix/Hydrogen :
// app/routes/($locale)._index.jsx
exporter le chargeur de fonctions asynchrones ({ requête, contexte, paramètres }) {
const {locale} = paramètres ; // "fr-fr"
const monnaie = getCurrencyFromLocale(locale); // "EUR"
// Passer à l'API Shopify
const produits = attendre context.storefront.query(PRODUCTS_QUERY, {
variables : {pays : getCountryCode(currency) }
});
return json({ produits });
}
Stratégie 2 : l’en-tête Vary (la solution dynamique)
Si vous devez garder l’URL propre (example.com pour tout le monde), vous devez demander au CDN de mettre en cache plusieurs versions de la même URL.
Nous utilisons l’en-tête « Vary ».
Varier : CF-IPCountry
Cela indique à Cloudflare : “Veuillez stocker une copie distincte de cette page pour chaque code de pays unique.”
- Utilisateur (US) -> CDN recherche « Page d’accueil + US ». Miss. Récupère à partir du serveur. Caches.
- Utilisateur (FR) -> CDN recherche “Page d’accueil + FR”. Miss. Récupère à partir du serveur. Caches.
- Utilisateur (US) -> CDN recherche « Page d’accueil + US ». Frapper.
L’inconvénient : Fragmentation du cache. Si vous prenez en charge 200 pays, vous divisez effectivement votre taux de réussite du cache par 200. Votre serveur d’origine prend plus de charge.
Stratégie 3 : Peinture côté client (l’hybride)
Pour les pages hautes performances strictement mises en cache, nous mettons parfois en cache le Generic Skeleton.
Le HTML renvoyé ne contient ni prix ni espace réservé.
Ensuite, useEffect sur le client récupère le prix local.
fonction Prix({ montant de base }) {
const { devise, taux } = useCurrencyContext();
if (!rate) return <Squelette /> ;
return <span>{formatMoney(baseAmount * taux, devise)}</span> ;
}
Avertissement : Cela provoque un Layout Shift (CLS) et un « Flash de contenu sans prix ». Cela semble bon marché. À utiliser uniquement en dernier recours.
L’architecture des marchés Shopify
Dans l’écosystème Shopify, nous utilisons l’API contextuelle Storefront. Vous ne calculez pas vous-même les taux de change. Vous laissez le backend de Shopify s’en charger. Pourquoi? Parce que Shopify permet aux commerçants de définir des taux de change fixes ou des ajustements de prix par marché. Peut-être que la chemise coûte 20 € aux États-Unis, mais 25 € en Europe (pour couvrir la TVA et les frais d’expédition), pas seulement une conversion directe de 20 € * 0,92.
# La Directive Magique : @inContext
requête GetProduct($handle : String !, $country : CountryCode !)
@inContext(pays : $pays) {
produit (poignée : $poignée) {
gamme de prix {
minVariantPrix {
montant
deviseCode # Renvoie automatiquement la devise locale
}
}
}
}
En transmettant le code du pays, nous déchargeons la complexité de la logique de tarification au moteur de la plateforme.
Mathématiques monétaires : cauchemars à virgule flottante
N’utilisez jamais de numéros JavaScript standard pour gagner de l’argent.
0,1 + 0,2 === 0,30000000000000004
Si vous créez un panier personnalisé, vous perdrez un centime toutes les dix commandes. Plus d’un million de commandes, cela représente 10 000 € de perte à cause d’erreurs en virgule flottante. Règle : stockez l’argent en Cents (entiers).
- 10,00 € -> « 1 000 »
- 19,99€ -> ‘1999’
Utilisez des bibliothèques comme « Dinero.js » ou « Currency.js » pour tous les calculs côté client.
importer Dinero depuis 'dinero.js' ;
const price = Dinero({ montant : 5000, devise : 'EUR' }); // 50,00 €
taxe const = prix.pourcentage (20); // TVA
const total = prix.ajouter (taxes);
Tarification et arrondis psychologiques
La conversion algorithmique est moche.
- 100,00 € * 0,92 = 92,00 €.
- Mais 92,00 € semblent “aléatoires”.
- Le prix psychologique devrait probablement être de 95,00 € ou 89,00 €.
Nous implémentons des Stratégies d’arrondi au niveau de la couche application (si elles ne sont pas gérées par le backend).
function roundToCharmPrice(montant : nombre): nombre {
// 92.34 -> 95.00
// 98.10 -> 99.00
const heavyPart = Math.floor(montant);
const dernierDigit = heavyPart % 10 ;
si (lastDigit < 5) renvoie heavyPart - lastDigit + 5 ; // Arrondir à 5
retourner heavyPart - lastDigit + 9 ; // Arrondir à 9
}
Remarque : Nous conseillons généralement aux clients de les définir explicitement dans Shopify Markets, mais disposer d’une logique de secours est utile pour le regroupement dynamique.
SEO : les métadonnées de l’argent
Google ne parvient pas à indexer vos prix s’ils sont dynamiques (injectés par JS). Si vous utilisez la stratégie 1 (sous-dossiers), les données structurées JSON-LD standard fonctionnent parfaitement.
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "Produit",
"name": "Montre de luxe",
"offres": {
"@type": "Offre",
"prixCurrency": "EUR",
"prix": "5000,00"
}
}
</script>
Assurez-vous que ce bloc correspond au contenu visible. Si le JSON-LD indique EUR et que le texte visible indique USD (en raison d’un changement côté client), Google Merchant Center bannira votre compte pour « incompatibilité de prix ».
13. Le risque de change de remboursement
L’utilisateur achète pour 100 € (110 €). La semaine prochaine, le dollar s’effondre. 100 € sont désormais 120 €. L’utilisateur demande un remboursement. Remboursez-vous 100 € (le client est content, vous perdez 10 €) ? Ou remboursez-vous 110 € convertis en euros (92 €) (le client est furieux) ? Réponse légale : Remboursez le montant en devise d’origine (100 €). Réponse financière : Vous prenez le risque de change. Nous construisons une logique de « Réserves de remboursement » dans le grand livre pour tenir compte de cette fluctuation sous la forme « COGS – FX Loss ».
14. Couverture et gestion de trésorerie
Si vous vendez 10 millions de dollars en euros, vous détenez beaucoup d’euros. Si l’euro s’effondre, vous perdez de l’argent avant de pouvoir payer votre usine américaine. C’est là qu’intervient la couverture automatisée. Nous intégrons Wise Business API ou Airwallex. Lorsque le solde > 10 000 €, conversion automatique en USD. Cette stratégie de « Micro-Hedging » réduit l’exposition aux événements macro-économiques.
15. Rejets de la passerelle de paiement (inadéquation des devises)
Stripe vous permet de facturer dans n’importe quelle devise. Mais certaines banques locales (par exemple au Brésil ou en Inde) refusent les transactions en « devises étrangères » même si la carte les prend en charge. Le correctif : tentatives intelligentes. Si un débit échoue avec « do_not_honor », réessayez immédiatement dans la devise d’émission de la carte (si détectable via BIN). “Vous avez essayé de facturer 100 €. Échec. Réessayez de facturer 92 €. Succès.” Cela améliore les taux d’autorisation de 4 %.
16. Paiement à la livraison (COD) et devise
Au Moyen-Orient (CCG), 60 % des commandes sont contre remboursement. Le coursier récupère l’argent liquide. Le coursier n’accepte pas les USD. Ils n’acceptent que les AED/SAR. Si vous affichez USD à la caisse, le coursier arrivera et demandera un montant différent (taux de change du jour). Règle : Si le COD est sélectionné, vous DEVEZ verrouiller le prix dans la devise locale au moment du paiement et l’imprimer sur le manifeste physique. Une divergence conduit ici à « Refusé à la livraison ».
17. Conclusion
Le multi-devises n’est pas un “Plug-in”. Il s’agit d’une décision architecturale fondamentale qui touche au routage, à la mise en cache, à la conception de bases de données et au référencement. Chez Maison Code, nous pensons que le véritable Commerce sans frontières est invisible. L’utilisateur atterrit, voit sa monnaie, paie avec sa méthode locale et reçoit les marchandises. La complexité est notre fardeau, pas le leur.
Vous vous mondialisez ?
Si vous rencontrez des difficultés avec les en-têtes « Vary » ou des caches défectueux. Engagez nos Architectes.