Retour au blog

WooCommerce Multidevise : Comment Intégrer une API de Taux de Change en Temps Réel (Guide Développeur 2026)

V
Vlado Grigirov
May 07, 2026
WooCommerce Currency API Exchange Rates WordPress PHP E-commerce Finexly

WooCommerce fait tourner environ un tiers des boutiques en ligne du monde, et dès que l'une d'elles décide de vendre à l'international, la même question revient : comment garder prix, taxes et reversements honnêtes dans douze devises sans surveiller le marché des changes à la main chaque matin ? La réponse honnête est de brancher une API de taux de change multidevise pour WooCommerce sur sa boutique — mais bien le faire est plus subtil que d'installer un plugin et de partir. Il faut penser à la cadence de rafraîchissement, aux ruées de cache (cache stampede), à la compatibilité de la passerelle de paiement, à l'arrondi, et à ce qui se passe quand le fournisseur amont renvoie un 503 au milieu d'un checkout.

Ce guide passe en revue trois patterns de production que les développeurs utilisent vraiment en 2026, avec du PHP fonctionnel pour chacun. Nous allons brancher l'API Finexly sur WooCommerce de trois manières : comme flux personnalisé pour un plugin de switcher de devises existant, comme couche basée sur des filtres construite de zéro, et comme tâche Action Scheduler avec verrouillage propre. À la fin, vous aurez un pattern adapté à n'importe quel stack — WooCommerce vanille, WooCommerce + plugin switcher, ou un build B2B totalement custom.

Pourquoi une API de Taux de Change en Temps Réel Compte pour WooCommerce

Tel quel, WooCommerce définit une seule devise de base dans woocommerce_currency et suppose que toutes les commandes, remboursements et reversements sont réglés dans cette devise. Le support multidevise est une couche qui s'empile par-dessus : un switcher dans l'en-tête, des totaux du panier recalculés et (parfois) un traitement de paiement localisé. Cette couche a besoin de taux de change venant de quelque part, et « quelque part » se résume souvent à deux choses — un taux saisi à la main que personne ne pense à mettre à jour, ou un flux gratuit qui clignote toutes les 12 à 24 heures.

Les deux sont dangereuses dans l'environnement FX de 2026. Des paires comme USD/JPY ont bougé de 0,95 % rien que le 6 mai après l'intervention de Tokyo, et les devises émergentes oscillent régulièrement de plus de 1 % en intraday. Une boutique qui affiche un taux vieux de 24 heures vend avec une remise ou un surcoût de 1 à 2 % selon le sens du vent — et c'est avant le spread que la passerelle ajoute au règlement. Une API de taux de change multidevise pour WooCommerce en temps réel avec une couche de cache saine maintient les prix affichés à des fractions de pourcentage du mid-market, à chaque fois.

L'autre raison : la compatibilité de la passerelle. Stripe, PayPal et la plupart des grosses passerelles ont leurs propres listes de devises de règlement supportées. Si votre boutique affiche des prix dans 15 devises mais que Stripe ne règle qu'en 6, il faut savoir quelle conversion arrive, où, et à quel taux — et ce calcul est nettement plus simple quand votre couche est la source de vérité FX, pas une passerelle boîte noire.

Les Trois Patterns d'Intégration

Il y a trois endroits où brancher une API de devises sur une boutique WooCommerce, chacun avec ses arbitrages :

  1. S'accrocher à un plugin de switcher existant — chemin le plus rapide, fonctionne avec CURCY, FOX, Aelia, VillaTheme et la plupart des plugins populaires. Surface de code minimale mais vous êtes lié à la cadence de mise à jour du plugin et à son UI.
  2. Couche custom à base de filtres avec les hooks core de WooCommerce — contrôle total sur quels prix sont convertis (régulier, promo, livraison, taxe), mais plus de code et l'UI à votre charge.
  3. Hybride : le plugin gère l'UI, votre code gère les taux — le meilleur des deux mondes pour les boutiques déjà sur un plugin de switcher. Le plugin affiche le switcher ; votre tâche planifiée pousse les taux dans son stockage.

On va construire les trois.

Pattern 1 : Flux Personnalisé pour un Plugin de Switcher Existant

