🏪 فصل ۱۲: مارکتپلیس چندفروشنده
در این فصل یاد میگیرید چطور ووکامرس را به یک مارکتپلیس چندفروشنده (مثل دیجیکالا، باسلام) تبدیل کنید — هم با افزونههای آماده و هم با پیادهسازی اختصاصی.
🎯 مفهوم Multi-Vendor
در یک مارکتپلیس:
- چندین فروشنده روی یک سایت محصول میفروشند
- سایت اصلی از هر فروش کمیسیون میگیرد
- هر فروشنده داشبورد، آمار، پروفایل اختصاصی دارد
- خریدار از چند فروشنده در یک سفارش میخرد
- سفارش به فروشندگان مربوطه split میشود
📊 مقایسه افزونههای موجود
| افزونه | ویژگیهای شاخص | قیمت |
|---|---|---|
| Dokan Lite | محبوبترین، رابط زیبا، RTL | رایگان + Pro $149 |
| WC Vendors | سبک، انعطافپذیر | رایگان + Pro $159 |
| WCFM Marketplace | کاملترین، RTL، چت داخلی | رایگان + Ultimate $79 |
| YITH Multi Vendor | یکپارچه با اکوسیستم YITH | $149/سال |
✅ توصیه برای ایران: Dokan یا WCFM – هر دو رایگان شروع میشوند، RTL کامل دارند، و امکان توسعه گسترده دارند.
🆔 ساخت نقش Vendor از صفر
اگر میخواهید بدون افزونه و کاملاً سفارشی پیادهسازی کنید:
includes/setup-vendor-role.php
// ساخت نقش Vendor در فعالسازی
register_activation_hook(__FILE__, function() {
add_role('vendor', 'فروشنده', [
'read' => true,
'edit_posts' => false,
'delete_posts' => false,
'edit_products' => true,
'publish_products' => false, // نیاز به تایید
'delete_products' => true,
'edit_published_products'=> true,
'edit_others_products' => false, // فقط محصولات خود
'manage_woocommerce_orders' => false, // فقط سفارشهای خود
'view_woocommerce_reports' => true,
'upload_files' => true,
]);
});
// محدود کردن دسترسی به محصولات دیگران
add_action('pre_get_posts', function($query) {
if (!is_admin() || !$query->is_main_query()) return;
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
if (!$screen || $screen->post_type !== 'product') return;
if (current_user_can('vendor') && !current_user_can('manage_options')) {
$query->set('author', get_current_user_id());
}
});
// مخفی کردن منوهای غیرضروری از پنل
add_action('admin_menu', function() {
if (!current_user_can('vendor')) return;
remove_menu_page('index.php');
remove_menu_page('edit-comments.php');
remove_menu_page('themes.php');
remove_menu_page('plugins.php');
remove_menu_page('users.php');
remove_menu_page('tools.php');
remove_menu_page('options-general.php');
}, 999);
// تغییر ادمینبار برای فروشنده
add_action('admin_bar_menu', function($wp_admin_bar) {
if (!current_user_can('vendor')) return;
$wp_admin_bar->remove_node('comments');
$wp_admin_bar->remove_node('new-content');
$wp_admin_bar->add_node([
'id' => 'vendor-dashboard',
'title' => '📊 داشبورد فروشنده',
'href' => home_url('/vendor-dashboard/'),
]);
}, 999);
🛍️ محصولات هر فروشنده
نمایش فروشنده در صفحه محصول
add_action('woocommerce_single_product_summary', function() {
global $product;
$author_id = $product->get_meta('_vendor_id') ?: get_post_field('post_author', $product->get_id());
$vendor = get_userdata($author_id);
if (!$vendor || !in_array('vendor', $vendor->roles)) return;
$vendor_name = get_user_meta($vendor->ID, '_vendor_shop_name', true) ?: $vendor->display_name;
$vendor_url = get_user_meta($vendor->ID, '_vendor_shop_url', true);
?>
🏪 فروشنده:
= esc_html($vendor_name) ?>
ID, '_vendor_avg_rating', true);
if ($rating) {
echo '⭐ امتیاز: ' . esc_html($rating) . ' از ۵';
}
?>
صفحه فروشگاه فروشنده
// rewrite rule برای /vendor/{slug}/
add_action('init', function() {
add_rewrite_rule('^vendor/([^/]+)/?$', 'index.php?vendor_slug=$matches[1]', 'top');
add_rewrite_tag('%vendor_slug%', '([^&]+)');
});
// قالب
add_action('template_redirect', function() {
$slug = get_query_var('vendor_slug');
if (!$slug) return;
$vendor = get_user_by('slug', $slug);
if (!$vendor || !in_array('vendor', (array) $vendor->roles)) {
global $wp_query;
$wp_query->set_404();
return;
}
set_query_var('current_vendor', $vendor);
locate_template(['vendor-shop.php'], true);
exit;
});
// قالب vendor-shop.php
?>
<?php get_header(); ?>
<div class="vendor-shop">
<h1>🏪 <?= esc_html($vendor_name) ?></h1>
<?php
$products = wc_get_products([
'author' => $vendor->ID,
'limit' => 12,
'status' => 'publish',
]);
foreach ($products as $product) {
wc_get_template_part('content', 'product');
}
?>
</div>
<?php get_footer(); ?>
✂️ تقسیم سفارش بین فروشندگان
وقتی خریدار از چند فروشنده محصول داشته باشد، باید سفارش به sub-orderها تقسیم شود:
add_action('woocommerce_checkout_order_processed', function($order_id) {
$order = wc_get_order($order_id);
// گروهبندی آیتمها بر اساس فروشنده
$vendors_items = [];
foreach ($order->get_items() as $item) {
$product_id = $item->get_product_id();
$vendor_id = get_post_meta($product_id, '_vendor_id', true)
?: get_post_field('post_author', $product_id);
$vendors_items[$vendor_id][] = $item;
}
// اگر فقط یک فروشنده دارد، چیزی split نکن
if (count($vendors_items) <= 1) return;
// برای هر فروشنده، sub-order بساز
foreach ($vendors_items as $vendor_id => $items) {
$sub_order = wc_create_order([
'customer_id' => $order->get_customer_id(),
'parent' => $order_id,
]);
$sub_total = 0;
foreach ($items as $item) {
$sub_order->add_product(
$item->get_product(),
$item->get_quantity(),
['total' => $item->get_total()]
);
$sub_total += $item->get_total();
}
// کپی آدرس
$sub_order->set_address($order->get_address('billing'), 'billing');
$sub_order->set_address($order->get_address('shipping'), 'shipping');
$sub_order->update_meta_data('_vendor_id', $vendor_id);
$sub_order->update_meta_data('_parent_order_id', $order_id);
$sub_order->update_meta_data('_is_sub_order', 'yes');
// محاسبه کمیسیون
$commission_rate = get_user_meta($vendor_id, '_commission_rate', true) ?: 10; // ۱۰٪
$commission = ($sub_total * $commission_rate) / 100;
$vendor_earning = $sub_total - $commission;
$sub_order->update_meta_data('_commission_amount', $commission);
$sub_order->update_meta_data('_vendor_earning', $vendor_earning);
$sub_order->update_meta_data('_commission_rate', $commission_rate);
$sub_order->set_status('processing');
$sub_order->calculate_totals();
$sub_order->save();
// ثبت در جدول کمیسیون
record_commission($vendor_id, $sub_order->get_id(), $commission, $vendor_earning);
}
});
💰 محاسبه کمیسیون
جدول کمیسیونها
CREATE TABLE wp_icsd_vendor_commissions (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
vendor_id BIGINT UNSIGNED NOT NULL,
order_id BIGINT UNSIGNED NOT NULL,
sub_total DECIMAL(15,2) NOT NULL,
commission_rate DECIMAL(5,2) NOT NULL,
commission_amount DECIMAL(15,2) NOT NULL,
vendor_earning DECIMAL(15,2) NOT NULL,
status ENUM('pending', 'paid', 'cancelled') DEFAULT 'pending',
created_at DATETIME NOT NULL,
paid_at DATETIME,
KEY vendor_id (vendor_id),
KEY status (status)
);
کمیسیون پویا (بر اساس دسته)
function calculate_commission_rate($vendor_id, $product_id) {
// ۱. کمیسیون اختصاصی محصول
$product_rate = get_post_meta($product_id, '_custom_commission', true);
if ($product_rate !== '') return floatval($product_rate);
// ۲. کمیسیون اختصاصی دسته
$cats = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'ids']);
foreach ($cats as $cat_id) {
$cat_rate = get_term_meta($cat_id, '_category_commission', true);
if ($cat_rate !== '') return floatval($cat_rate);
}
// ۳. کمیسیون اختصاصی فروشنده
$vendor_rate = get_user_meta($vendor_id, '_commission_rate', true);
if ($vendor_rate !== '') return floatval($vendor_rate);
// ۴. پیشفرض سایت
return floatval(get_option('icsd_default_commission', 10));
}
کمیسیون پلکانی
// هرچه فروش بالاتر، کمیسیون کمتر
function tier_commission($vendor_id, $monthly_sales) {
$tiers = [
['min' => 0, 'rate' => 15], // 15٪
['min' => 5_000_000, 'rate' => 12],
['min' => 20_000_000, 'rate' => 10],
['min' => 50_000_000, 'rate' => 8],
['min' => 100_000_000, 'rate' => 5],
];
$rate = 15;
foreach ($tiers as $tier) {
if ($monthly_sales >= $tier['min']) {
$rate = $tier['rate'];
}
}
return $rate;
}
📊 داشبورد فروشنده
صفحه داشبورد در فرانتاند
// shortcode
add_shortcode('vendor_dashboard', function() {
if (!is_user_logged_in()) {
return wp_login_form(['echo' => false]);
}
$user = wp_get_current_user();
if (!in_array('vendor', $user->roles)) {
return 'شما فروشنده نیستید.
';
}
ob_start();
?>
get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}icsd_vendor_commissions
WHERE vendor_id = %d AND DATE(created_at) = CURDATE()",
$vendor_id
));
$month_earning = $wpdb->get_var($wpdb->prepare(
"SELECT SUM(vendor_earning) FROM {$wpdb->prefix}icsd_vendor_commissions
WHERE vendor_id = %d AND status = 'paid'
AND MONTH(created_at) = MONTH(NOW())",
$vendor_id
));
$pending_payout = $wpdb->get_var($wpdb->prepare(
"SELECT SUM(vendor_earning) FROM {$wpdb->prefix}icsd_vendor_commissions
WHERE vendor_id = %d AND status = 'pending'",
$vendor_id
));
$product_count = count_user_posts($vendor_id, 'product');
?>
سفارشهای امروز
= $today_orders ?>
درآمد ماه
= wc_price($month_earning) ?>
قابل برداشت
= wc_price($pending_payout) ?>
تعداد محصولات
= $product_count ?>
💸 سیستم تسویه
درخواست برداشت توسط فروشنده
// فرم درخواست برداشت
function vendor_payout_request_form() {
$vendor_id = get_current_user_id();
$available = get_vendor_available_balance($vendor_id);
if (isset($_POST['payout_amount']) && wp_verify_nonce($_POST['_payout_nonce'] ?? '', 'request_payout')) {
$amount = floatval($_POST['payout_amount']);
$iban = sanitize_text_field($_POST['iban'] ?? '');
if ($amount > $available) {
echo 'مبلغ بیشتر از موجودی است
';
} elseif ($amount < 100000) {
echo 'حداقل برداشت ۱۰۰ هزار تومان است
';
} else {
global $wpdb;
$wpdb->insert($wpdb->prefix . 'icsd_payout_requests', [
'vendor_id' => $vendor_id,
'amount' => $amount,
'iban' => $iban,
'status' => 'pending',
'created_at'=> current_time('mysql'),
]);
echo 'درخواست شما ثبت شد
';
}
}
?>
get_var($wpdb->prepare(
"SELECT COALESCE(SUM(vendor_earning), 0) FROM {$wpdb->prefix}icsd_vendor_commissions
WHERE vendor_id = %d AND status = 'pending'",
$vendor_id
));
$pending_payouts = $wpdb->get_var($wpdb->prepare(
"SELECT COALESCE(SUM(amount), 0) FROM {$wpdb->prefix}icsd_payout_requests
WHERE vendor_id = %d AND status IN ('pending', 'approved')",
$vendor_id
));
return max(0, $earned - $pending_payouts);
}
تایید و پرداخت توسط مدیر
// در پنل ادمین، صفحه payouts
add_action('admin_menu', function() {
add_menu_page('درخواستهای برداشت', 'برداشتها', 'manage_options',
'icsd-payouts', 'render_payouts_page', 'dashicons-money-alt', 56);
});
function render_payouts_page() {
global $wpdb;
if (isset($_POST['approve_id']) && check_admin_referer('approve_payout')) {
$id = absint($_POST['approve_id']);
$wpdb->update($wpdb->prefix . 'icsd_payout_requests',
['status' => 'paid', 'paid_at' => current_time('mysql')],
['id' => $id]);
// علامت کمیسیونهای مربوطه بهعنوان paid
// ... (پیادهسازی منطق)
}
$requests = $wpdb->get_results(
"SELECT r.*, u.display_name FROM {$wpdb->prefix}icsd_payout_requests r
JOIN {$wpdb->users} u ON r.vendor_id = u.ID
WHERE r.status = 'pending'"
);
?>
فروشنده مبلغ شبا تاریخ عملیات
= esc_html($req->display_name) ?>
= wc_price($req->amount) ?>
= esc_html($req->iban) ?>
= esc_html($req->created_at) ?>
📝 خلاصه فصل
- مفهوم Multi-Vendor و افزونههای آماده (Dokan، WCFM)
- ساخت نقش Vendor با capabilities محدود
- محدود کردن دسترسی به محصولات خود
- تقسیم سفارش به sub-order برای هر فروشنده
- کمیسیون پویا، پلکانی و دستهای
- داشبورد فرانتاند فروشنده
- سیستم درخواست برداشت با شبا
💡 پیشنهاد: برای پروژههای واقعی، شروع با Dokan یا WCFM Free بهتر است؛ سپس فقط بخشهای لازم را با کد سفارشی توسعه دهید.