مقاله آموزشی

قیمت‌گذاری خودکار محصولات ووکامرس بر اساس دلار

نویسنده: تیم نِت اَرز ۱۰ دقیقه مطالعه ۴ بازدید

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

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

افزونهٔ نرخ ارز نِت اَرز چه می‌کند و چه نمی‌کند

افزونهٔ رسمی ما برای وردپرس (NetArz FX، نسخهٔ ۱.۱.۰) قیمت محصولات را عوض نمی‌کند. کارش نمایش نرخ است: شورت‌کد برای یک نرخ یا جدول، مبدل ارز، سه بلوک برای ویرایشگر و یک ابزارک (ویجت) کناری. نصب و تنظیمش را در افزونهٔ نرخ ارز وردپرس نوشته‌ایم.

یک گزینهٔ ووکامرس هم دارد که پیش‌فرض خاموش است: «معادل تومانی کنار قیمت محصولات نشان داده شود». این گزینه برای فروشگاهی است که قیمت‌هایش به دلار یا ارز دیگری است. کنار هر قیمت «حدود … تومان» می‌نویسد، از همان نرخ‌های کش‌شده و فقط در حالت اتصال از سرور. مبلغی که مشتری می‌پردازد همان مبلغ دلاری می‌ماند و روی محصول متغیر هم، که بازهٔ قیمت دارد، چیزی نمی‌نویسد.

اگر فروشگاه شما به تومان است و می‌خواهید خودِ قیمت با دلار بالا و پایین برود، به چیزی بیشتر از نمایش نیاز دارید. سه راه هست:

روشقیمتی که در فروشگاه ذخیره می‌شودفیلتر بازهٔ قیمت و خوراک سایت‌های مقایسهمناسب برای
گزینهٔ «معادل تومانی» افزونهدلاربا عدد دلاریفروشگاه دلاری که خریدار ایرانی هم دارد
تبدیل در لحظهٔ نمایش با فیلتردلارهر چیزی که قیمت را مستقیم از دیتابیس بخواند، دلار می‌بیندفروشگاه کوچک بدون فیلتر قیمت
بازنویسی قیمت هر ساعت (این مقاله)توماندرست، با عدد تومانیفروشگاه تومانی با قیمت خرید دلاری

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

قدم اول: قیمت دلاری کنار هر محصول

یک افزونهٔ کوچک می‌سازیم: پوشهٔ wp-content/plugins/ntz-usd-pricing و فایل ntz-usd-pricing.php داخلش. بخش اول یک فیلد «قیمت دلاری» به برگهٔ «همگانی» ویرایش محصول اضافه می‌کند و هنگام ذخیرهٔ محصول آن را نگه می‌دارد:

<?php
/**
 * Plugin Name: NTZ USD pricing
 * Description: Reprices WooCommerce products from a USD base price every hour.
 */
if (! defined('ABSPATH')) {
    exit;
}

const NTZ_MARKUP = 1.15;   // your margin on top of the dollar cost
const NTZ_ROUND  = 1000;   // round up to a clean step (Toman)
const NTZ_JUMP   = 0.08;   // a rate move above 8% waits for a human

add_action('woocommerce_product_options_pricing', function () {
    woocommerce_wp_text_input([
        'id'        => '_ntz_usd_price',
        'label'     => 'قیمت دلاری ($)',
        'data_type' => 'price',
    ]);
});

add_action('woocommerce_admin_process_product_object', function ($product) {
    if (isset($_POST['_ntz_usd_price'])) {
        $product->update_meta_data('_ntz_usd_price', wc_format_decimal(wp_unslash($_POST['_ntz_usd_price'])));
    }
});

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

قدم دوم: نرخ دلار از کجا بیاید

اگر افزونهٔ NetArz FX را نصب کرده‌اید، تابلوی نرخ را خودش کش می‌کند و بهتر است از همان بخوانیم تا درخواست دوباره‌ای نرود. اگر نصب نیست، تابع مستقیم سراغ API می‌رود. کلید را در wp-config.php تعریف کنید، نه در کد افزونه:

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

