Voltar ao Blog

WooCommerce Multimoeda: Como Integrar uma API de Câmbio em Tempo Real (Guia para Desenvolvedores 2026)

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

WooCommerce roda aproximadamente um terço das lojas online do mundo, e no momento em que uma dessas lojas decide vender entre fronteiras, surge sempre a mesma pergunta: como manter preços, impostos e repasses honestos em doze moedas sem checar o mercado de câmbio à mão toda manhã? A resposta sincera é plugar uma API de câmbio multimoeda para WooCommerce na sua loja — mas fazê-la corretamente tem mais nuances do que largar um plugin e ir embora. Você precisa pensar em cadência de atualização, cache stampede, compatibilidade de gateway de pagamento, arredondamento, e o que acontece quando o provedor upstream devolve um 503 no meio de um checkout.

Este guia percorre três padrões de produção que desenvolvedores realmente usam em 2026, com PHP funcional para cada um. Vamos conectar a API Finexly ao WooCommerce de três formas diferentes: como feed personalizado para um plugin de seletor de moedas existente, como camada baseada em filtros construída do zero, e como job do Action Scheduler com travamento adequado. No final, você terá um padrão que serve para qualquer stack — WooCommerce limpo, WooCommerce com plugin de switcher, ou um build B2B totalmente custom.

Por Que uma API de Câmbio em Tempo Real Importa para WooCommerce

Pronto pra usar, o WooCommerce define uma única moeda base em woocommerce_currency e assume que todos os pedidos, reembolsos e repasses são liquidados nessa moeda. O suporte multimoeda é uma camada que vem por cima: um seletor no header, totais recalculados no carrinho e (às vezes) processamento de pagamento localizado. Essa camada precisa de taxas de câmbio vindas de algum lugar, e "algum lugar" geralmente cai em uma de duas opções — uma taxa inserida manualmente que ninguém lembra de atualizar, ou um feed gratuito que pisca uma vez a cada 12-24 horas.

As duas são perigosas no ambiente cambial de 2026. Pares como USD/JPY se moveram 0,95% só no dia 6 de maio depois da intervenção de Tóquio, e moedas de mercados emergentes oscilam mais de 1% intradiário com regularidade. Uma loja exibindo uma taxa com 24 horas de atraso está vendendo com desconto ou sobrepreço de 1-2% dependendo do vento — e isso antes do spread que o gateway adiciona na liquidação. Uma API de câmbio multimoeda em tempo real para WooCommerce com camada de cache razoável mantém seus preços exibidos a frações de um por cento do mid-market, sempre.

A outra razão pela qual isso importa: compatibilidade de gateway de pagamento. Stripe, PayPal e a maioria dos gateways grandes têm suas próprias listas de moedas de liquidação suportadas. Se sua loja exibe preços em 15 moedas mas o Stripe só liquida em 6, você precisa saber qual conversão acontece, onde, e a que taxa — e essa conta é muito mais fácil quando sua camada é a fonte da verdade para FX, não um gateway tipo caixa-preta.

Os Três Padrões de Integração

Existem três lugares onde você pode plugar uma API de câmbio numa loja WooCommerce, cada um com tradeoffs distintos:

  1. Engatar num plugin de switcher existente — caminho mais rápido, funciona com CURCY, FOX, Aelia, VillaTheme e a maioria dos populares. Superfície de código mínima, mas você fica preso à cadência de atualização do plugin e à UI da loja.
  2. Camada baseada em filtros usando hooks core do WooCommerce — controle total sobre quais preços são convertidos (regular, promoção, frete, imposto), mas você escreve mais código e cuida da UI da loja.
  3. Híbrido: o plugin gerencia a UI, seu código gerencia as taxas — o melhor dos dois mundos para lojas que já usam um plugin de switcher. O plugin renderiza o switcher; seu job agendado empurra as taxas para o storage do plugin.

Vamos construir os três.

Padrão 1: Feed Personalizado para um Plugin de Switcher Existente

A maioria dos plugins populares de moedas para WooCommerce expõe um filtro ou action hook para fontes customizadas de taxas. O CURCY da VillaTheme usa wmc_get_currency_exchange_rates. O FOX usa woocs_currencies_array. O Aelia usa wc_aelia_cs_exchange_rates_request_args. O padrão é o mesmo em todos — você intercepta as taxas que o plugin buscaria nos provedores embutidos e substitui pelas suas.

