ن
مقاله آموزشی

نرخ ارز در وردپرس و ووکامرس بدون کند کردن سایت

نویسنده: تیم نِت اَرز 1405/07/04 ۱۱ دقیقه مطالعه ۱۳ بازدید

یک افزونهٔ نرخ ارز نصب می‌کنید، تابلو می‌آید، و دو هفته بعد متوجه می‌شوید صفحهٔ اول سایت یک ثانیه کندتر باز می‌شود. دلیلش معمولاً یک خط ساده است: افزونه در هر بار بارگذاری صفحه یک درخواست به سرویس بیرونی می‌فرستد و تا جواب نیاید، رندر صفحه معطل می‌ماند.

این مقاله همان تابلو را بدون آن مشکل می‌سازد. سه کار می‌کنیم: یک شورت‌کد ساده با ویجت، یک شورت‌کد جدی با کش transient، و تبدیل قیمت ووکامرس از دلار به تومان. همهٔ کدها روی وب سرویس نرخ ارز نِت اَرز اجرا می‌شوند.

چرا گرفتن نرخ در هر بارگذاری صفحه غلط است

وردپرس هر صفحه را سمت سرور می‌سازد. اگر در آن مسیر یک wp_remote_get بدون کش بنشیند، سه اتفاق می‌افتد:

  • زمان پاسخ سایت به زمان پاسخ سرویس بیرونی گره می‌خورد. هر کندی آن‌طرف، مستقیم روی سایت شما دیده می‌شود.
  • اگر سرویس قطع باشد، صفحه هم معطل می‌ماند تا تایم‌اوت تمام شود؛ یعنی چند ثانیه صفحهٔ سفید.
  • سهمیهٔ روزانه بی‌دلیل تمام می‌شود. نرخ هر چند دقیقه عوض می‌شود، نه هر بازدید. هزار بازدید در ساعت یعنی هزار درخواست برای عددی که شاید اصلاً تغییر نکرده.

راه‌حل هر سه یکی است: نرخ را یک بار بگیرید، در transient نگه دارید و بقیهٔ بازدیدها از همان بخوانند.

ساده‌ترین راه: ویجت در یک شورت‌کد

اگر فقط یک تابلوی نمایشی می‌خواهید و کاری با اعداد ندارید، ویجت آماده هیچ باری روی سرور شما نمی‌گذارد؛ چون در مرورگر بازدیدکننده اجرا می‌شود، نه در PHP. یک شورت‌کد کوتاه در functions.php قالب فرزند:

add_shortcode('netarz_fx', function ($atts) {
    $a = shortcode_atts([
        'codes' => 'USD,EUR,AED,TRY',
        'theme' => 'light',
        'title' => 'نرخ ارز امروز',
    ], $atts);

    return sprintf(
        '<script src="https://netarz.ir/fx/widget.js" data-key="%s" data-codes="%s" data-theme="%s" data-title="%s" async></script>',
        esc_attr(NETARZ_FX_KEY),
        esc_attr($a['codes']),
        esc_attr($a['theme']),
        esc_attr($a['title'])
    );
});
// در نوشته یا برگه: [netarz_fx codes="USD,TRY" theme="dark"]

کلید در کد صفحه دیده می‌شود، ولی به دامنهٔ اپ قفل است و روی دامنهٔ دیگری جواب نمی‌دهد. گزینه‌های کامل ویجت در مستندات ویجت آمده است.

راه درست برای اعداد: شورت‌کد با کش transient

وقتی می‌خواهید خود عدد را در متن، قالب یا محاسبه به کار ببرید، باید نرخ را سمت سرور بگیرید. این نسخه نرخ را پنج دقیقه نگه می‌دارد، و اگر سرویس جواب نداد، به‌جای خطا آخرین نرخ سالم را برمی‌گرداند:

// wp-config.php
define('NETARZ_FX_KEY', 'fx-ntz-v1-...');

// functions.php
function netarz_fx_board() {
    $cached = get_transient('netarz_fx_board');
    if ($cached !== false) {
        return $cached;
    }

    $res = wp_remote_get(
        'https://netarz.ir/api/fx/v1/rates?codes=USD,EUR,AED,TRY',
        [
            'timeout' => 8,
            'headers' => ['Authorization' => 'Bearer ' . NETARZ_FX_KEY],
        ]
    );

    if (is_wp_error($res) || wp_remote_retrieve_response_code($res) !== 200) {
        // Serve the last good board for another minute instead of an error.
        $stale = get_option('netarz_fx_last_good');
        if ($stale) {
            set_transient('netarz_fx_board', $stale, 60);
            return $stale;
        }
        return null;
    }

    $body = json_decode(wp_remote_retrieve_body($res), true);
    set_transient('netarz_fx_board', $body, 5 * MINUTE_IN_SECONDS);
    update_option('netarz_fx_last_good', $body, false);

    return $body;
}

