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

سایت حراجی با چند لایه داده حساس سر و کار دارد: پیشنهادها، اعتبار خریدار، داده پرداخت، و تاریخچه مزایده. هر لایه نیازمند سطح دسترسی متفاوتی است. مدل نقش پیش‌فرض وردپرس، این تفکیک را نمی‌شناسد. بدون تنظیمات مزایده‌ای، خطر تقلب، نشت داده مالی، و از دست رفتن اعتماد کاربران وجود دارد. راه‌حل عملی، تعریف نقش‌های سفارشی، ثبت تغییرناپذیر پیشنهادها، و پیاده‌سازی State Machine مزایده است. این مقاله معماری کامل این حوزه را بررسی می‌کند.

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

چرا نقش‌های پیش‌فرض وردپرس کافی نیستند؟

وردپرس شش نقش پیش‌فرض دارد که حول مدیریت محتوا ساخته شده‌اند. در سایت حراجی، این نقش‌ها با شکاف‌های زیر روبه‌رو می‌شوند:

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

در سایت حراجی، هر پیشنهاد یک سند حقوقی است که باید تغییرناپذیر باشد.

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

نقش‌های ضروری در سایت حراجی

خریدار (Bidder)

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

فروشنده (Seller)

ثبت آیتم مزایده، تعیین قیمت پایه، مشاهده پیشنهادها پس از پایان مزایده، بدون امکان پیشنهاد خودش.

اپراتور مزایده (Auction Operator)

شروع و پایان مزایده، نظارت بر انصاف، رسیدگی به شکایات.

ناظر مالی (Financial Auditor)

دسترسی به داده پرداخت پس از پایان مزایده، بدون دسترسی به داده هویتی خریداران.

مدیر پلتفرم (Platform Manager)

مدیریت کلی، گزارش تجمیعی، بدون دسترسی مستقیم به پیشنهادها.

چالش‌های مزایده و انصاف

پیش از ورود به پیاده‌سازی، باید چالش‌های خاص این حوزه را دقیق بشناسیم.

۱. تغییرناپذیری پیشنهاد

هر پیشنهاد باید پس از ثبت، تغییرناپذیر باشد. این نیازمند یک لایه Append-Only Storage است که فقط اجازه درج می‌دهد. برای مطالعه بیشتر، مطلب پیاده‌سازی درست Custom Post Type را ببینید.

۲. همزمانی (Concurrency)

چند خریدار ممکن است در یک لحظه پیشنهاد ثبت کنند. سیستم باید بتواند پیشنهادها را به‌صورت Atomic ثبت کند تا از Race Condition جلوگیری شود. برای مطالعه بیشتر، مطلب کار با Options API در وردپرس را ببینید.

۳. اعتبارسنجی خریدار

خریدار باید پیش از پیشنهاد، اعتبارسنجی شود تا از پیشنهادات جعلی جلوگیری شود. این نیازمند یک لایه KYC (Know Your Customer) سبک است.

۴. کف و سقف قیمت

هر آیتم مزایده یک قیمت پایه و در صورت نیاز، سقف دارد. این مقادیر باید در متادیتای آیتم ذخیره شوند. برای مطالعه بیشتر، مطلب فیلدهای سفارشی ACF و کاربردهای واقعی آن را ببینید.

۵. زمان‌بندی دقیق

زمان پایان مزایده باید دقیق باشد و پس از آن، هیچ پیشنهادی پذیرفته نشود. این نیازمند یک لایه Time Synchronization است که از ساعت سرور مرجع استفاده کند.

تنظیمات مزایده‌ای برای هر نقش

نقش ثبت پیشنهاد مشاهده پیشنهاد ایجاد مزایده
خریدار بله فقط خودش خیر
فروشنده خیر پس از پایان بله
اپراتور خیر کامل مدیریت
ناظر مالی خیر پس از پایان خیر
مدیر پلتفرم خیر گزارش تجمیعی مدیریت

پیاده‌سازی با PHP

ابتدا CPT آیتم مزایده را می‌سازیم. برای مطالعه بیشتر، مطلب پیاده‌سازی درست Custom Post Type را ببینید.

function wk_register_auction_item_cpt() {
    register_post_type('wk_auction_item', array(
        'label' => 'آیتم‌های مزایده',
        'public' => false,
        'show_ui' => true,
        'capability_type' => 'wk_auction_item',
        'capabilities' => array(
            'read_post' => 'read_wk_auction_item',
            'edit_post' => 'edit_wk_auction_item',
            'edit_posts' => 'edit_wk_auction_items',
            'edit_others_posts' => 'edit_others_wk_auction_items',
        ),
        'map_meta_cap' => true,
        'supports' => array('title', 'editor', 'author', 'custom-fields'),
    ));
}
add_action('init', 'wk_register_auction_item_cpt');