function ntz_usd_rate(): ?float
{
    // NetArz FX 1.1.0 keeps one cached board; reuse it when the plugin is active.
    if (class_exists('Netarz_FX_Client')) {
        $row = Netarz_FX_Client::rate('USD');
        if ($row && ! empty($row['sell'])) {
            return (float) $row['sell'];
        }
    }
    if (! defined('NETARZ_FX_KEY')) {
        return null;
    }
    $res = wp_remote_get('https://netarz.ir/api/fx/v1/rates/USD', [
        'headers' => ['Authorization' => 'Bearer ' . NETARZ_FX_KEY],
        'timeout' => 10,
    ]);
    if (is_wp_error($res) || wp_remote_retrieve_response_code($res) !== 200) {
        return null;
    }
    $body = json_decode(wp_remote_retrieve_body($res), true);

    return isset($body['data']['sell']) ? (float) $body['data']['sell'] : null;
}

سه نکته در همین چند خط هست. نرخ فروش (sell) را برمی‌داریم، چون شما برای جایگزین کردن کالا باید دلار بخرید و خرید و فروش در این سرویس از دید صرافی نوشته شده. کلاس Netarz_FX_Client بخش داخلی افزونه است، نه یک رابط رسمی؛ اگر در نسخه‌ای عوض شود، تابع بی‌سروصدا به درخواست مستقیم برمی‌گردد. و درخواست از سرور وردپرس می‌رود، پس IP خروجی سرور باید در فهرست IPهای مجاز اپ باشد؛ اگر خطای ip_not_allowed گرفتید، راه‌حل خطای ip_not_allowed را ببینید. این‌که عدد دلار از کدام بازار می‌آید هم در چهار نرخ ارز در ایران توضیح داده شده.

قدم سوم: بازنویسی قیمت‌ها هر ساعت

حالا بخش اصلی. هر ساعت یک بار نرخ را می‌گیریم، اگر نرخی نبود یا جهش غیرعادی داشت کاری نمی‌کنیم، و برای هر محصولی که قیمت دلاری دارد، قیمت تومانی را با سود و گرد کردن دوباره می‌نویسیم:

add_action('ntz_reprice', function () {
    $rate = ntz_usd_rate();
    if (! $rate) {
        return;                                    // no rate: keep the current prices
    }

    $last = (float) get_option('ntz_last_rate', 0);
    if ($last > 0 && abs($rate - $last) / $last > NTZ_JUMP) {
        update_option('ntz_rate_needs_review', $rate);
        return;                                    // a jump this large waits for a human
    }

    $ids = get_posts([
        'post_type'   => 'product',
        'post_status' => 'any',
        'numberposts' => -1,
        'fields'      => 'ids',
        'meta_key'    => '_ntz_usd_price',
    ]);

    foreach ($ids as $id) {
        $product = wc_get_product($id);
        if (! $product || ! $product->is_type('simple')) {
            continue;
        }
        $usd = (float) $product->get_meta('_ntz_usd_price');
        if ($usd <= 0) {
            continue;
        }
        $toman = ceil($usd * $rate * NTZ_MARKUP / NTZ_ROUND) * NTZ_ROUND;
        if ((float) $product->get_regular_price() !== (float) $toman) {
            $product->set_regular_price($toman);
            $product->save();                      // also refreshes the active price and lookup table
        }
    }

    update_option('ntz_last_rate', $rate);
});

add_action('init', function () {
    if (! wp_next_scheduled('ntz_reprice')) {
        wp_schedule_event(time() + 60, 'hourly', 'ntz_reprice');
    }
});

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

سود، گرد کردن و محافظ جهش، به زبان ساده

  • NTZ_MARKUP ضریب سود شماست. ۱٫۱۵ یعنی پانزده درصد روی هزینهٔ دلاری. اگر برای دسته‌های مختلف سود متفاوت دارید، به‌جای یک ثابت آن را از یک فیلد دیگر محصول بخوانید.
  • NTZ_ROUND قیمت را به سمت بالا روی پله‌ای تمیز گرد می‌کند تا مشتری عددی با رقم‌های آخر عجیب نبیند. گرد کردن فقط همین یک جا انجام می‌شود، پس سبد خرید و فاکتور با هم می‌خوانند.
  • NTZ_JUMP محافظ روز بد است. اگر نرخ در یک ساعت بیش از هشت درصد تکان بخورد، افزونه قیمت‌ها را دست نمی‌زند و نرخ تازه را در گزینهٔ ntz_rate_needs_review نگه می‌دارد تا خودتان تصمیم بگیرید. یک عدد اشتباه نباید در یک ساعت کل فروشگاه را ارزان یا گران کند.
  • مالیات در این محاسبه نیست. اگر مالیات بر ارزش افزوده را در تنظیمات ووکامرس روشن کرده‌اید، همان‌جا روی قیمت جدید حساب می‌شود.

