العودة إلى المدونة

WooCommerce متعدد العملات: كيفية ربط واجهة برمجية لأسعار الصرف لحظيًا (دليل المطورين 2026)

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

تشغّل WooCommerce ما يقارب ثلث المتاجر الإلكترونية في العالم، وما إن يقرر أحد هذه المتاجر البيع عبر الحدود حتى يطفو السؤال نفسه على السطح: كيف تحافظ على دقّة الأسعار والضرائب والتسويات في اثنتي عشرة عملة دون فحص سوق العملات يدويًا كل صباح؟ الجواب الصريح هو ربط واجهة برمجية لأسعار الصرف متعددة العملات في WooCommerce بمتجرك — لكن إنجاز ذلك بشكل صحيح أكثر دقة من مجرد تثبيت إضافة والمضي. عليك التفكير في وتيرة التحديث، وفي اندفاعات التخزين المؤقت (cache stampede)، وفي توافق بوابات الدفع، وفي التقريب، وفي ما يحدث حين يردّ مزوّدك العلوي بـ 503 في منتصف عملية الدفع.

يستعرض هذا الدليل ثلاثة أنماط إنتاجية يستخدمها المطورون فعلًا في 2026، مع كود PHP عملي لكل نمط. سنربط واجهة Finexly بـ WooCommerce بثلاث طرق مختلفة: كموجز مخصص لإضافة محول عملة موجودة، كطبقة قائمة على الفلاتر مبنية من الصفر، وكوظيفة Action Scheduler مع قفل سليم. في النهاية ستحصل على نمط يناسب أي ستاك تنطلق منه — WooCommerce نقي، أو WooCommerce مع إضافة محول، أو نظام B2B مخصص بالكامل.

لماذا تهمّ واجهة برمجية فورية لأسعار الصرف في WooCommerce

افتراضيًا، يضبط WooCommerce عملة أساسية واحدة في woocommerce_currency، ويفترض أن كل طلب واسترداد وتسوية تتم بهذه العملة. دعم تعدد العملات طبقة فوقية: محوّل في الترويسة، مجاميع سلة معاد حسابها، وأحيانًا معالجة دفع موضعية. تحتاج هذه الطبقة إلى مصدر لأسعار الصرف، و"المصدر" غالبًا ما يكون أحد اثنين — سعر صرف يدخله أحدهم يدويًا ولا يتذكر تحديثه، أو موجز مزوّد مجاني يدقّ مرة كل 12 إلى 24 ساعة.

كلاهما خطر في بيئة عملات 2026. تحرّك زوج USD/JPY بنسبة 0,95% في 6 مايو وحده بعد تدخل طوكيو، وتتذبذب عملات الأسواق الناشئة دائمًا أكثر من 1% خلال اليوم. متجر يعرض سعرًا عمره 24 ساعة هو متجر يبيع بخصم أو علاوة 1-2% بحسب اتجاه الريح — هذا قبل احتساب الفارق الذي تضيفه البوابة عند التسوية. واجهة برمجية لأسعار الصرف متعددة العملات في WooCommerce فورية مع طبقة تخزين مؤقت معقولة تبقي الأسعار المعروضة ضمن جزء صغير من النقطة المئوية حول السعر الوسطي للسوق دائمًا.

السبب الآخر: توافق بوابة الدفع. لدى Stripe وPayPal ومعظم البوابات الكبرى قائمة عملات تسوية مدعومة خاصة بها. إذا كان متجرك يعرض الأسعار بـ 15 عملة فيما يسوّي Stripe في 6 فقط، فأنت بحاجة لمعرفة أين يحدث التحويل وبأي سعر — وهذه الحسبة أسهل بكثير حين تكون طبقتك أنت مصدر الحقيقة في FX، لا بوابة صندوق أسود.

أنماط الدمج الثلاثة

ثمّة ثلاث نقاط لربط واجهة برمجية للعملة في متجر WooCommerce، ولكل نقطة موازنتها:

  1. الانعقاد بإضافة محوّل قائمة — أسرع طريق، يعمل مع CURCY وFOX وAelia وVillaTheme وأكثر الإضافات شيوعًا. الكود الإضافي ضئيل لكنك مرتبط بإيقاع تحديث الإضافة وبواجهتها على الواجهة.
  2. طبقة فلاتر مخصصة عبر hooks الأساسية لـ WooCommerce — تحكّم كامل بالأسعار التي ستحوّل (الاعتيادية، التخفيضية، الشحن، الضريبة)، لكن مع كود أكثر وواجهة على عاتقك.
  3. هجين: الإضافة تتولّى الواجهة، كودك يتولّى الأسعار — الأمثل لمتاجر تستخدم محوّلًا أصلًا. تعرض الإضافة المحوّل، وتدفع وظيفتك المجدولة الأسعار إلى مخزن الإضافة.

سنبني الأنماط الثلاثة.