La plupart des plugins de devises populaires de WooCommerce exposent un filtre ou un action hook pour des sources de taux personnalisées. Le CURCY de VillaTheme utilise wmc_get_currency_exchange_rates. FOX utilise woocs_currencies_array. Aelia utilise wc_aelia_cs_exchange_rates_request_args. Le pattern est le même partout — vous interceptez les taux que le plugin irait chercher chez ses fournisseurs intégrés et vous les remplacez par les vôtres.

Voici un exemple complet pour CURCY (la même forme fonctionne pour FOX en changeant le nom du filtre). Posez ça dans un petit mu-plugin ou dans le functions.php du thème :

<?php
/**
 * Plugin Name: Finexly Rates for CURCY
 * Description: Pulls real-time WooCommerce multi-currency exchange rates from Finexly.
 */

add_filter( 'wmc_get_currency_exchange_rates', 'finexly_curcy_rates', 10, 1 );

function finexly_curcy_rates( $rates ) {
    $cached = get_transient( 'finexly_curcy_rates' );
    if ( false !== $cached ) {
        return wp_parse_args( $cached, $rates );
    }

    $base    = get_woocommerce_currency(); // e.g. "USD"
    $symbols = array_keys( $rates );        // e.g. ["EUR","GBP","JPY",...]
    $api_key = defined( 'FINEXLY_API_KEY' ) ? FINEXLY_API_KEY : '';

    if ( empty( $api_key ) || empty( $symbols ) ) {
        return $rates; // graceful fall-through to plugin defaults
    }

    $url = add_query_arg( array(
        'apikey'     => $api_key,
        'base'       => $base,
        'currencies' => implode( ',', $symbols ),
    ), 'https://api.finexly.com/v1/latest' );

    $response = wp_remote_get( $url, array( 'timeout' => 8 ) );

    if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
        return $rates;
    }

    $body = json_decode( wp_remote_retrieve_body( $response ), true );
    if ( empty( $body['rates'] ) ) {
        return $rates;
    }

    // CURCY expects an associative array of CODE => rate (relative to base).
    $fresh = array();
    foreach ( $body['rates'] as $code => $rate ) {
        $fresh[ $code ] = (float) $rate;
    }

    set_transient( 'finexly_curcy_rates', $fresh, HOUR_IN_SECONDS );
    return wp_parse_args( $fresh, $rates );
}

Quelques détails qui comptent. D'abord, l'appel wp_remote_get a un timeout de 8 secondes — assez long pour absorber une réponse lente, assez court pour que PHP-FPM ne tombe pas si l'amont est dégradé. Ensuite, à n'importe quel échec (erreur réseau, non-200, payload manquant) on renvoie les $rates par défaut du plugin plutôt qu'un tableau vide. Un checkout dégradé vaut mieux qu'un checkout cassé. Enfin, le transient met les réponses réussies en cache une heure, c'est largement assez frais pour le retail et ça vous garde bien en dessous de tout quota d'API.

Définissez votre clé dans wp-config.php :

define( 'FINEXLY_API_KEY', 'your_api_key_here' );

C'est tout. Le switcher CURCY s'affiche dans le header, les clients choisissent une devise, et les prix se recalculent avec des taux frais venus de votre appel API plutôt que du flux Yahoo Finance par défaut que CURCY met à jour toutes les 12 heures.

Pattern 2 : Multidevise From Scratch avec les Filtres Core de WooCommerce

Si vous ne voulez pas de plugin dans la boucle — ou que vous montez une boutique B2B custom où le switcher est un réglage de compte plutôt qu'un dropdown d'en-tête — vous pouvez construire toute la couche multidevise avec deux filtres core : woocommerce_currency et raw_woocommerce_price.

woocommerce_currency permet de changer la devise active par requête. raw_woocommerce_price permet de transformer chaque prix que WooCommerce affiche. Combinez-les avec une variable de session et vous avez un switcher de devises fonctionnel en ~60 lignes :

<?php

class Finexly_WC_Currency {

    const SESSION_KEY = 'finexly_active_currency';
    const TRANSIENT   = 'finexly_rates';

    public function __construct() {
        add_action( 'init',                          array( $this, 'maybe_switch_currency' ) );
        add_filter( 'woocommerce_currency',          array( $this, 'active_currency' ) );
        add_filter( 'raw_woocommerce_price',         array( $this, 'convert_price' ) );
        add_filter( 'woocommerce_package_rates',     array( $this, 'convert_shipping' ), 10, 2 );
    }

