Marketplace

Dokan Multi-Vendor Marketplace Development for a B2B Industrial-Parts Store

A B2B industrial-parts marketplace on Dokan Pro needed commissions that matched product margins and withdrawals that could not go negative. I rebuilt both, plus onboarding, shipping, accounting exports and page speed.

Client
B2B industrial-parts marketplace
My role
Lead WordPress / WooCommerce engineer
  • Dokan Pro
  • WooCommerce
  • WooCommerce Subscriptions
  • WP Rocket
  • Redis object cache
  • PHP
active vendors
340

active vendors

GMV processed per year
≈ $4.2M

GMV processed per year

withdrawal processing
5 days → same day

withdrawal processing

archive TTFB
4.8s → 1.6s

archive TTFB

The challenge

The client runs a B2B industrial-parts marketplace on Dokan Pro. Vendors fulfil their own orders, and the client also keeps a central warehouse for some SKUs.

Commission was a flat 10% for everyone. That cut margin on low-value, high-volume categories and overpaid the store on high-ticket items.

Vendors could also request withdrawals against pending order balances. When a buyer later charged back, the vendor's balance went negative. Support was reviewing about 5 withdrawals by hand every day.

What I built

  1. 01

    Tiered, category-aware commission engine

    Built on Dokan's dokan_get_seller_percentage filter. Each vendor's tier is stored in user meta, and low-margin categories get an override. All the rules live in one filter with a test matrix.

  2. 02

    Withdrawal guard

    Only completed-order funds can be withdrawn. A rolling hold period, configurable per vendor tier, and a negative-balance lock replaced the manual review queue.

  3. 03

    Per-vendor shipping zones

    Shipping rules by weight and dimensions for each vendor, with a warehouse-fulfilled fallback when a vendor has no zone for the destination.

  4. 04

    Vendor onboarding wizard

    KYC document upload, a store-category picker and a required-fields filter, so non-technical sellers could not submit an incomplete store.

  5. 05

    Accounting export

    CSV export and a scheduled email with the commission breakdown per order line, the VAT split and payout batch IDs.

  6. 06

    Front-end performance pass

    Object caching, removal of a slow meta_query on the shop archive, and lazy-loaded vendor maps.

Gotchas and how I fixed them

  • Dokan's percentage filter changes between versions

    The filter signature shifts from one Dokan release to another. I guard with function_exists and check the Dokan changelog before every release.

  • Renewal orders paid vendors nothing

    Some vendors sold subscriptions. Commission has to be calculated on renewal orders too, not only the parent order. I hook wcs_renewal_order_created and copy the vendor line-item meta from the parent. Without that, vendors are paid once and renewals silently pay 100% to the store.

  • Withdrawal race condition

    Two requests fired in the same second both passed the balance check. A short wp_cache_add lock keyed on the seller ID fixes it, with a database row lock where Redis is not available.

  • HPOS compatibility

    Compatibility with High-Performance Order Storage is declared with FeaturesUtil::declare_compatibility('custom_order_tables', ...).

Code from the build

Tiered Dokan commission (filter)php
add_filter( 'dokan_get_seller_percentage', function ( $percentage, $seller_id, $product_id, $category_id ) {
    $tier = get_user_meta( $seller_id, '_vendor_tier', true ) ?: 'standard';
    $tiers = [ 'standard' => 90, 'silver' => 92, 'gold' => 94 ]; // vendor keeps %
    $base  = $tiers[ $tier ] ?? 90;

    // Low-margin category override
    if ( in_array( (int) $category_id, [ 41, 42 ], true ) ) {
        $base -= 3;
    }

    return max( 0, min( 100, $base ) );
}, 10, 4 );

Commission is a business rule, not a number — keep it in one filter with a test matrix.

Withdrawal guard (completed funds only)php
add_filter( 'dokan_withdraw_request_validation_errors', function ( $errors, $seller_id, $amount ) {
    $withdrawable = dokan_get_seller_withdrawable_balance( $seller_id ); // completed only
    $pending      = dokan_get_seller_balance( $seller_id, false ) - $withdrawable;

    if ( $amount > $withdrawable ) {
        $errors[] = sprintf(
            __( 'Only completed-order funds are withdrawable. Pending: %s', 'my-textdomain' ),
            wc_price( $pending )
        );
    }

    return $errors;
}, 10, 3 );

The hook name varies by Dokan version — verify against the installed version and guard with function_exists.

HPOS declarationphp
add_action( 'before_woocommerce_init', function () {
    if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
            'custom_order_tables', __FILE__, true
        );
    }
} );

Skip this and your plugin silently breaks the day the client enables High-Performance Order Storage.

Results

  • 340 active vendors and about $4.2M in GMV a year processed through the marketplace.
  • Withdrawal processing went from 5 days of manual review to same-day automated payouts, recovering about 30 hours a month of admin time.
  • Marketplace archive TTFB dropped from 4.8s to 1.6s, and mobile LCP is under 2.5s.
  • Vendor-related support tickets fell about 40% in the first quarter after the onboarding and withdrawal changes.
  • Zero negative-balance incidents in 14 months after launch, down from about 6 a month.

Questions people ask

Can Dokan commission be different per vendor or per category?

Yes. On this marketplace I used the dokan_get_seller_percentage filter. Each vendor's tier (standard, silver or gold) is stored in user meta, and low-margin categories get an extra override. All of it sits in one filter, tested against a matrix of tiers and categories.

How do I stop Dokan vendors from withdrawing money that can still be charged back?

Limit withdrawals to completed-order funds, add a rolling hold period per vendor tier, and lock accounts with a negative balance. After I added these, the marketplace had zero negative-balance incidents in 14 months, compared with about 6 a month before.

Does Dokan pay vendors commission on WooCommerce Subscriptions renewals?

Not by default in this setup. Without extra work, vendors were paid on the parent order and renewals paid 100% to the store. Hooking wcs_renewal_order_created and copying the vendor line-item meta from the parent order fixes it.

How much faster can a Dokan marketplace get?

On this build, archive TTFB went from 4.8s to 1.6s. The changes were object caching with Redis, removing a slow meta_query on the shop archive, and lazy-loading vendor maps. Mobile LCP is now under 2.5s.