ブログに戻る

WooCommerce マルチ通貨:リアルタイム為替レートAPIの組み込み方(2026年 開発者ガイド)

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

WooCommerce は世界のオンラインストアのおよそ 3 分の 1 を支えており、そのうちの一つが越境販売を始めようとした瞬間、決まって同じ問いが現れます——12 通貨で価格・税金・支払いを正しく保ちながら、毎朝手作業で為替市場をチェックしないで済ませるにはどうすればいいのか。誠実な答えは、ストアに WooCommerce マルチ通貨向けの為替レートAPI を組み込むことです。ただし、プラグインを入れて放置するよりずっと繊細な話で、リフレッシュ頻度、キャッシュスタンピード、決済ゲートウェイ互換性、丸め、そしてチェックアウト中に上流が 503 を返したときの挙動を考える必要があります。

このガイドでは、2026 年に開発者が実運用で実際に使っている 3 つのパターンを、それぞれ動く PHP コード付きで解説します。Finexly API を WooCommerce に 3 通り——既存の通貨スイッチャープラグイン用カスタムフィードとして、コアフィルタを使ったゼロから自作のレイヤーとして、そして適切なロックを備えた Action Scheduler ジョブとして——組み込みます。読み終える頃には、素の WooCommerce、スイッチャープラグイン入りの WooCommerce、完全カスタムの B2B ビルドのいずれにも当てはまるパターンが手に入ります。

なぜ WooCommerce にリアルタイム為替レートAPIが重要か

デフォルトでは、WooCommerce は woocommerce_currency に 1 つの基準通貨を設定し、すべての注文・返金・支払いがその通貨で決済される前提で動きます。マルチ通貨対応はその上に載るレイヤーで、ヘッダーのスイッチャー、再計算されるカート合計、(場合によっては)ローカライズされた決済処理から成ります。このレイヤーは為替レートを必要とし、その出所はたいてい二択になります——誰も更新を覚えていない手入力レートか、12〜24 時間に 1 度だけ動く無料プロバイダのフィードか。

どちらも 2026 年の FX 環境では危険です。USD/JPY は東京の介入後の 5 月 6 日だけで 0.95% 動きましたし、新興国通貨は日中で 1% を超える振れがざらです。24 時間古いレートを表示しているストアは、風向き次第で 1〜2% の値引きまたは値乗せで売っていることになります——これは決済ゲートウェイが settlement 時に上乗せするスプレッドより前の話です。妥当なキャッシュレイヤーを伴う WooCommerce マルチ通貨向け為替レートAPI をリアルタイムで使えば、表示価格は常にミッドマーケットから 1% 未満のずれに収まります。

もう一つ重要な理由が、決済ゲートウェイの通貨対応です。Stripe、PayPal、主要な大手ゲートウェイにはそれぞれ対応する settlement 通貨のリストがあります。ストアが 15 通貨で価格を表示しても、Stripe が 6 通貨でしか settlement できないなら、どこで・どのレートで・どの変換が起きているかを把握する必要があります——そしてこの計算は、ブラックボックスのゲートウェイに頼るのではなく 自分のレイヤー が FX の真実を持っているときの方が圧倒的に楽です。

3 つの統合パターン

通貨 API を WooCommerce ストアに組み込む位置は 3 つあり、それぞれトレードオフが違います:

  1. 既存のスイッチャープラグインにフックする — 最速。CURCY、FOX、Aelia、VillaTheme などほとんどの人気プラグインで動きます。コードは小さく済みますが、プラグインの更新サイクルとフロント UI に縛られます。
  2. コア WooCommerce フックを使ったカスタムフィルタレイヤー — どの価格(通常価・セール価・送料・税)を変換するか完全に制御できますが、コード量が増え、UI も自前です。
  3. ハイブリッド:プラグインが UI、自分のコードがレート — すでにスイッチャープラグインを使っているストアにとっては両者のいいとこ取り。プラグインがスイッチャーを描画し、スケジュールジョブがプラグインのストレージにレートを書き込みます。

3 つすべて構築します。

パターン 1:既存スイッチャープラグイン向けのカスタムレートフィード

人気の WooCommerce 通貨プラグインは、ほとんどがカスタムレート用のフィルタまたはアクションフックを公開しています。VillaTheme の CURCY は wmc_get_currency_exchange_rates。FOX は woocs_currencies_array。Aelia は wc_aelia_cs_exchange_rates_request_args。パターンはどれも同じで、プラグインが内蔵プロバイダから取得しようとするレートを傍受し、自分のものに差し替えます。