    public function maybe_switch_currency() {
        if ( isset( $_GET['set_currency'] ) ) {
            $code = strtoupper( sanitize_text_field( $_GET['set_currency'] ) );
            if ( array_key_exists( $code, $this->rates() ) ) {
                WC()->session->set( self::SESSION_KEY, $code );
            }
        }
    }

    public function active_currency( $default ) {
        $code = WC()->session ? WC()->session->get( self::SESSION_KEY ) : null;
        return $code ?: $default;
    }

    public function convert_price( $price ) {
        $code  = $this->active_currency( get_option( 'woocommerce_currency' ) );
        $rates = $this->rates();
        if ( ! isset( $rates[ $code ] ) ) {
            return $price;
        }
        return (float) $price * (float) $rates[ $code ];
    }

    public function convert_shipping( $rates, $package ) {
        $code     = $this->active_currency( get_option( 'woocommerce_currency' ) );
        $fx       = $this->rates();
        if ( ! isset( $fx[ $code ] ) ) {
            return $rates;
        }
        foreach ( $rates as $rate ) {
            $rate->cost = (float) $rate->cost * (float) $fx[ $code ];
            foreach ( $rate->taxes as &$tax ) {
                $tax = (float) $tax * (float) $fx[ $code ];
            }
        }
        return $rates;
    }

    private function rates() {
        $cached = get_transient( self::TRANSIENT );
        return is_array( $cached ) ? $cached : array();
    }
}

new Finexly_WC_Currency();

Le transient est rempli par une tâche planifiée séparée (section suivante), donc cette classe ne fait jamais d'appel réseau dans une requête — chaque chargement de page est un get_transient unique, ce qui dans des environnements avec object cache coûte ~0,1 ms. C'est la différence entre ajouter 8 ms au TTFB sur chaque page non-cachée et ajouter 800 ms une fois par heure à un seul worker cron.

Deux choses à ajouter pour la prod : précision et arrondi. WooCommerce stocke les prix en décimal ; multiplier une valeur decimal(13,4) par un taux 1.10851234 et restocker en decimal(13,4) perd silencieusement de la précision. Pour les articles à bas prix (SKU à moins d'un dollar, pots à pourboire, micro-dons), il vous faut au moins 8 décimales de précision interne, avec arrondi à l'unité minimale naturelle de la devise uniquement à l'affichage. Le helper wc_price() de WooCommerce gère l'arrondi d'affichage ; ce que vous devez faire, c'est garder les floats sous-jacents assez larges.

Pattern 3 : Tâche de Refresh Action Scheduler avec Protection Anti-Stampede

Les deux patterns précédents lisent un transient. Quelque chose doit y écrire. Le bon outil dans WooCommerce, c'est Action Scheduler — l'exécuteur de tâches livré avec WooCommerce — parce qu'il est fiable, fait des retries automatiques et gère mieux la concurrence que wp_schedule_event.

<?php

/**
 * Plugin Name: Finexly Rates Refresh
 * Description: Refreshes WooCommerce multi-currency exchange rates from Finexly hourly.
 */

add_action( 'init', function () {
    if ( false === as_next_scheduled_action( 'finexly_refresh_rates' ) ) {
        as_schedule_recurring_action(
            time() + 60,
            HOUR_IN_SECONDS,
            'finexly_refresh_rates',
            array(),
            'finexly'
        );
    }
} );

add_action( 'finexly_refresh_rates', 'finexly_do_refresh' );

function finexly_do_refresh() {
    // Stampede lock: only one worker at a time.
    $lock_key = 'finexly_refresh_lock';
    if ( get_transient( $lock_key ) ) {
        return;
    }
    set_transient( $lock_key, 1, 30 );

    try {
        $base       = get_option( 'woocommerce_currency', 'USD' );
        $supported  = apply_filters( 'finexly_currencies', array(
            'USD','EUR','GBP','JPY','CHF','CAD','AUD','CNY','INR','BRL','MXN','SEK','NOK','SGD','HKD','TRY','ZAR','PLN'
        ) );

        $url = add_query_arg( array(
            'apikey'     => FINEXLY_API_KEY,
            'base'       => $base,
            'currencies' => implode( ',', $supported ),
        ), 'https://api.finexly.com/v1/latest' );

        $response = wp_remote_get( $url, array( 'timeout' => 10 ) );
        if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
            // Don't blow away existing rates on a transient upstream failure.
            error_log( 'Finexly refresh failed: ' . wp_remote_retrieve_response_code( $response ) );
            return;
        }

        $body = json_decode( wp_remote_retrieve_body( $response ), true );
        if ( empty( $body['rates'] ) ) {
            return;
        }

        $rates = array_map( 'floatval', $body['rates'] );
        $rates[ $base ] = 1.0;

        set_transient( 'finexly_rates', $rates, 3 * HOUR_IN_SECONDS );

        // Persist to options as a durable fallback. Object cache flushes
        // would otherwise wipe transients and force a cold checkout.
        update_option( 'finexly_rates_persistent', array(
            'fetched_at' => time(),
            'base'       => $base,
            'rates'      => $rates,
        ), false );
    } finally {
        delete_transient( $lock_key );
    }
}

