Torna al Blog

WooCommerce Multivaluta: Come Integrare un'API di Cambio in Tempo Reale (Guida Sviluppatori 2026)

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

WooCommerce fa girare circa un terzo dei negozi online del mondo, e nel momento in cui uno di quei negozi vuole vendere oltre confine si ripresenta sempre la stessa domanda: come mantieni prezzi, tasse e payout corretti in dodici valute senza controllare il mercato FX a mano ogni mattina? La risposta onesta è collegare un'API multivaluta di tassi di cambio per WooCommerce al negozio — ma farlo bene è più sfumato che lasciare un plugin attivo e dimenticarsene. Devi pensare alla cadenza di refresh, alle cache stampede, alla compatibilità del payment gateway, all'arrotondamento e a cosa succede quando l'upstream restituisce un 503 nel mezzo di un checkout.

Questa guida percorre tre pattern di produzione che gli sviluppatori usano davvero nel 2026, con PHP funzionante per ciascuno. Collegheremo l'API Finexly a WooCommerce in tre modi diversi: come feed personalizzato per un plugin di switcher esistente, come livello a filtri costruito da zero, e come job di Action Scheduler con locking adeguato. Alla fine avrai un pattern adatto a qualunque stack — WooCommerce vanilla, WooCommerce + plugin switcher, o un build B2B totalmente custom.

Perché un'API di Cambio in Tempo Reale Conta per WooCommerce

Pronto all'uso, WooCommerce imposta un'unica valuta base in woocommerce_currency e dà per scontato che ogni ordine, rimborso e payout venga regolato in quella valuta. Il supporto multivaluta è uno strato sopra: uno switcher in header, totali del carrello ricalcolati e (a volte) elaborazione di pagamento localizzata. Quello strato ha bisogno di tassi di cambio da qualche parte, e "qualche parte" tende a essere una di due cose — un tasso inserito a mano che nessuno si ricorda di aggiornare, o un feed gratuito di un provider che ticchetta una volta ogni 12-24 ore.

Entrambi sono pericolosi nell'ambiente FX del 2026. Coppie come USD/JPY si sono mosse dello 0,95% solo il 6 maggio dopo l'intervento di Tokyo, e le valute dei mercati emergenti oscillano regolarmente più dell'1% intraday. Un negozio che mostra un tasso vecchio di 24 ore sta vendendo con uno sconto o un ricarico dell'1-2% a seconda di come gira il vento — e questo prima dello spread che il gateway aggiunge alla regolazione. Un'API multivaluta di tassi di cambio per WooCommerce in tempo reale con uno strato di cache sensato mantiene i prezzi mostrati entro frazioni di punto percentuale dal mid-market, sempre.

L'altro motivo per cui conta: la compatibilità del gateway di pagamento. Stripe, PayPal e i grandi gateway hanno la propria lista di valute di settlement supportate. Se il negozio espone prezzi in 15 valute ma Stripe regola solo in 6, devi sapere quale conversione succede, dove e a che tasso — e quel calcolo è molto più semplice quando il tuo livello è la fonte di verità FX, non un gateway scatola nera.

I Tre Pattern di Integrazione

Ci sono tre punti dove agganciare un'API di valuta a un negozio WooCommerce, ognuno con il suo trade-off:

  1. Agganciarsi a un plugin di switcher esistente — la via più rapida, funziona con CURCY, FOX, Aelia, VillaTheme e la maggior parte dei plugin popolari. Superficie di codice piccolissima, ma sei legato alla cadenza di update del plugin e alla sua UI.
  2. Livello a filtri custom usando gli hook core di WooCommerce — controllo totale su quali prezzi convertire (regolare, saldo, spedizione, IVA), ma scrivi più codice e ti tieni l'UI da solo.
  3. Ibrido: il plugin gestisce l'UI, il tuo codice gestisce i tassi — il meglio dei due mondi per chi è già su un plugin di switcher. Il plugin renderizza lo switcher; il tuo job pianificato spinge i tassi nello storage del plugin.

Costruiremo tutti e tre.

Pattern 1: Feed Tassi Custom per un Plugin di Switcher Esistente

La maggior parte dei plugin di valute popolari per WooCommerce espone un filtro o un action hook per sorgenti tassi custom. CURCY di VillaTheme usa wmc_get_currency_exchange_rates. FOX usa woocs_currencies_array. Aelia usa wc_aelia_cs_exchange_rates_request_args. Il pattern è lo stesso ovunque — intercetti i tassi che il plugin recupererebbe dai suoi provider integrati e li sostituisci con i tuoi.