النمط 1: موجز أسعار مخصص لإضافة محوّل قائمة

تعرض معظم إضافات العملات الشائعة لـ WooCommerce فلترًا أو action hook لمصدر أسعار مخصص. CURCY من VillaTheme يستخدم 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 الردود الناجحة لمدة ساعة، وهي طزاجة فائضة لمتجر تجزئة وتُبقيك مرتاحًا تحت أي حصة.

عرّف مفتاحك في wp-config.php:

define( 'FINEXLY_API_KEY', 'your_api_key_here' );

وانتهينا. يظهر محوّل CURCY في الترويسة، يختار العميل عملة، وتُعاد حسبة الأسعار بأسعار طازجة من استدعاء واجهتك بدلًا من موجز Yahoo Finance الافتراضي الذي يحدّثه CURCY كل 12 ساعة.

النمط 2: تعدّد العملات من الصفر بفلاتر WooCommerce الأساسية

إن لم ترغب في إضافة في الحلقة — أو كنت تبني متجر B2B مخصصًا حيث يكون المحوّل جزءًا من إعداد على مستوى الحساب لا قائمة منسدلة في الترويسة — يمكنك بناء طبقة تعدد العملات بأكملها بفلترين أساسيين: woocommerce_currency وraw_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 وظيفة مجدولة منفصلة (القسم التالي)، لذا لا يجري هذا الصف أي اتصال شبكة داخل الطلب — كل تحميل صفحة هو استعلام get_transient وحيد، يكلف نحو 0,1ms في بيئات يفعّل فيها object cache. الفرق هو بين إضافة 8ms إلى TTFB لكل صفحة غير مخزنة، وإضافة 800ms مرة كل ساعة لعامل cron واحد.

شيئان يجب إضافتهما للإنتاج: الدقة والتقريب. يخزن WooCommerce الأسعار رقمًا عشريًا. ضرب قيمة decimal(13,4) بسعر 1.10851234 ثم تخزينها مجددًا بـ decimal(13,4) يفقد الدقة بصمت. للسلع منخفضة الثمن (SKU بأقل من دولار، صناديق إكراميات، تبرعات صغيرة) يلزمك ما لا يقل عن 8 خانات عشرية للدقة الداخلية، مع تقريب على طبقة العرض فقط إلى الوحدة الدنيا الطبيعية للعملة. مساعد wc_price() يتولى التقريب عند العرض؛ ما عليك أنت هو إبقاء أرقام الـ float تحته بعرض كافٍ.

النمط 3: وظيفة تحديث Action Scheduler مع حماية ضد الاندفاع

يقرأ النمطان السابقان من 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 );
    }
}

ثلاثة أنماط جديرة بالملاحظة. قفل الاندفاع يمنع عاملين متوازيين من إغراق الجهة العلوية حين يتأخر Action Scheduler ويصفّ مهمتين متتاليتين. TTL transient ضعف ثلاثة أضعاف فترة التحديث المتمثلة في ساعة هو متعمد كاحتياطي — إذا فشل تحديث، تبقى القيمة المخزنة حتى المحاولة التالية. الرجوع المستديم في options هو الطبقة الثالثة: لو أُفرغ object cache لأي سبب، سيظل لدى المتجر مجموعة أسعار صالحة عمرها لا يتجاوز 24 ساعة، وبإمكان طريقة rates() الرجوع إليها قبل أن تُعيد فراغًا.

إن أردت غوصًا أعمق في معمارية التخزين المؤقت ذاتها، فأنماط دليل التخزين المؤقت ومعالجة الأخطاء لواجهات العملات تُترجم مباشرة إلى WooCommerce.

اختبار دمج تعدد العملات

تُعطب متاجر WooCommerce في الإنتاج بثلاث طرق متوقعة، وكلها قابلة للاختبار. سويت PHPUnit أدنى الحد باستخدام WP_Mock و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.
    }
}

الاختبار الثالث هو الذي يُهمله أكثر الفرق. تقريبًا كل علة عملات في WooCommerce رأيتها في الإنتاج تختزل إلى "ردّت الجهة العلوية بخطأ فخزّنا الرد الفارغ". التحقق من أنك لا تكتب قمامة عند الفشل أعلى قيمة من التحقق من أنك تكتب الصواب عند النجاح.

مزالق شائعة وكيف تتجنبها

دعم البوابة للعملة. عرض الأسعار بالليرة التركية في متجرك لا يعني أن Stripe سيسوّي بالليرة التركية. قبل عرض عملة في المحوّل، افحص WC()->payment_gateways() وصفّ على تقاطع العملات التي تدعمها بواباتك المفعّلة. تتناول المقارنة بين واجهة Stripe FX Quotes وواجهة عملات مخصصة المقايضات بالتفصيل.