function netarz_fx_rate($code = 'USD', $side = 'mid') {
    $board = netarz_fx_board();
    if (!$board) {
        return null;
    }
    foreach ($board['data'] as $row) {
        if ($row['code'] === strtoupper($code)) {
            return $row[$side];
        }
    }
    return null;
}

add_shortcode('fx_rate', function ($atts) {
    $a = shortcode_atts(['code' => 'USD', 'side' => 'mid'], $atts);
    $rate = netarz_fx_rate($a['code'], $a['side']);

    return $rate === null
        ? 'نرخ در دسترس نیست'
        : number_format_i18n($rate);
});
// در متن: قیمت هر دلار [fx_rate code="USD"] تومان

سرور شما هدر Origin نمی‌فرستد، پس IP سرور وردپرس باید در پنل «API نرخ ارز» به فهرست IPهای مجاز اپ اضافه شود؛ در غیر این صورت پاسخ 403 origin_required می‌گیرید. اگر هاست اشتراکی دارید و IP خروجی را نمی‌دانید، از پشتیبانی هاست بپرسید.

نمایش نرخ در بلوک و صفحه‌ساز

شورت‌کد در ویرایشگر بلوک هم کار می‌کند: بلوک «شورت‌کد» را اضافه کنید و [fx_rate code="USD"] را داخلش بگذارید. در صفحه‌سازها همان ویجت متن یا شورت‌کد کافی است. اگر می‌خواهید عدد را مستقیم در قالب چاپ کنید، تابع را صدا بزنید و حتماً حالت خالی را هم مدیریت کنید:

<?php $rate = netarz_fx_rate('USD', 'sell'); ?>
<?php if ($rate): ?>
    <span class="fx-rate">هر دلار <?php echo number_format_i18n($rate); ?> تومان</span>
<?php else: ?>
    <span class="fx-rate">نرخ در دسترس نیست</span>
<?php endif; ?>

حالت خالی را جدی بگیرید. اگر سرویس یک بار جواب ندهد و قالب شما عدد خالی چاپ کند، بازدیدکننده جمله‌ای بی‌معنا با یک جای خالی می‌بیند؛ همین چند خط، تفاوت یک صفحهٔ سالم با یک صفحهٔ شکسته است.

دو نکتهٔ محیطی هم هست. اگر روی سایتتان کش شیء (object cache) مثل Redis فعال است، transient در همان می‌نشیند و با پاک شدن کش از بین می‌رود؛ برای همین در کد بالا یک نسخهٔ پشتیبان در option نگه داشتیم. و اگر چند سایت روی یک هاست دارید، هرکدام جدا درخواست می‌فرستند؛ در این حالت بهتر است یکی از آن‌ها نرخ را بگیرد و بقیه از همان بخوانند تا سهمیه چند برابر مصرف نشود.

تبدیل قیمت ووکامرس از دلار به تومان

سناریوی رایج: قیمت‌ها را به دلار وارد می‌کنید و می‌خواهید مشتری تومان ببیند. کار درست این است که قیمت پایه را دست‌نخورده نگه دارید و تبدیل را در لحظهٔ نمایش انجام بدهید:

// Prices are stored in USD; show and charge Toman at today's rate.
add_filter('woocommerce_product_get_price', 'netarz_to_toman', 20, 2);
add_filter('woocommerce_product_get_regular_price', 'netarz_to_toman', 20, 2);

function netarz_to_toman($price, $product) {
    if ($price === '' || $price === null) {
        return $price;
    }

    $rate = netarz_fx_rate('USD', 'sell');
    if (!$rate) {
        return $price;   // no rate, no silent wrong number
    }

    // Round up to a clean step so the cart never shows odd trailing digits.
    return ceil(((float) $price * $rate) / 1000) * 1000;
}

