Zurück zum Blog

WooCommerce Multi-Currency: So integrierst du eine Echtzeit-Wechselkurs-API (Entwicklerguide 2026)

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

WooCommerce treibt rund ein Drittel der Onlineshops weltweit, und sobald einer dieser Shops grenzüberschreitend verkaufen will, kommt dieselbe Frage auf: Wie hältst du Preise, Steuern und Auszahlungen in zwölf Währungen ehrlich, ohne jeden Morgen den Devisenmarkt von Hand zu prüfen? Die ehrliche Antwort lautet: eine WooCommerce-Multi-Currency-Wechselkurs-API an den Shop anbinden – aber das richtig zu tun, ist nuancierter, als ein Plugin reinzuwerfen und davonzulaufen. Du musst über Refresh-Kadenz, Cache-Stampedes, Payment-Gateway-Kompatibilität, Rundung und das Verhalten bei einem 503 vom Upstream mitten im Checkout nachdenken.

Dieser Guide arbeitet drei Produktionsmuster durch, die Entwickler 2026 wirklich einsetzen, mit funktionierendem PHP für jedes davon. Wir hängen die Finexly-API auf drei Wegen in WooCommerce ein: als Custom-Feed für ein bestehendes Currency-Switcher-Plugin, als from-scratch-Filterschicht und als Action-Scheduler-Job mit ordentlichem Locking. Am Ende hast du ein Muster, das zu jedem Stack passt – bare WooCommerce, WooCommerce mit Switcher-Plugin oder ein vollständig custom B2B-Build.

Warum eine Echtzeit-Wechselkurs-API für WooCommerce wichtig ist

Out of the box setzt WooCommerce eine einzige Basiswährung in woocommerce_currency und nimmt an, dass jede Bestellung, jede Rückerstattung und jeder Payout darin abgerechnet wird. Multi-Currency-Support ist eine Schicht obendrauf: ein Switcher im Header, neu berechnete Cart-Totals und (manchmal) lokalisierte Zahlungsabwicklung. Diese Schicht braucht Wechselkurse von irgendwoher, und „irgendwo" ist standardmäßig fast immer eines von zwei Dingen – ein manuell eingetragener Kurs, an den niemand mehr denkt, oder ein kostenloser Provider-Feed, der alle 12–24 Stunden tickt.

Beides ist im FX-Umfeld 2026 gefährlich. Paare wie USD/JPY haben sich allein am 6. Mai um 0,95 % bewegt – nach Tokios Intervention –, und Schwellenländerwährungen schwanken intraday regelmäßig über 1 %. Ein Shop, der einen 24 Stunden alten Kurs anzeigt, verkauft je nach Windrichtung mit 1–2 % Rabatt oder Aufschlag – und das bevor der Spread, den das Gateway beim Settlement draufpackt, ins Spiel kommt. Eine WooCommerce-Multi-Currency-Wechselkurs-API in Echtzeit mit einer vernünftigen Caching-Schicht hält deine angezeigten Preise jederzeit innerhalb von Bruchteilen eines Prozents am Mid-Market.

Der zweite Grund: Gateway-Kompatibilität. Stripe, PayPal und die meisten großen Gateways haben ihre eigene Liste unterstützter Settlement-Währungen. Wenn dein Shop Preise in 15 Währungen anzeigt, Stripe aber nur in 6 abrechnet, musst du wissen, welche Konvertierung wo zu welchem Kurs passiert – und diese Rechnung ist deutlich einfacher, wenn deine Schicht die FX-Wahrheit besitzt, nicht eine Black-Box-Gateway.

Die drei Integrationsmuster