Aqui vai um exemplo completo para CURCY (a mesma forma serve para FOX trocando o nome do filtro). Cole isso num mu-plugin pequeno ou no functions.php do 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 );
}

Alguns detalhes importantes. Primeiro, o wp_remote_get tem timeout de 8 segundos — longo o bastante para absorver uma resposta lenta, curto o bastante para que o PHP-FPM não capote se o upstream estiver degradado. Segundo, em qualquer falha (erro de rede, não-200, payload faltando) devolvemos os $rates padrão do plugin em vez de array vazio. Um checkout degradado é melhor que um quebrado. Terceiro, o transient cacheia respostas de sucesso por uma hora, frescor de sobra para varejo e bem confortável dentro de qualquer cota de API.

Defina sua chave em wp-config.php:

define( 'FINEXLY_API_KEY', 'your_api_key_here' );

E pronto. O switcher do CURCY renderiza no header, o cliente escolhe a moeda, e os preços recalculam usando taxas frescas da sua chamada à API em vez do feed Yahoo Finance que o CURCY atualiza a cada 12 horas.

Padrão 2: Multimoeda do Zero com Filtros Core do WooCommerce

Se você não quer um plugin no caminho — ou está construindo uma loja B2B custom onde o switcher faz parte de uma configuração no nível da conta e não um dropdown no header — dá pra construir toda a camada multimoeda com dois filtros core: woocommerce_currency e raw_woocommerce_price.

woocommerce_currency te deixa trocar a moeda ativa por requisição. raw_woocommerce_price te deixa transformar todo preço que o WooCommerce exibe. Combine com uma variável de session e você tem um seletor de moeda funcional em ~60 linhas:

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

O transient é populado por um job agendado separado (próxima seção), então essa classe nunca faz chamada de rede dentro de uma requisição — toda carga de página é uma única get_transient, que em ambientes com object cache custa ~0,1ms. Essa é a diferença entre adicionar 8ms ao TTFB de toda página não-cacheada e adicionar 800ms uma vez por hora a um único worker do cron.

Duas coisas para adicionar em produção: precisão e arredondamento. WooCommerce guarda preços como decimais; multiplicar um valor decimal(13,4) por uma taxa 1.10851234 e salvar de volta como decimal(13,4) corta precisão silenciosamente. Para itens de baixo preço (SKUs sub-$1, gorjetas, micro-doações) você quer pelo menos 8 casas decimais de precisão interna, com arredondamento à unidade mínima natural da moeda apenas na camada de exibição. O helper wc_price() cuida do arredondamento de display por você; o que você precisa fazer é manter os floats subjacentes largos o suficiente.

Padrão 3: Job de Refresh com Action Scheduler e Proteção contra Stampede

Os dois padrões acima leem de um transient. Alguma coisa precisa escrever nesse transient. A ferramenta certa no WooCommerce é o Action Scheduler — o executor de jobs que vem com o WooCommerce — porque é confiável, tenta novamente sozinho, e lida com concorrência melhor 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 );
    }
}

Três padrões merecem destaque. O lock anti-stampede evita que dois workers paralelos martelem o upstream quando o Action Scheduler atrasa e enfileira dois jobs em sequência. O TTL do transient ×3 vs. intervalo de refresh de 1 hora é um buffer proposital — se um refresh falhar, o valor cacheado sobrevive até a próxima tentativa. O fallback persistente em options é a terceira camada: se o object cache for limpo por qualquer motivo, sua loja ainda tem um set de taxas usável de até 24 horas atrás, e seu método rates() pode recorrer a ele antes de devolver vazio.

Se quiser um mergulho mais fundo na arquitetura de cache, os padrões do nosso guia de cache e tratamento de erros para APIs de câmbio se traduzem direto para WooCommerce.

Testando Sua Integração Multimoeda

Lojas WooCommerce quebram em produção de três formas previsíveis, e as três são testáveis. Aqui está uma suite mínima com PHPUnit usando 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.
    }
}

O terceiro teste é o que mais time pula. Quase todo bug de moeda em WooCommerce que vi em produção se reduz a "o upstream devolveu erro e a gente cacheou a resposta vazia." Garantir que você não escreve lixo numa falha vale mais do que garantir que escreve a coisa certa em sucesso.

Armadilhas Comuns e Como Evitá-las