Ecco un esempio completo per CURCY (la stessa forma vale per FOX cambiando il nome del filtro). Buttalo in un piccolo mu-plugin o nel functions.php del tema:

<?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 );
}

Qualche dettaglio rilevante. Primo, wp_remote_get ha un timeout di 8 secondi — abbastanza lungo per assorbire una risposta lenta, abbastanza corto perché PHP-FPM non vada giù se l'upstream è degradato. Secondo, su qualunque errore (errore di rete, non-200, payload mancante) restituiamo i $rates di default del plugin invece di un array vuoto. Un checkout degradato è meglio di uno rotto. Terzo, il transient mette in cache le risposte di successo per un'ora, freschezza più che sufficiente per il retail e ben sotto qualunque quota API.

Definisci la chiave in wp-config.php:

define( 'FINEXLY_API_KEY', 'your_api_key_here' );

Fine. Lo switcher di CURCY appare nell'header, il cliente sceglie una valuta, i prezzi si ricalcolano con tassi freschi dalla tua chiamata API invece del feed Yahoo Finance di default che CURCY aggiorna ogni 12 ore.

Pattern 2: Multivaluta Da Zero con i Filtri Core di WooCommerce

Se non vuoi un plugin di mezzo — o stai costruendo un B2B custom dove lo switcher è parte di un'impostazione a livello account e non un dropdown in header — puoi costruire l'intero livello multivaluta con due filtri core: woocommerce_currency e raw_woocommerce_price.

woocommerce_currency ti permette di cambiare la valuta attiva per richiesta. raw_woocommerce_price ti permette di trasformare ogni prezzo che WooCommerce mostra. Aggiungi una variabile di sessione e hai uno switcher di valute funzionante in circa 60 righe:

<?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();

Il transient lo popola un job pianificato a parte (prossima sezione), quindi questa classe non fa mai una chiamata di rete dentro una request — ogni page load è una singola lookup get_transient, che in ambienti con object cache attivo costa ~0,1ms. È la differenza tra aggiungere 8ms al TTFB su ogni pagina non in cache e aggiungere 800ms una volta all'ora a un singolo worker cron.

Due cose da aggiungere per la produzione: precisione e arrotondamento. WooCommerce salva i prezzi come decimali; moltiplicare un valore decimal(13,4) per un tasso 1.10851234 e risalvarlo come decimal(13,4) perde silenziosamente precisione. Per articoli a basso prezzo (SKU sotto un dollaro, vasi delle mance, micro-donazioni) servono almeno 8 cifre decimali di precisione interna, con arrotondamento all'unità minima naturale della valuta solo a livello di display. L'helper wc_price() di WooCommerce gestisce l'arrotondamento al display per te; quello che devi fare è tenere abbastanza larghi i float sottostanti.

Pattern 3: Job di Refresh Action Scheduler con Protezione Anti-Stampede

Entrambi i pattern sopra leggono da un transient. Qualcosa deve scrivere nel transient. Lo strumento giusto in WooCommerce è Action Scheduler — il job runner che WooCommerce porta con sé — perché è affidabile, fa retry automatici e gestisce la concorrenza meglio di 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 );
    }
}

Tre pattern da notare. Il lock anti-stampede evita che due worker paralleli martellino l'upstream quando Action Scheduler parte in ritardo e accoda due job a catena. Il TTL del transient ×3 rispetto all'intervallo di refresh di un'ora è un buffer voluto — se un refresh fallisce, il valore in cache sopravvive fino al tentativo successivo. Il fallback persistente in options è il terzo strato: se l'object cache si svuota per qualunque motivo, il negozio ha ancora un set di tassi usabile vecchio fino a 24 ore, e il metodo rates() può ripiegare lì prima di restituire vuoto.

Se vuoi approfondire l'architettura di caching in sé, i pattern del nostro guida caching ed error handling per API valutarie si traducono direttamente in WooCommerce.

Testare l'Integrazione Multivaluta

I negozi WooCommerce si rompono in produzione in tre modi prevedibili, e tutti e tre sono testabili. Ecco una suite minima PHPUnit con WP_Mock e 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.
    }
}

Il terzo test è quello che la maggior parte dei team salta. Quasi tutti i bug di valuta in WooCommerce che ho visto in produzione si riducono a "l'upstream ha restituito un errore e abbiamo messo in cache la risposta vuota". Verificare che non scrivi spazzatura su un fallimento ha più valore di verificare che scrivi la cosa giusta in caso di successo.

Trabocchetti Comuni e Come Evitarli