Es gibt drei Stellen, an denen du eine Currency-API in einen WooCommerce-Shop einklinken kannst, jede mit anderen Trade-offs:

  1. In ein bestehendes Switcher-Plugin einhaken – schnellster Weg, funktioniert mit CURCY, FOX, Aelia, VillaTheme und den meisten beliebten Plugins. Code-Footprint winzig, aber du bist an die Update-Kadenz und das Storefront-UI des Plugins gebunden.
  2. Eigene Filter-Schicht über die WooCommerce-Core-Hooks – volle Kontrolle darüber, welche Preise konvertiert werden (regulär, Sale, Versand, Steuer), aber mehr Code und das Storefront-UI liegt bei dir.
  3. Hybrid: Plugin macht UI, dein Code besitzt die Kurse – Best-of-Both für Shops, die schon ein Switcher-Plugin nutzen. Das Plugin rendert den Switcher; dein geplanter Job schiebt die Kurse in den Speicher des Plugins.

Wir bauen alle drei.

Muster 1: Custom Rate Feed für ein bestehendes Switcher-Plugin

Die meisten beliebten WooCommerce-Currency-Plugins exponieren einen Filter oder Action-Hook für eigene Kurslieferanten. VillaThemes CURCY-Plugin nutzt wmc_get_currency_exchange_rates. FOX nutzt woocs_currencies_array. Aelia nutzt wc_aelia_cs_exchange_rates_request_args. Das Muster ist überall gleich – du fängst die Kurse ab, die das Plugin sonst aus seinen eingebauten Quellen ziehen würde, und ersetzt sie durch deine eigenen.

Hier ein vollständiges Beispiel für CURCY (gleiche Form, nur anderer Filtername, funktioniert für FOX). Wirf das in ein kleines mu-plugin oder in die functions.php des Themes:

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

Ein paar relevante Details. Erstens hat der wp_remote_get-Call ein Timeout von 8 Sekunden – lang genug, um eine langsame Antwort aufzunehmen, kurz genug, dass PHP-FPM nicht umkippt, wenn der Upstream schwächelt. Zweitens geben wir bei jedem Fehler (Netzwerkfehler, Nicht-200, fehlender Payload) die Default-$rates des Plugins zurück, nicht ein leeres Array. Ein degradierter Checkout ist besser als ein kaputter. Drittens cached der Transient erfolgreiche Antworten eine Stunde, was für Retail-E-Commerce mehr als frisch genug ist und dich locker unter jedem API-Quota hält.

Definiere deinen Key in wp-config.php:

define( 'FINEXLY_API_KEY', 'your_api_key_here' );

Fertig. CURCYs Switcher rendert im Header, Kunden wählen eine Währung, Preise werden mit frischen Kursen aus deinem API-Call statt mit CURCYs Default-12h-Yahoo-Finance-Feed neu berechnet.

Muster 2: Multi-Currency from Scratch mit Core-Filtern

Wenn du kein Plugin in der Schleife haben willst – oder einen Custom-B2B-Shop baust, in dem der Switcher Teil eines Account-Settings statt eines Header-Dropdowns ist – kannst du die gesamte Multi-Currency-Schicht mit zwei Core-Filtern bauen: woocommerce_currency und raw_woocommerce_price.

woocommerce_currency lässt dich die aktive Währung pro Request umschalten. raw_woocommerce_price lässt dich jeden Preis transformieren, den WooCommerce ausgibt. In Kombination mit einer Session-Variable hast du in etwa 60 Zeilen einen funktionierenden Currency-Switcher:

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

Der Transient wird von einem separaten geplanten Job befüllt (nächster Abschnitt), daher macht diese Klasse nie einen Netzwerk-Call innerhalb eines Requests – jeder Page-Load ist ein einzelnes get_transient-Lookup, das in Umgebungen mit Object Cache ~0,1 ms kostet. Das ist der Unterschied zwischen 8 ms zusätzlichem TTFB auf jeder uncached Seite und 800 ms einmal pro Stunde in einem einzelnen Cron-Worker.

Zwei Dinge für den Produktivbetrieb: Präzision und Rundung. WooCommerce speichert Preise als Decimals; multipliziert man einen decimal(13,4)-Wert mit 1.10851234 und speichert das Ergebnis wieder als decimal(13,4), gehen Stellen verloren. Für Niedrigpreis-Artikel (Sub-$1-SKUs, Trinkgeldgläser, Mikrospenden) brauchst du intern mindestens 8 Nachkommastellen Präzision und rundest erst auf der Anzeigeschicht auf die natürliche Mindesteinheit der Währung. WooCommerces wc_price()-Helper erledigt die Anzeige-Rundung; was du selbst tun musst, ist die zugrunde liegenden Floats breit genug zu halten.