الاسترداد وتعديل الطلب. استرداد بـ EUR بعد ثلاثة أسابيع من طلب أصله USD لن يستخدم سعر اليوم — سيستخدم السعر الذي تلتقطه البوابة وقت الاسترداد. خزّن سعر وقت الطلب في ميتا الطلب (update_post_meta( $order_id, '_finexly_rate', $rate )) كي يستطيع التقرير المطابقة. مفيد كذلك لطبقة دمج برامج المحاسبة.

التقريب. قرّب عند العرض لا عند التخزين. تخزين 9.99 * 0.92 = 9.1908 مقربًا إلى 9.19 ثم إعادته إلى USD يعطي 9.989... لا 9.99. دقة داخلية واسعة + تقريب عند العرض هو النمط السليم الوحيد.

سلامة ISO 4217. لا تثق بإدخال المستخدم. تحقق من جميع رموز العملات مقابل قائمة ISO 4217 قبل تخزينها في الجلسة.

اختيار الواجهة: مجاني مقابل مدفوع

تحدّث مزوّدات WooCommerce المدمجة (الطبقة المجانية لـ Open Exchange Rates، CurrencyLayer المتروك إلخ) كل 12-24 ساعة، وتُحدّ بحصص صغيرة. لمتجر صغير بمنتج وعشرة طلبات في الشهر هذا كاف؛ لأي شيء فيه حركة، ستحتاج مزوّدًا مخصصًا بتحديث ساعي على الأقل وحصة لا تجبرك على ترشيد الاستدعاءات. تستعرض مقارنتنا بين الواجهات المجانية والمدفوعة لعام 2026 الأرقام الفعلية.

تستهدف واجهة Finexly هذا السيناريو تحديدًا — أكثر من 170 عملة، تحديث 60 ثانية للعملات الرئيسية، وساعي للذيل الطويل، وطبقة مجانية بـ 1000 طلب شهريًا تكفي لوظيفة Action Scheduler ساعية مع هامش لإعادة المحاولات. يمكنك مقارنة واجهات العملات جنبًا إلى جنب أو التسجيل مجانًا لتجربتها على متجر تطويري.

الأسئلة الشائعة

هل يدعم WooCommerce تعدد العملات أصلًا؟ جزئيًا. يدعم WooCommerce عملة أساس واحدة للطلبات والتسويات. أمّا عرض تعدد العملات والدفع فيتطلبان إمّا إضافة محوّل (CURCY، FOX، Aelia… إلخ) وإمّا كودًا مخصصًا بفلاتر woocommerce_currency وraw_woocommerce_price، وكلا المسارين يحتاجان مصدر أسعار توفّره أنت.

كم مرة عليّ تحديث الأسعار في WooCommerce؟ لتجارة التجزئة، ساعة كل ساعة كافية. لمتاجر B2B بفواتير عالية القيمة، 15-30 دقيقة أأمن. أكثر من ذلك نادرًا ما يستحق الحصة — تُعيد البوابات التسعير عند التسوية على أي حال، فيغيب الفرق بين سعر عمره 5 دقائق وآخر 30 دقيقة في فارق البوابة.

هل يمكنني دمج واجهة عملات مخصصة مع إضافة المحوّل التي أستخدمها؟ نعم. CURCY يكشف wmc_get_currency_exchange_rates، FOX يكشف woocs_currencies_array، Aelia يكشف wc_aelia_cs_exchange_rates_request_args. اربط الفلتر، أعد أسعارك، وستظل واجهة الإضافة تعمل دون تغيير.

ماذا يحدث إذا توقفت واجهة العملات أثناء الدفع؟ بهذه المعمارية، لا شيء يراه العميل. تقرأ طبقة التسعير من transient تملؤه وظيفة في الخلفية؛ إن فشل آخر تحديث يبقى السعر السابق مخزنًا، ويحتفظ option المستديم بفولباك 24 ساعة. تستمر عملية الدفع بآخر سعر معروف صالح.

كيف أتعامل مع الاسترداد بين عملات مختلفة؟ خزّن السعر المستخدم وقت الطلب في ميتا الطلب. عند الاسترداد، احسب المبلغ بـذلك السعر، لا سعر اليوم، حتى يُعاد للعميل وفق العملة التي دفع بها. يقوم نظامك المحاسبي بتسوية الربح والخسارة في FX على حدة.

في الختام

الدمج الجيّد لـ واجهة برمجية لأسعار الصرف متعددة العملات في WooCommerce ليس في حقيقته عن استدعاء الواجهة — بل عن الطبقات الأربع المحيطة به: التخزين المؤقت، الجدولة، الفولباك، والاختبارات. أحسن صنعها وستحفظ متجرك أسعارًا دقيقة في 50+ عملة دون أي تنبيه ليلي.

جاهز لدمج أسعار الصرف الفورية في متجر WooCommerce؟ احصل على مفتاح Finexly مجاني — دون بطاقة ائتمان. ابدأ بـ 1000 طلب مجاني شهريًا، أسقِط المقاطع أعلاه في 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 →