سپس نقش‌های سفارشی را می‌سازیم. برای درک بهتر هوک‌ها، مطلب هوک‌های وردپرس: قلب تپنده توسعه را ببینید.

function wk_register_auction_roles() {
    add_role('wk_bidder', 'خریدار', array(
        'read' => true,
        'read_wk_auction_items' => true,
        'place_wk_bid' => true,
        'read_own_wk_bids' => true,
    ));

    add_role('wk_seller', 'فروشنده', array(
        'read' => true,
        'edit_wk_auction_items' => true,
        'publish_wk_auction_items' => true,
        'read_wk_bids_after_end' => true,
    ));

    add_role('wk_auction_operator', 'اپراتور مزایده', array(
        'read' => true,
        'start_wk_auction' => true,
        'end_wk_auction' => true,
        'read_all_wk_bids' => true,
        'resolve_wk_disputes' => true,
    ));

    add_role('wk_financial_auditor', 'ناظر مالی', array(
        'read' => true,
        'read_wk_payment_data' => true,
        'export_wk_financial_reports' => true,
    ));
}
add_action('init', 'wk_register_auction_roles');

برای اعتبارسنجی و پاک‌سازی داده‌های ورودی، مطلب اعتبارسنجی داده‌ها در کدنویسی وردپرس و پاک‌سازی داده‌ها در کدنویسی وردپرس را ببینید.

ثبت تغییرناپذیر پیشنهادها

هر پیشنهاد باید به‌صورت تغییرناپذیر ثبت شود و از Race Condition جلوگیری شود:

function wk_place_bid($user_id, $item_id, $amount) {
    if (!current_user_can('place_wk_bid')) {
        return new WP_Error('forbidden', 'دسترسی غیرمجاز');
    }
    global $wpdb;
    $lock_key = 'wk_auction_lock_' . $item_id;
    if (!wp_cache_add($lock_key, 1, '', 5)) {
        return new WP_Error('locked', 'سیستم مشغول است، دوباره تلاش کنید');
    }
    $current_max = (float) get_post_meta($item_id, 'wk_current_max_bid', true);
    $base_price = (float) get_post_meta($item_id, 'wk_base_price', true);
    $min_bid = max($current_max, $base_price) + 1;
    if ($amount < $min_bid) {
        wp_cache_delete($lock_key);
        return new WP_Error('too_low', 'پیشنهاد باید بالاتر از قیمت فعلی باشد');
    }
    $bid_id = wp_insert_post(array(
        'post_type' => 'wk_bid',
        'post_status' => 'publish',
        'post_author' => $user_id,
        'post_title' => sprintf('Bid #%d', $item_id),
    ));
    update_post_meta($bid_id, 'wk_item_id', $item_id);
    update_post_meta($bid_id, 'wk_amount', $amount);
    update_post_meta($bid_id, 'wk_bid_hash', hash('sha256', sprintf('%d|%d|%f|%d', $item_id, $user_id, $amount, time())));
    update_post_meta($bid_id, 'wk_created_at', current_time('mysql'));
    update_post_meta($item_id, 'wk_current_max_bid', $amount);
    wp_cache_delete($lock_key);
    return $bid_id;
}

نکته کلیدی: استفاده از wp_cache_add به‌عنوان قفل سبک برای جلوگیری از همزمانی. برای مطالعه بیشتر، مطلب ترنزینت وردپرس و کش هوشمند بدون افزونه را ببینید.

State Machine مزایده

هر آیتم مزایده از چند وضعیت عبور می‌کند که انتقال‌های آن باید کنترل‌شده باشد:

function wk_transition_auction_state($item_id, $new_state, $actor_id) {
    $allowed = array(
        'draft' => array('scheduled', 'cancelled'),
        'scheduled' => array('active', 'cancelled'),
        'active' => array('ended', 'cancelled'),
        'ended' => array('settled', 'disputed'),
        'disputed' => array('settled', 'cancelled'),
    );
    $current = get_post_meta($item_id, 'wk_auction_state', true);
    if (!isset($allowed[$current]) || !in_array($new_state, $allowed[$current], true)) {
        return new WP_Error('invalid_transition', 'انتقال وضعیت مجاز نیست');
    }
    update_post_meta($item_id, 'wk_auction_state', $new_state);
    update_post_meta($item_id, 'wk_last_actor', $actor_id);
    update_post_meta($item_id, 'wk_state_changed_at', current_time('mysql'));
    return true;
}

امنیت و حریم خصوصی