Compatibilidade de moeda do gateway. Sua loja exibir preços em TRY não significa que o Stripe vai liquidar em TRY. Antes de mostrar uma moeda no seu switcher, cheque WC()->payment_gateways() e filtre para a interseção das moedas que seus gateways ativos suportam. A comparação entre a API de cotações FX da Stripe e uma API de câmbio dedicada cobre os tradeoffs em detalhe.

Reembolsos e edições de pedido. Um reembolso emitido em EUR três semanas depois do pedido original em USD não vai usar a taxa de hoje — vai usar a que o gateway capturar no momento do reembolso. Salve a taxa do momento do pedido em meta da order (update_post_meta( $order_id, '_finexly_rate', $rate )) para que o reporting consiga conciliar. Isso também serve para a camada de integração com software contábil.

Arredondamento. Arredonde no display, não no storage. Salvar 9.99 * 0.92 = 9.1908 arredondado a 9.19 e converter de volta para USD dá 9.989..., não 9.99. Precisão interna larga mais arredondamento no display é o único padrão são.

Validade ISO 4217. Não confie em input do usuário. Valide todos os códigos de moeda contra a lista ISO 4217 antes de gravar em session.

Escolhendo a API: Grátis vs. Paga

Os provedores embutidos no WooCommerce (camada gratuita do Open Exchange Rates, o descontinuado CurrencyLayer, etc.) atualizam a cada 12-24 horas e travam em cotas pequenas. Pra uma loja pequena com um produto e dez pedidos por mês, ok; pra qualquer coisa com tráfego, vai querer um provedor dedicado com refresh ao menos por hora e cota que não te force a racionar chamadas. Nossa comparação grátis vs. paga de APIs de câmbio para 2026 traz números reais.

A API Finexly é desenhada exatamente para esse caso — 170+ moedas, refresh de 60 segundos para majors, hora a hora para long-tail, e um plano grátis de 1.000 chamadas/mês que dá pra um job do Action Scheduler por hora com folga pra retries. Você pode comparar APIs de câmbio lado a lado ou se cadastrar de graça para testar na sua loja de dev.

Perguntas Frequentes

O WooCommerce suporta multimoeda nativamente? Parcialmente. WooCommerce suporta uma única moeda base para pedidos e repasses. Exibição multimoeda e checkout exigem ou um plugin de switcher (CURCY, FOX, Aelia, etc.) ou código custom usando os filtros woocommerce_currency e raw_woocommerce_price, e qualquer abordagem precisa de uma fonte de taxas que você forneça.

Com que frequência devo atualizar as taxas no WooCommerce? Pra varejo, hora a hora basta. Pra B2B com faturas de alto valor, 15-30 minutos é mais seguro. Mais agressivo que isso raramente compensa a cota — gateways re-cotam na liquidação de qualquer jeito, então a diferença entre uma taxa de 5 e de 30 minutos vira ruído frente ao spread.

Consigo integrar uma API de câmbio custom com o plugin de switcher que já uso? Sim. CURCY expõe wmc_get_currency_exchange_rates, FOX expõe woocs_currencies_array, Aelia tem wc_aelia_cs_exchange_rates_request_args. Engata no filtro, devolve suas taxas, e a UI do plugin segue funcionando.

O que acontece se a API estiver fora durante o checkout? Com a arquitetura acima, nada visível pro cliente. A camada de pricing lê de um transient populado por um job em background; se o último refresh falhou, a taxa anterior continua cacheada e a option persistente guarda um fallback de 24 horas. O checkout segue na última taxa válida conhecida.

Como lidar com reembolsos entre moedas? Salve a taxa usada no momento do pedido na meta da order. No reembolso, calcule o valor com aquela taxa, não a de hoje, pra que o cliente seja ressarcido na moeda em que pagou. Seu sistema contábil concilia o P&L de FX em separado.

Encerrando

Uma integração sólida de API de câmbio multimoeda para WooCommerce não é sobre a chamada à API — é sobre as quatro camadas em volta dela: cache, agendamento, fallbacks e testes. Acerte essas e a loja mantém preços honestos em 50+ moedas sem um único alerta de madrugada.

Pronto pra integrar taxas de câmbio em tempo real na sua loja WooCommerce? Pegue sua chave grátis da Finexly — sem cartão de crédito. Comece com 1.000 requisições grátis por mês, cole os snippets acima num mu-plugin, e tem multimoeda no ar antes do almoço. Quando passar do plano grátis, os planos de preço escalam linearmente com a loja, não exponencialmente.

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 →

Compartilhar este artigo