Muster 3: Action-Scheduler-Refresh-Job mit Stampede-Schutz

Beide bisherigen Muster lesen aus einem Transient. Irgendwer muss in den Transient schreiben. Das richtige Werkzeug in WooCommerce ist Action Scheduler – der Job-Runner, der mit WooCommerce ausgeliefert wird –, weil er zuverlässig ist, automatisch retried und Concurrency besser handhabt als 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 );
    }
}

Drei erwähnenswerte Muster. Der Stampede-Lock verhindert, dass zwei parallele Worker den Upstream zertrümmern, wenn Action Scheduler verspätet feuert und zwei Jobs hintereinander queued. Die Transient-TTL ×3 versus Refresh-Intervall von einer Stunde ist ein bewusster Puffer – wenn ein Refresh fehlschlägt, überlebt der gecachte Wert bis zum nächsten Versuch. Der persistente Options-Fallback ist die dritte Schicht: Wenn der Object Cache aus irgendeinem Grund geleert wird, hat der Storefront immer noch ein nutzbares Ratenset von bis zu 24 Stunden vorher, und deine rates()-Methode kann darauf zurückfallen, bevor sie Leeres zurückgibt.

Wenn du tiefer in die Caching-Architektur einsteigen willst, lassen sich die Muster aus unserem Currency-API-Caching- und Error-Handling-Guide direkt auf WooCommerce übertragen.

Tests deiner Multi-Currency-Integration

WooCommerce-Shops gehen in Produktion auf drei vorhersagbare Arten kaputt – alle drei sind testbar. Hier eine minimale PHPUnit-Suite mit WP_Mock und 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.
    }
}

Der dritte Test ist der, den die meisten Teams überspringen. Fast jeder WooCommerce-Currency-Bug, den ich in Produktion gesehen habe, lässt sich auf „Upstream hat einen Fehler zurückgegeben und wir haben die leere Antwort gecached" reduzieren. Sicherzustellen, dass du keinen Müll bei einem Fehler schreibst, ist wertvoller, als zu prüfen, dass du im Erfolgsfall das Richtige schreibst.

Häufige Fallstricke und wie du sie vermeidest

Gateway-Währungssupport. Nur weil dein Shop Preise in TRY anzeigt, heißt das nicht, dass Stripe in TRY abrechnet. Bevor du eine Währung in deinem Switcher anbietest, prüfe WC()->payment_gateways() und filtere auf den Schnitt der von deinen aktiven Gateways unterstützten Währungen. Der Vergleich Stripe-FX-Quotes-API vs. dedizierte Currency-API behandelt die Trade-offs im Detail.

Rückerstattungen und Order-Edits. Eine Rückerstattung in EUR drei Wochen nach einer USD-Basis-Bestellung nutzt nicht den heutigen Kurs – sie nutzt den Kurs, den das Gateway zum Refund-Zeitpunkt zieht. Speichere den Kurs zum Bestellzeitpunkt im Order-Meta (update_post_meta( $order_id, '_finexly_rate', $rate )), damit dein Reporting reconcilen kann. Auch nützlich für die Buchhaltungssoftware-Integration.

Rundung. Runde beim Display, nicht beim Speichern. 9.99 * 0.92 = 9.1908 zu 9.19 zu speichern und zurück nach USD zu konvertieren ergibt 9.989..., nicht 9.99. Breite interne Präzision plus Rundung erst beim Anzeigen ist das einzige saubere Muster.

ISO-4217-Validität. Vertraue keiner User-Eingabe. Validiere alle Währungscodes gegen die ISO-4217-Liste, bevor du sie in die Session schreibst.

Welche API: Free vs. Paid