CURCY 向けの完全なサンプルです(FOX でもフィルタ名を変えるだけで同形)。小さな mu-plugin かテーマの functions.php に置きます:

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

要点をいくつか。第一に、wp_remote_get のタイムアウトは 8 秒——遅いレスポンスを吸収するには十分長く、上流が劣化したときに PHP-FPM が倒れない程度に短い。第二に、あらゆる失敗(ネットワークエラー、200 以外、ペイロード欠落)に対し、空配列ではなくプラグインのデフォルト $rates を返します。劣化したチェックアウトは壊れたチェックアウトより常にマシです。第三に、transient は成功レスポンスを 1 時間キャッシュします。リテール EC には十分鮮度が高く、API クォータの心配もまずありません。

wp-config.php でキーを定義します:

define( 'FINEXLY_API_KEY', 'your_api_key_here' );

これで完了です。CURCY のスイッチャーがヘッダーに表示され、顧客が通貨を選ぶと、CURCY デフォルトの 12 時間 Yahoo Finance フィードではなく、あなたの API コールから来る新鮮なレートで価格が再計算されます。

パターン 2:コア WooCommerce フィルタでマルチ通貨を自作する

プラグインを挟みたくない、あるいは通貨スイッチャーがヘッダードロップダウンではなくアカウント単位の設定の一部になる B2B カスタムストアを作っているなら、2 つのコアフィルタ woocommerce_currencyraw_woocommerce_price だけでマルチ通貨レイヤー全体を作れます。

woocommerce_currency はリクエスト単位でアクティブ通貨を切り替えるためのフィルタ。raw_woocommerce_price は WooCommerce が表示するすべての価格を変換するためのフィルタです。これにセッション変数を組み合わせれば、約 60 行で機能する通貨スイッチャーになります:

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

transient は別のスケジュールジョブが書き込みます(次節)。したがってこのクラスはリクエスト内でネットワークコールを一切行いません。各ページロードは 1 回の get_transient ルックアップで、オブジェクトキャッシュ有効環境なら約 0.1ms。これは未キャッシュページごとに TTFB を 8ms 増やすか、1 時間に 1 度だけ 1 つの cron ワーカーに 800ms 加えるかの違いです。

本番投入時に追加すべきは精度と丸めです。WooCommerce は価格を decimal で保持します。decimal(13,4) の値を 1.10851234 のレートで乗じて再び decimal(13,4) に格納すれば、精度は静かに失われます。低価格商品($1 未満の SKU、チップジャー、マイクロドネーション)には内部で少なくとも 8 桁の精度を確保し、表示層で初めて通貨の自然な最小単位に丸めるのが正解です。WooCommerce の wc_price() ヘルパーが表示の丸めをやってくれるので、自分でやるのは内部 float を十分に広く保つことだけです。

パターン 3:スタンピード保護付き Action Scheduler リフレッシュジョブ

ここまでの 2 パターンは transient から読みます。誰かがその transient に書き込まなければなりません。WooCommerce での正解は Action Scheduler——WooCommerce 同梱のジョブランナー——です。信頼でき、自動リトライが効き、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 );
    }
}

注目すべきパターンが 3 つあります。スタンピードロックは、Action Scheduler が遅延して 2 つのジョブが連続でキューに積まれたときに、並列ワーカーが上流を叩き合うのを防ぎます。transient TTL を更新間隔の 3 倍にしているのは意図的なバッファで、1 度のリフレッシュ失敗くらいでは次回まで持ちこたえます。option への永続フォールバックは 3 段目の保険で、何らかの理由でオブジェクトキャッシュが flush されても、最大 24 時間前のレートセットがまだ使えるので、rates() メソッドはそれにフォールバックしてから空を返すようにできます。

キャッシュアーキテクチャ自体をさらに掘り下げたい場合、通貨APIキャッシュとエラーハンドリングのガイドのパターンはそのまま WooCommerce に応用できます。

マルチ通貨統合のテスト

WooCommerce ストアが本番で壊れる壊れ方は 3 通りあり、すべてテスト可能です。WP_Mock と Mockery を使った最小の PHPUnit スイート:

<?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.
    }
}

3 つ目のテストは、ほとんどのチームが省略するものです。私が本番で見てきた WooCommerce の通貨バグはほぼすべて「上流がエラーを返したのに空のレスポンスをキャッシュした」に帰着します。失敗時にゴミを書き込まないことを確認するほうが、成功時に正しい値を書くことを確認するより価値があります。

よくある落とし穴と回避策