Trois patterns à noter. Le verrou anti-stampede empêche deux workers parallèles de marteler l'amont quand Action Scheduler prend du retard et empile deux tâches à la suite. Le TTL du transient ×3 vs intervalle de refresh d'une heure est un buffer voulu — si un refresh échoue, la valeur cachée tient jusqu'à la prochaine tentative. Le fallback persistant en options est la troisième couche : si l'object cache se vide pour une raison quelconque, votre boutique a toujours un jeu de taux utilisable d'il y a 24 heures max, et votre méthode rates() peut s'y rabattre avant de renvoyer vide.

Pour creuser l'architecture de cache elle-même, les patterns de notre guide caching et gestion d'erreurs pour API de devises se transposent directement à WooCommerce.

Tester Votre Intégration Multidevise

Les boutiques WooCommerce cassent en production de trois façons prévisibles, et toutes les trois sont testables. Voici une suite PHPUnit minimale avec WP_Mock et Mockery :

<?php

class Finexly_WC_Currency_Test extends WP_Mock\Tools\TestCase {

    public function test_convert_price_with_known_rate() {
        WP_Mock::userFunction( 'get_transient' )
            ->with( 'finexly_rates' )
            ->andReturn( array( 'EUR' => 0.92, 'GBP' => 0.79 ) );

        WP_Mock::userFunction( 'get_option' )
            ->with( 'woocommerce_currency' )
            ->andReturn( 'USD' );

        $svc = new Finexly_WC_Currency();
        $svc->set_active_currency( 'EUR' );

        $this->assertEqualsWithDelta( 92.0, $svc->convert_price( 100.0 ), 0.0001 );
    }

    public function test_returns_original_price_when_rate_missing() {
        WP_Mock::userFunction( 'get_transient' )
            ->andReturn( array() );

        $svc = new Finexly_WC_Currency();
        $svc->set_active_currency( 'XYZ' ); // unsupported code

        $this->assertEquals( 100.0, $svc->convert_price( 100.0 ) );
    }

    public function test_refresh_handles_upstream_500() {
        WP_Mock::userFunction( 'wp_remote_get' )
            ->andReturn( array( 'response' => array( 'code' => 500 ) ) );
        WP_Mock::userFunction( 'set_transient' )->never();
        WP_Mock::userFunction( 'update_option' )->never();

        finexly_do_refresh();
        // Test passes if no fatal error and we don't overwrite cached rates.
    }
}

Le troisième test, c'est celui que la plupart des équipes zappent. À peu près tous les bugs de devises WooCommerce que j'ai vus en prod se résument à « l'amont a renvoyé une erreur et on a caché la réponse vide. » Vérifier que vous n'écrivez pas de la merde en cas d'échec a plus de valeur que vérifier que vous écrivez juste en cas de succès.

Pièges Courants et Comment les Éviter

Compatibilité des devises de la passerelle. Que votre boutique affiche des prix en TRY ne veut pas dire que Stripe règlera en TRY. Avant d'afficher une devise dans le switcher, regardez WC()->payment_gateways() et filtrez l'intersection des devises supportées par vos passerelles actives. La comparaison entre l'API de quotes FX de Stripe et une API de devises dédiée couvre les arbitrages en détail.

Remboursements et modifications de commande. Un remboursement émis en EUR trois semaines après une commande en base USD n'utilisera pas le taux du jour — il utilisera celui que la passerelle capture au moment du remboursement. Stockez le taux de la commande dans la meta de l'order (update_post_meta( $order_id, '_finexly_rate', $rate )) pour que le reporting puisse réconcilier. Utile aussi pour la couche d'intégration au logiciel comptable.