WooCommerces eingebaute Provider (Open-Exchange-Rates-Free-Tier, das deprecated CurrencyLayer usw.) updaten alle 12–24 Stunden und deckeln auf kleine Quotas. Für einen kleinen Shop mit einem Produkt und zehn Bestellungen pro Monat reicht das; für alles mit Traffic willst du einen dedizierten Provider mit mindestens stündlichem Refresh und einer Quota, die dich nicht zum Rationieren zwingt. Unser Currency-API-Vergleich Free vs. Paid für 2026 liefert die echten Zahlen.

Die Finexly-API zielt genau auf diesen Use Case – 170+ Währungen, 60-Sekunden-Refresh auf Majors, stündlich auf Long-Tail, und ein Free-Tier mit 1.000 Requests/Monat, das für einen Action-Scheduler-Job pro Stunde plus Retry-Headroom reicht. Du kannst Currency-APIs vergleichen oder dich kostenlos anmelden, um sie im Dev-Shop auszuprobieren.

Häufig gestellte Fragen

Unterstützt WooCommerce Multi-Currency nativ? Teilweise. WooCommerce unterstützt eine einzige Basiswährung für Bestellungen und Auszahlungen. Multi-Currency-Anzeige und Checkout erfordern entweder ein Switcher-Plugin (CURCY, FOX, Aelia usw.) oder eigenen Code mit den Filtern woocommerce_currency und raw_woocommerce_price, und beide Wege brauchen eine Kursquelle, die du lieferst.

Wie oft sollte ich Wechselkurse in WooCommerce refreshen? Für Retail-E-Commerce reicht stündlich. Für B2B-Shops mit hochwertigen Rechnungen sind 15–30 Minuten sicherer. Aggressiver lohnt sich selten – Payment-Gateways re-quoten beim Settlement sowieso, also ist der Unterschied zwischen einem 5- und einem 30-Minuten-alten Kurs Rauschen gegen den Gateway-Spread.

Kann ich eine Custom-Currency-API mit dem Switcher-Plugin nutzen, das ich schon habe? Ja. CURCY exponiert wmc_get_currency_exchange_rates, FOX exponiert woocs_currencies_array, Aelia hat wc_aelia_cs_exchange_rates_request_args. Hänge dich in den Filter, gib deine Kurse zurück, und das UI des Plugins läuft unverändert weiter.

Was passiert, wenn die API während eines Checkouts down ist? Mit der Architektur oben für den Kunden nichts Sichtbares. Die Pricing-Schicht liest aus einem Transient, der von einem Background-Job befüllt wird; wenn der letzte Refresh fehlgeschlagen ist, ist der vorherige Kurs noch gecached und die persistente Option hält einen 24-Stunden-Fallback. Der Checkout läuft mit dem letzten bekannten guten Kurs durch.

Wie handhabe ich Refunds über Währungen hinweg? Speichere den Kurs zum Bestellzeitpunkt im Order-Meta. Bei Rückerstattung rechne den Betrag mit diesem Kurs, nicht dem heutigen, damit der Kunde in der Zahlungswährung wieder ganz gemacht wird. Das FX-P&L kann dein Buchhaltungssystem separat reconcilen.

Zum Abschluss

Eine solide WooCommerce-Multi-Currency-Wechselkurs-API-Integration dreht sich nicht wirklich um den API-Call – sie dreht sich um die vier Schichten drumherum: Caching, Scheduling, Fallbacks, Tests. Mach die richtig und dein Shop hält Preise in 50+ Währungen ehrlich, ohne einen einzigen Pager-Alarm in der Nacht.

Bereit, Echtzeit-Wechselkurse in deinen WooCommerce-Shop zu integrieren? Hol dir deinen kostenlosen Finexly-API-Key – ohne Kreditkarte. Starte mit 1.000 freien Requests pro Monat, wirf die Snippets oben in ein mu-plugin, und du hast Multi-Currency-Pricing live, bevor das Mittagessen kommt. Wenn du das Free-Tier sprengst, skalieren die Pricing-Pläne linear mit deinem Shop, nicht exponentiell.

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 →

Diesen Artikel teilen