ゲートウェイの通貨サポート。 ストアが TRY で価格を表示できるからといって、Stripe が TRY で settlement できるわけではありません。スイッチャーで通貨を出す前に WC()->payment_gateways() を見て、有効なゲートウェイがサポートする通貨の積集合に絞ります。Stripe の FX クォート API と専用通貨 API の比較でトレードオフを詳しく扱っています。

返金と注文編集。 USD ベースの注文から 3 週間後に EUR で返金する場合、今日のレートではなくゲートウェイが返金時点で取得するレートが使われます。注文時のレートを order meta に保存しておく(update_post_meta( $order_id, '_finexly_rate', $rate ))と、レポートが照合できます。会計ソフト統合レイヤーにも有用です。

丸め。 表示時に丸める、保存時には丸めない。9.99 * 0.92 = 9.19089.19 に丸めて USD に戻すと 9.989... になり 9.99 には戻りません。広い内部精度+表示時のみ丸める、これだけが正気のパターンです。

ISO 4217 の妥当性。 ユーザー入力を信用しないこと。すべての通貨コードを ISO 4217 リストに照合してからセッションに保存します。

API の選び方:無料 vs 有料

WooCommerce 標準のプロバイダ(Open Exchange Rates の無料枠、廃止された CurrencyLayer など)は 12〜24 時間ごとの更新で、リクエスト枠も小さめです。商品 1 つ・月 10 件のストアなら十分。それ以上のトラフィックがあるなら、少なくとも毎時更新かつコールを節約しなくていい枠を持つ専用プロバイダが要ります。2026 年の通貨 API 無料 vs 有料比較に実数値があります。

Finexly API はまさにこの用途に向いています——170 以上の通貨、メジャー通貨は 60 秒、ロングテールは毎時更新、無料枠は月 1,000 リクエストで、毎時 1 回の Action Scheduler ジョブとリトライに十分な余裕があります。通貨 API を比較するか、開発ストアで試すなら 無料登録 から始められます。

よくある質問

WooCommerce はマルチ通貨をネイティブにサポートしていますか? 部分的にです。注文と支払いの基準通貨は 1 つだけ。マルチ通貨の表示チェックアウトには、スイッチャープラグイン(CURCY、FOX、Aelia など)か、woocommerce_currencyraw_woocommerce_price フィルタを使うカスタムコードが必要で、どちらの方法でも自分でレートソースを供給する必要があります。

WooCommerce のレートはどのくらいの頻度で更新するべきですか? リテール EC なら 1 時間に 1 回で十分。高額請求の B2B なら 15〜30 分に 1 回が安全。それ以上の頻度はクォータの割に効果が薄く、決済ゲートウェイは settlement 時に再見積もりするので、5 分前のレートと 30 分前のレートの差はゲートウェイのスプレッドの中に埋もれます。

今使っている WooCommerce スイッチャープラグインにカスタム通貨 API を統合できますか? できます。CURCY は wmc_get_currency_exchange_rates、FOX は woocs_currencies_array、Aelia は wc_aelia_cs_exchange_rates_request_args を公開しています。フィルタにフックして自分のレートを返せば、プラグインの UI はそのまま動き続けます。

チェックアウト中に為替 API がダウンしたらどうなりますか? 上記アーキテクチャなら顧客には何も見えません。価格レイヤーはバックグラウンドジョブが書き込む transient から読みます。最後のリフレッシュが失敗していても以前のレートはまだキャッシュされており、永続オプションが 24 時間のフォールバックを保持します。チェックアウトは最後に有効だったレートで進みます。

通貨をまたいだ返金はどう扱いますか? 注文時点で使ったレートを order meta に保存します。返金時はその同じレートで金額を計算し、顧客が支払った通貨で同額を返します。FX 損益は会計システムで別途消し込みます。

まとめ

確かな WooCommerce マルチ通貨向け為替レート API 統合の本質は、API コールそのものではなく、その周りの 4 層——キャッシュ、スケジューリング、フォールバック、テスト——にあります。これらを正しく設計すれば、深夜のページャーアラートなしに 50 以上の通貨で価格を正しく保てます。

WooCommerce ストアにリアルタイム為替レートを組み込む準備はできましたか?クレジットカード不要で Finexly の無料 API キーを取得 しましょう。月 1,000 リクエストの無料枠から始め、上記スニペットを mu-plugin に貼れば、昼食前にはマルチ通貨価格がライブになります。無料枠を超えても、料金プラン はストアの成長に対して指数ではなく線形にスケールします。

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 →

この記事を共有する