سه هشدار که اگر نادیده بگیرید بعداً گرفتارتان می‌کند:

  1. سفارش ثبت‌شده نباید با نرخ جدید بازخوانی شود. ووکامرس قیمت را هنگام ثبت سفارش ذخیره می‌کند؛ اگر جایی قیمت را دوباره از محصول می‌خوانید، فاکتور قدیمی با نرخ امروز عوض می‌شود.
  2. کش صفحه را از یاد نبرید. اگر افزونهٔ کش دارید، صفحه‌ای که قیمت تبدیل‌شده دارد ممکن است ساعت‌ها ثابت بماند. یا مدت کش را با مدت transient هماهنگ کنید، یا قیمت را در آن صفحه‌ها با جاوااسکریپت تازه کنید.
  3. گرد کردن را یک بار و در یک جا انجام بدهید. اگر در سبد یک جور و در فاکتور جور دیگر گرد شود، جمع کل با مجموع ردیف‌ها نمی‌خواند.

سه رویکرد، کنار هم

رویکردبار روی سرورکلید کجاستعدد در متن استفاده می‌شود؟مناسب
ویجت در شورت‌کدهیچکد صفحه، قفل به دامنهخیرسایدبار و وبلاگ
شورت‌کد با transientیک درخواست در هر پنج دقیقهwp-configبلهمتن، قالب، محاسبه
افزونهٔ بدون کشیک درخواست در هر بازدیدتنظیمات افزونهبلهتوصیه نمی‌شود

کلید را کجا بگذارید

کلید را در wp-config.php به‌صورت ثابت تعریف کنید، نه در تنظیمات یک افزونهٔ متفرقه و نه داخل فایل قالب. سه دلیل: هنگام به‌روزرسانی قالب پاک نمی‌شود، در نسخهٔ گیت پروژه نمی‌نشیند، و اگر روزی لو رفت، از پنل «API نرخ ارز» می‌توانید کلید را بچرخانید و کلید قبلی همان لحظه از کار می‌افتد.

اگر کلید را برای ویجت در صفحه می‌گذارید، حتماً قفل دامنه را تأیید کنید. قفل دامنه با Referer صفحهٔ میزبان کار می‌کند، پس اگر سایتتان با Referrer-Policy: no-referrer سرو می‌شود، ویجت دامنه را تشخیص نمی‌دهد و کار نمی‌کند.

تازه‌سازی در پس‌زمینه، نه روی دوش بازدیدکننده

در الگوی بالا یک ریزه‌کاری باقی می‌ماند: وقتی مدت کش تمام می‌شود، اولین بازدیدکنندهٔ بعدی همان کسی است که منتظر پاسخ سرویس می‌ماند. برای سایت کم‌بازدید مهم نیست، ولی اگر صفحهٔ اول سایت است، بهتر است تازه‌سازی را به کرون وردپرس بسپارید و کش را همیشه پر نگه دارید:

// Warm the cache in the background instead of on a visitor request.
add_action('netarz_fx_refresh', 'netarz_fx_warm');

function netarz_fx_warm() {
    delete_transient('netarz_fx_board');
    netarz_fx_board();
}

add_filter('cron_schedules', function ($schedules) {
    $schedules['netarz_fx_five_minutes'] = [
        'interval' => 300,
        'display'  => 'هر پنج دقیقه',
    ];
    return $schedules;
});

add_action('init', function () {
    if (!wp_next_scheduled('netarz_fx_refresh')) {
        wp_schedule_event(time(), 'netarz_fx_five_minutes', 'netarz_fx_refresh');
    }
});

یک نکتهٔ مهم: کرون وردپرس با بازدید صفحه اجرا می‌شود، پس روی سایت خیلی کم‌بازدید سر وقت اجرا نمی‌شود. اگر به دقت نیاز دارید، کرون وردپرس را خاموش و یک کرون واقعی روی سرور تنظیم کنید که هر پنج دقیقه همان آدرس را صدا بزند.

اگر نمی‌خواهید به functions.php دست بزنید