Arrondi. Arrondissez à l'affichage, pas au stockage. Stocker 9.99 * 0.92 = 9.1908 arrondi à 9.19 puis reconvertir en USD donne 9.989..., pas 9.99. Précision interne large + arrondi à l'affichage est le seul pattern sain.

Validité ISO 4217. Ne faites pas confiance à l'input utilisateur. Validez tous les codes devises contre la liste ISO 4217 avant de les écrire en session.

Choisir l'API : Gratuit vs. Payant

Les fournisseurs intégrés à WooCommerce (palier gratuit Open Exchange Rates, le déprécié CurrencyLayer, etc.) rafraîchissent toutes les 12-24 heures et plafonnent en quotas faibles. Pour une petite boutique avec un produit et dix commandes par mois, ça suffit ; pour quoi que ce soit avec du trafic, vous voudrez un fournisseur dédié avec au moins un refresh horaire et un quota qui ne vous oblige pas à rationner. Notre comparaison API de devises gratuit vs payant pour 2026 détaille les vrais chiffres.

L'API Finexly cible précisément ce cas — 170+ devises, refresh à 60 secondes pour les majors, horaire pour le long-tail, et un palier gratuit de 1 000 requêtes/mois qui suffit à une tâche Action Scheduler par heure avec de la marge pour les retries. Vous pouvez comparer les API de devises côte à côte ou vous inscrire gratuitement pour la tester en boutique de dev.

Foire Aux Questions

WooCommerce supporte-t-il le multidevise nativement ? Partiellement. WooCommerce supporte une seule devise de base pour les commandes et reversements. L'affichage multidevise et le checkout exigent soit un plugin de switcher (CURCY, FOX, Aelia, etc.), soit du code custom utilisant les filtres woocommerce_currency et raw_woocommerce_price, et les deux approches ont besoin d'une source de taux que vous fournissez.

À quelle fréquence dois-je rafraîchir les taux dans WooCommerce ? Pour le retail, toutes les heures suffit. Pour le B2B avec des factures à fort montant, 15-30 minutes est plus sûr. Plus agressif, ça vaut rarement le quota — les passerelles re-cotent au règlement de toute façon, donc l'écart entre un taux de 5 minutes et un de 30 est noyé dans le spread.

Puis-je intégrer une API de devises custom au plugin de switcher que j'utilise déjà ? Oui. CURCY expose wmc_get_currency_exchange_rates, FOX expose woocs_currencies_array, Aelia a wc_aelia_cs_exchange_rates_request_args. Branchez-vous sur le filtre, renvoyez vos taux, et l'UI du plugin continue de marcher.

Que se passe-t-il si l'API est en panne pendant un checkout ? Avec l'architecture ci-dessus, rien de visible côté client. La couche de pricing lit un transient rempli par une tâche en arrière-plan ; si le dernier refresh a échoué, le taux précédent est encore en cache et l'option persistante porte un fallback de 24 heures. Le checkout continue au dernier taux valide connu.

Comment gérer les remboursements multi-devises ? Stockez le taux utilisé au moment de la commande dans la meta de l'order. Au remboursement, calculez le montant avec ce taux-là, pas celui du jour, pour que le client soit remboursé dans la devise où il a payé. Le P&L FX se réconcilie séparément côté compta.

Pour Conclure

Une intégration solide d'API de taux de change multidevise pour WooCommerce ne tourne pas vraiment autour de l'appel API — elle tourne autour des quatre couches qui l'entourent : cache, planification, fallbacks, tests. Faites-les bien et la boutique garde des prix honnêtes dans 50+ devises sans la moindre alerte de nuit.

Prêt à intégrer des taux de change en temps réel à votre boutique WooCommerce ? Récupérez votre clé Finexly gratuite — sans carte bancaire. Démarrez avec 1 000 requêtes gratuites par mois, collez les snippets ci-dessus dans un mu-plugin, et vous aurez le multidevise en prod avant midi. Quand vous dépasserez le palier gratuit, les plans tarifaires montent linéairement avec la boutique, pas exponentiellement.

Vlado Grigirov

Senior Currency Markets Analyst & Financial Strategist

Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...

View full profile →

Partager cet article