Compatibilità della valuta del gateway. Che il negozio mostri prezzi in TRY non vuol dire che Stripe regoli in TRY. Prima di esporre una valuta nello switcher, controlla WC()->payment_gateways() e filtra sull'intersezione delle valute supportate dai tuoi gateway attivi. La comparazione tra Stripe FX Quotes API e API valutaria dedicata entra nei trade-off in dettaglio.

Rimborsi e modifiche d'ordine. Un rimborso emesso in EUR tre settimane dopo un ordine con base USD non userà il tasso di oggi — userà quello che il gateway cattura al momento del rimborso. Salva il tasso del momento dell'ordine nelle meta dell'order (update_post_meta( $order_id, '_finexly_rate', $rate )) così il reporting può quadrare. Utile anche per il livello di integrazione con il software contabile.

Arrotondamento. Arrotonda alla visualizzazione, non al salvataggio. Salvare 9.99 * 0.92 = 9,1908 arrotondato a 9,19 e riconvertirlo in USD dà 9,989..., non 9,99. Precisione interna larga + arrotondamento al display è l'unico pattern sano.

Validità ISO 4217. Non fidarti dell'input utente. Valida tutti i codici valuta contro la lista ISO 4217 prima di metterli in sessione.

Scegliere l'API: Free vs. Pagata

I provider integrati in WooCommerce (free tier di Open Exchange Rates, il deprecato CurrencyLayer ecc.) aggiornano ogni 12-24 ore e tagliano su quote piccole. Per un piccolo negozio con un prodotto e dieci ordini al mese va bene; per qualcosa con traffico vorrai un provider dedicato con almeno refresh orario e una quota che non ti costringa a razionare le chiamate. La nostra comparazione free vs. pagata di API valutarie per il 2026 riporta i numeri reali.

L'API Finexly è pensata esattamente per questo caso — 170+ valute, refresh a 60 secondi sui major, orario sui long-tail, e un free tier da 1.000 richieste/mese che basta per un job di Action Scheduler all'ora con margine per i retry. Puoi comparare le API valutarie fianco a fianco o registrarti gratis per testarla in dev.

Domande Frequenti

WooCommerce supporta nativamente il multivaluta? In parte. WooCommerce supporta una sola valuta base per ordini e payout. La visualizzazione multivaluta e il checkout richiedono o un plugin di switcher (CURCY, FOX, Aelia ecc.) o codice custom con i filtri woocommerce_currency e raw_woocommerce_price, e in entrambi i casi serve una sorgente di tassi che fornisci tu.

Con che frequenza dovrei aggiornare i tassi in WooCommerce? Per il retail va bene ogni ora. Per B2B con fatture di valore alto, 15-30 minuti è più sicuro. Più aggressivo raramente vale la quota — i gateway di pagamento ricalcolano comunque al settlement, quindi la differenza tra un tasso di 5 minuti e uno di 30 sparisce nello spread.

Posso integrare un'API di valuta custom con il plugin di switcher che già uso? Sì. CURCY espone wmc_get_currency_exchange_rates, FOX espone woocs_currencies_array, Aelia ha wc_aelia_cs_exchange_rates_request_args. Aggancia il filtro, restituisci i tuoi tassi e l'UI del plugin continua a funzionare.

Cosa succede se l'API di cambio è giù durante un checkout? Con l'architettura sopra, niente di visibile per il cliente. Il livello dei prezzi legge da un transient popolato da un job in background; se l'ultimo refresh è fallito, il tasso precedente è ancora in cache e l'option persistente tiene un fallback di 24 ore. Il checkout prosegue con l'ultimo tasso valido conosciuto.

Come gestisco i rimborsi tra valute diverse? Salva nelle meta dell'order il tasso usato al momento dell'ordine. Al rimborso calcola l'importo con quel tasso, non quello di oggi, in modo che il cliente venga rimesso intero nella valuta in cui ha pagato. Il P&L FX lo riconcilia il tuo sistema contabile a parte.

Per Chiudere

Una solida integrazione API multivaluta di tassi di cambio per WooCommerce non riguarda davvero la chiamata API — riguarda i quattro strati attorno: caching, scheduling, fallback, test. Sistema quelli e il negozio terrà prezzi corretti in 50+ valute senza un singolo alert notturno.

Pronto a integrare tassi di cambio in tempo reale nel tuo negozio WooCommerce? Prendi la tua chiave Finexly gratuita — senza carta di credito. Parti con 1.000 richieste gratis al mese, butta gli snippet sopra in un mu-plugin e avrai il multivaluta in produzione prima di pranzo. Quando supererai il free tier, i piani di prezzo scalano linearmente con il negozio, non esponenzialmente.

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 →

Condividi questo articolo