همهٔ کاری که در این مقاله با کد کردیم، کش، شورت‌کد و نگه داشتن آخرین نرخ سالم، به‌صورت یک افزونهٔ متن‌باز هم آماده است: netarz-fx-wordpress. فایل ZIP را از گیت‌هاب بگیرید، از «افزونه‌ها ← افزودن ← بارگذاری افزونه» نصب کنید و کلید را در «تنظیمات ← نرخ ارز نِت اَرز» بگذارید. شورت‌کدهایش [netarz_rate currency="usd"] و [netarz_rates currencies="usd,eur,aed"] است و دکمهٔ «آزمایش اتصال» اگر IP سرور مجاز نباشد، همان IPی را که ما دیده‌ایم نشان می‌دهد. افزونه در مخزن افزونه‌های وردپرس نیست؛ نسخهٔ رسمی فقط همان مخزن گیت‌هاب است. نمونه‌کدهای PHP و پایتون همین وب‌سرویس هم در کدهای متن‌باز نِت اَرز معرفی شده‌اند.

چک‌لیست قبل از انتشار

  1. تابلو با کش پنج‌دقیقه‌ای کار می‌کند و با قطع شدن سرویس، صفحه سفید نمی‌شود.
  2. IP سرور وردپرس در فهرست IPهای مجاز اپ ثبت شده است.
  3. زمان نرخ کنار عدد نوشته شده؛ در طرح رایگان نرخ پانزده دقیقه تأخیر دارد و پنهان کردنش درست نیست.
  4. اگر قیمت ووکامرس را تبدیل می‌کنید، یک سفارش آزمایشی ثبت و فاکتورش را با سبد مقایسه کنید.
  5. کلید در wp-config.php است، نه در فایل قالب.

همین الگو را برای صفحهٔ ساده و بدون وردپرس هم در نمایش نرخ ارز زنده در سایت نوشته‌ایم، و اگر می‌خواهید همین داده را در صفحهٔ گسترده داشته باشید، نرخ دلار در گوگل شیت و اکسل نسخهٔ آماده دارد. برای وصل کردن سرویس‌های دیگر به وردپرس هم افزونهٔ هوش مصنوعی وردپرس با API اختصاصی همین ساختار را دنبال می‌کند. لایسنس‌های قالب و افزونهٔ اصلی را هم در دستهٔ لایسنس وب‌مستری می‌بینید، و اگر هاست را هم عوض می‌کنید دامنه و هاست خارجی گزینه‌ها را کنار هم گذاشته است.

پرسش‌های پرتکرار

افزونهٔ آماده بگیرم یا خودم شورت‌کد بنویسم؟

اگر فقط تابلوی نمایشی می‌خواهید، ویجت در یک شورت‌کد سه خطی کافی است و هیچ باری روی سرور نمی‌گذارد. اگر می‌خواهید خود عدد را در متن یا محاسبه به کار ببرید، شورت‌کد با کش transient بنویسید؛ افزونه‌ای که در هر بازدید یک درخواست می‌فرستد، سایت را کند می‌کند.

قیمت ووکامرس را چطور از دلار به تومان نشان بدهم؟

قیمت پایه را دلاری نگه دارید و تبدیل را با فیلتر قیمت در لحظهٔ نمایش انجام بدهید، با نرخی که از کش می‌آید. حواستان به سه چیز باشد: قیمت سفارش‌های ثبت‌شده نباید بازخوانی شود، کش صفحه را با مدت transient هماهنگ کنید، و گرد کردن را فقط در یک جا انجام بدهید.

کلید API را کجای وردپرس بگذارم؟

در wp-config.php به‌صورت یک ثابت. این‌طور با به‌روزرسانی قالب پاک نمی‌شود و در مخزن کد پروژه نمی‌نشیند. اگر کلید لو رفت، از پنل «API نرخ ارز» بچرخانیدش تا کلید قبلی همان لحظه از کار بیفتد.

سایت روی هاست اشتراکی است و خطای 403 می‌گیرم.

فراخوانی از سرور هدر Origin ندارد، پس با IP بررسی می‌شود و IP خروجی هاست باید در فهرست IPهای مجاز اپ باشد. روی هاست اشتراکی این IP را پشتیبانی هاست به شما می‌گوید. برای ویجت این مشکل پیش نمی‌آید، چون درخواست از مرورگر بازدیدکننده می‌رود.

محصولات مرتبط با این مقاله

مطالب مشابه

نظر خوانندگان

هنوز نظری ثبت نشده. اگر این مقاله پرسشتان را جواب داد یا جای چیزی در آن خالی ماند، همین‌جا بنویسید.

نظرتان را بنویسید

این مقاله چقدر به کارتان آمد؟ (اختیاری)

نظرها را پیش از انتشار بررسی می‌کنیم. نقد صریح مشکلی ندارد؛ تبلیغ و توهین منتشر نمی‌شود.