دو حالت را این نمونه عمداً کنار گذاشته است. قیمت حراج (sale) دست نمی‌خورد؛ اگر حراج دلاری می‌خواهید، فیلد دومی مثل _ntz_usd_sale بسازید و با set_sale_price() همین کار را تکرار کنید. محصول متغیر هم در حلقه نیست، چون هر گونه (variation) قیمت خودش را دارد؛ فیلد را با قلاب woocommerce_variation_options_pricing به گونه‌ها اضافه کنید و در حلقه نوع product_variation را هم بیاورید.

کرون وردپرس را به کرون واقعی بسپارید

زمان‌بندی وردپرس (WP-Cron) فقط وقتی اجرا می‌شود که کسی سایت را باز کند. در ساعت‌های خلوت ممکن است قیمت‌ها یک ساعت دیرتر به‌روز شوند. اگر به سرور یا کنترل‌پنل هاست دسترسی دارید، اجرای خودکار وردپرس را خاموش و یک کرون واقعی تعریف کنید:

# wp-config.php
define('DISABLE_WP_CRON', true);

# crontab -e   (every 15 minutes; WordPress runs the hourly job when it is due)
*/15 * * * * curl -s "https://example.com/wp-cron.php?doing_wp_cron" > /dev/null

با اجرای ساعتی، افزونه روزی حدود ۲۴ درخواست به سرویس نرخ می‌فرستد، و اگر افزونهٔ NetArz FX نصب باشد همان هم از کش آن خوانده می‌شود. این مقدار کسر کوچکی از سهمیهٔ روزانهٔ طرح رایگان است و تأخیر نرخ در این طرح هم برای قیمتی که ساعتی به‌روز می‌شود اهمیتی ندارد. سهمیهٔ دقیق هر طرح در صفحهٔ طرح‌ها است.

پیش از روشن کردن در فروشگاه واقعی

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

کد کامل افزونهٔ نرخ ارز و نمونه‌های دیگرش در مخزن netarz-fx-wordpress در گیت‌هاب است. اگر قیمت‌گذاری خودکار را برای افزونهٔ رسمی می‌خواهید، همان‌جا در بخش Issues بنویسید؛ درخواست‌های واقعی فروشگاه‌ها مسیر نسخهٔ بعد را روشن می‌کند.

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

افزونهٔ NetArz FX قیمت محصولات را خودکار با دلار عوض می‌کند؟

نه. افزونه نرخ را نمایش می‌دهد و یک گزینهٔ ووکامرس دارد که کنار قیمت ارزی محصولات «حدود … تومان» می‌نویسد؛ مبلغ پرداختی عوض نمی‌شود. برای این‌که خودِ قیمت تومانی با نرخ دلار به‌روز شود، همان افزونهٔ کوچکی را که در این مقاله ساختیم کنارش نصب کنید.

قیمت سفارش‌هایی که قبلاً ثبت شده هم عوض می‌شود؟

نه. ووکامرس مبلغ هر سفارش را هنگام ثبت در خود سفارش ذخیره می‌کند و بازنویسی قیمت محصول به آن دست نمی‌زند. فقط سبدهایی که هنوز پرداخت نشده‌اند با قیمت تازه حساب می‌شوند.

با طرح رایگان API نرخ ارز کار می‌کند؟

بله. به‌روزرسانی ساعتی روزی حدود ۲۴ درخواست است که کسر کوچکی از سهمیهٔ طرح رایگان است. تأخیر نرخ در طرح رایگان هم برای قیمتی که ساعتی یک بار عوض می‌شود فرقی ایجاد نمی‌کند.

اگر سرویس نرخ جواب ندهد، قیمت‌ها صفر می‌شوند؟

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

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

مطالب مشابه

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

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

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

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

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