سه لایه امنیتی ضروری است:

  1. ثبت تغییرناپذیر هر پیشنهاد با Hash محتوا برای ممیزی.
  2. استفاده از قفل سبک برای جلوگیری از Race Condition در همزمانی.
  3. ثبت لاگ هر پیشنهاد و هر تغییر وضعیت برای استناد حقوقی.

برای مطالعه بیشتر درباره کدنویسی امن، مطلب نوشتن کد PHP امن برای وردپرس را ببینید. برای Nonce، مطلب نانس وردپرس و امنیت فرم را ببینید. برای دفع Brute Force، مطلب دفع حملات Brute Force در وردپرس را ببینید.

مرجع فنی این حوزه در ویکی‌پدیا، مقاله Auction مرور خوبی از انواع مزایده و اصول آن ارائه می‌دهد.

پرسش‌های پرتکرار درباره تنظیمات نقش مزایده

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

خیر. Subscriber فقط می‌تواند پست بخواند، اما هیچ مکانیزمی برای ثبت پیشنهاد یا کنترل زمان مزایده ندارد. باید نقش سفارشی با Capability اختصاصی بسازید.

چگونه از تقلب خریدار و فروشنده جلوگیری کنم؟

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

چگونه همزمانی پیشنهادها را مدیریت کنم؟

با استفاده از قفل سبک (Light Lock) بر پایه wp_cache_add که در بازه کوتاه، از ثبت همزمان جلوگیری می‌کند.

آیا باید پیشنهادها را تغییرناپذیر نگه دارم؟

بله. هر پیشنهاد یک سند مالی است که باید تغییرناپذیر باشد. حتی پس از پایان مزایده، نباید قابل ویرایش باشد.

چگونه زمان پایان مزایده را دقیق کنترل کنم؟

با استفاده از ساعت سرور مرجع (NTP) و بررسی دقیق زمان در هر درخواست. زمان پایان باید در متادیتای آیتم ذخیره شود.

اشتباهات رایج

اشتباه پیامد راه‌حل
پیشنهاد قابل ویرایش نقض انصاف مزایده ثبت تغییرناپذیر با Hash
نبود قفل در همزمانی Race Condition و ثبت پیشنهاد نامعتبر Light Lock با wp_cache_add
نقش یکسان برای خریدار و فروشنده تقلب احتمالی نقش سفارشی مجزا
نبود State Machine وضعیت نامعتبر مزایده State Machine با انتقال معتبر
نبود لاگ پیشنهادها عدم امکان استناد حقوقی ثبت هر پیشنهاد در جدول اختصاصی

نگاه معماری در سطح سیستم‌های مزایده

در سطح معماری، سایت حراجی با یک چالش بنیادین روبه‌روست: هر پیشنهاد باید در یک بازه زمانی دقیق و با تضمین انصاف ثبت شود. این یعنی نیازمند یک معماری «Event-Sourced» هستیم که هر رویداد را تغییرناپذیر ثبت می‌کند.

رویکرد پیشنهادی، معماری «Event Sourcing + CQRS» است. هر پیشنهاد یک Event تغییرناپذیر است که در یک Event Store ذخیره می‌شود. وضعیت فعلی مزایده از تجمیع این رویدادها محاسبه می‌شود. این معماری، هم انصاف را تضمین می‌کند و هم ممیزی را ممکن می‌سازد.

نکته دوم، بحث «Anti-Sniping Extension» است. در حراجی‌های حرفه‌ای، اگر پیشنهادی در ۶۰ ثانیه آخر ثبت شود، زمان مزایده به‌طور خودکار تمدید می‌شود. این نیازمند یک لایه زمان‌بندی پویا است.

نکته سوم، بحث «Shill Bidding Detection» است. اگر خریدار و فروشنده هماهنگ باشند، می‌توانند قیمت را مصنوعی بالا ببرند. این نیازمند یک سیستم تحلیل رفتار است که الگوهای مشکوک را تشخیص دهد.

پیشنهادهای عملی برای پیاده‌سازی

سه توصیه عملی که در پروژه‌های واقعی مؤثر بوده‌اند:

  1. هر پیشنهاد را با Hash محتوا و امضای زمانی ثبت کنید تا در صورت اختلاف، قابل استناد باشد.
  2. از قفل سبک برای جلوگیری از همزمانی استفاده کنید، اما زمان قفل را کوتاه نگه دارید تا تجربه کاربری آسیب نبیند.
  3. یک Endpoint عمومی برای تأیید اصالت پیشنهاد طراحی کنید تا در صورت شکایت، قابل استناد باشد.

اگر این چالش‌ها را در یک پروژه واقعی تجربه کرده‌اید، برای ما جالب است بدانید کدام بخش بیشترین زمان را گرفت: پیاده‌سازی قفل، State Machine، یا تشخیص تقلب. تجربه خود را در دیدگاه‌ها بنویسید.