Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 7 min read

B2B Dealer Portals: Tier Pricing, Credit Limits, Bulk

A B2B dealer portal looks like an online store from the outside, but the hard parts are things a retail checkout never deals with: every buyer can see a different price for the same SKU, orders arrive as 80-line spreadsh

B2B Dealer Portals: Tier Pricing, Credit Limits, Bulk

A B2B dealer portal looks like an online store from the outside, but the hard parts are things a retail checkout never deals with: every buyer can see a different price for the same SKU, orders arrive as 80-line spreadsheets, and the most important check at checkout isn't the card. It's whether this dealer is still inside their credit limit. Get pricing and credit wrong and the portal creates more phone calls for your sales team than it removes.

I run Loop Origin, and my team built IvoryTex, a bilingual B2B wholesale platform with tiered pricing, minimum order quantities and buyer approval on Laravel 12, Vue 3 and PostgreSQL. I wrote this with help from AI tools and checked every query and claim myself. The code below is simplified from patterns we use in production.

What does a B2B dealer ordering portal need?

Direct answer: A dealer portal needs company accounts with role-based users, server-side price resolution (contract price, then tier price list, then base price), MOQ and case-pack rules, a credit exposure check before order submission, fast bulk entry (quick order, CSV upload, reorder), optional approval chains, and self-service invoices and statements synced with your ERP.

Accounts are companies, not people

In retail, one user is one customer. In B2B, the customer is a company with several people who do different things:

Role Can browse prices Can build cart Can submit order Can see invoices and statements
Buyer Yes Yes Up to an order limit No
Purchasing manager Yes Yes Yes, approves buyers' carts Optional
Finance Yes No No Yes
Account admin Yes Yes Yes Yes, manages users

Price lists, credit limits, payment terms and ship-to addresses belong to the company account. Users inherit them. This sounds obvious, but I've seen portals bolted onto retail platforms where every buyer at the same dealer had a separately configured discount, and they drifted apart within months.

Onboarding is also different. IvoryTex buyers apply with business details, a tax ID and expected order volume, and an admin approves them manually before wholesale prices unlock. Until approval, the account sees nothing it can't already see as a guest.

Price resolution: one function, server-side

The rule I enforce on every dealer portal: the browser never decides a price. It receives a resolved price from the server for the authenticated company, and the server recomputes it at submission.

Precedence, from most to least specific:

Priority Source Example Typical owner
1 Contract price for this company and SKU Dealer 1042 pays 410.00 for SKU TX-220 until 31 Dec Sales, per negotiation
2 Quantity break on the company's tier Wholesale tier: 10+ units at 455.00 Pricing team
3 Tier price list Wholesale tier: 480.00 Pricing team
4 Base list price 560.00 Catalog

In PostgreSQL, one query resolves it, so the cart, product page and order validation all use the same logic:

SELECT COALESCE(
  (SELECT cp.price FROM contract_prices cp
    WHERE cp.company_id = :company AND cp.sku_id = :sku
      AND now() BETWEEN cp.valid_from AND cp.valid_to),
  (SELECT tb.price FROM tier_breaks tb
    WHERE tb.tier_id = :tier AND tb.sku_id = :sku AND tb.min_qty <= :qty
    ORDER BY tb.min_qty DESC LIMIT 1),
  (SELECT tp.price FROM tier_prices tp
    WHERE tp.tier_id = :tier AND tp.sku_id = :sku),
  (SELECT s.base_price FROM skus s WHERE s.id = :sku)
) AS unit_price;

For catalog pages with hundreds of SKUs, run the set-based version of the same logic with joins, and index each table on its lookup key (company_id, sku_id and tier_id, sku_id, min_qty). Column order in those indexes matters; I went through why in composite indexes in PostgreSQL and MySQL.

Store the resolved unit price and its source on each order line. When a dealer disputes an invoice three months later, "contract price, agreement #88" ends the conversation in one minute.

MOQ, case packs and order rules

Wholesale catalogs carry rules that retail doesn't:

  • MOQ per SKU (minimum 12 units) and sometimes per order value (minimum 50,000 per order).
  • Case-pack multiples: sold in packs of 6, so 14 is invalid.
  • Tier-specific thresholds: approved wholesale buyers may get different minimums from smaller retail accounts, as on IvoryTex.

Validate these at three points: the quantity input (instant feedback), the cart (clear message before checkout), and the server on submission (the only check that counts).

Neat stacks of folded colorful fabric on a wooden table, representing a wholesale catalog ordered in bulk
AI-generated image.

The credit limit check

Most dealers buy on terms, Net 30 or Net 60. The question at checkout is not "does the card work" but "would this order push the account past its limit?"

Credit exposure is more than unpaid invoices:

exposure = open AR (invoiced, unpaid)
         + orders submitted but not yet invoiced
         + this order
available = credit_limit - exposure

Teams that only count invoiced AR miss the second line, and a dealer can place five large orders in a week before the first invoice posts. The check needs a lock so two simultaneous orders from the same company can't both pass:

DB::transaction(function () use ($order) {
    $account = CreditAccount::where('company_id', $order->company_id)
        ->lockForUpdate()->firstOrFail();

    $exposure = $account->open_ar + $account->uninvoiced_orders + $order->total;

    if ($account->on_hold || $exposure > $account->credit_limit) {
        $order->update(['status' => 'credit_review']);
        event(new OrderNeedsCreditReview($order));
        return;
    }

    $account->increment('uninvoiced_orders', $order->total);
    $order->update(['status' => 'submitted']);
});

Two design choices matter here. Orders over the limit go to credit_review instead of being rejected, because finance often approves them after a payment. And open_ar is synced from the ERP or accounting system, which remains the source of truth for invoices. Treat that sync like a ledger, with idempotent updates and an audit trail, the same discipline as marketplace payment ledgers.

Bulk ordering that dealers actually use

If ordering in the portal is slower than emailing a spreadsheet to their sales rep, dealers will keep emailing. Three entry modes cover most of it:

  1. Quick order grid: type or paste SKU and quantity pairs, with live price and stock per line.
  2. CSV upload: validate every row, show errors inline (unknown SKU, below MOQ, not a case multiple), and let the dealer fix and resubmit without starting over.
  3. Reorder: copy any past order into the cart, re-resolving today's prices, not the old ones. IvoryTex supports one-click reorder from order history because wholesale buying is repetitive.

Build, platform B2B features, or custom?

Option Strengths Limits to check Fits when
B2B features of a hosted commerce platform Fast start, hosted checkout, apps Plan tier needed for B2B features, depth of credit and approval logic, ERP sync options Standard tiers, card or simple terms
Off-the-shelf B2B portal software Built-in quoting and accounts Per-seat or per-order fees, customization limits Mid-size catalog, common workflows
Custom portal on your stack Exact pricing precedence, credit rules, ERP fit You own hosting, security and maintenance Complex contracts, credit terms, unusual rules

Check current plans and pricing directly with vendors; they change often. The decision usually comes down to how unusual your pricing and credit rules are. The 3-year cost framework for custom vs off-the-shelf software is how I run that comparison with clients.

Failure modes I design against

  • Prices computed in the frontend and trusted at submission.
  • Credit checks without locking, letting parallel orders overshoot the limit.
  • Stale AR data from a sync that silently stopped. Alert when the last successful sync is older than a few hours.
  • Shared logins at a dealer, which destroy the audit trail. Make adding users self-service for the account admin.

Launch checklist

  1. Model companies, users, roles and ship-to addresses.
  2. Implement one server-side price resolution function and store the price source on each line.
  3. Enforce MOQ and case packs on input, cart and server.
  4. Compute exposure including uninvoiced orders, with a row lock.
  5. Route over-limit orders to credit review, not rejection.
  6. Ship quick order, CSV upload and reorder.
  7. Sync invoices and AR from the ERP with monitoring.
  8. Give dealers self-service invoices, statements and order status.

Frequently asked questions

Should dealers see stock levels?

Usually yes, as bands (in stock, low, backorder) rather than exact counts, unless your agreements say otherwise.

Where should credit limits live, the portal or the ERP?

The limit and AR usually belong in the ERP or accounting system. The portal keeps a synced copy plus uninvoiced orders, and does the real-time check.

How do I handle sales reps ordering on behalf of dealers?

Let reps act on behalf of a company with their own login, and record both identities on the order.

Can a dealer portal support quotes?

Yes. A quote is a cart with reserved prices and an expiry. Converting it to an order runs the same MOQ and credit checks.

How do I price a new dealer before contract terms exist?

Assign them a tier on approval. Contract prices can be added later without changing the resolution logic.

What should the first version include?

Accounts and roles, server-side pricing, MOQ rules, the credit check, quick order and reorder, and invoice download. Approval chains and CSV upload can follow.

Follow & Connect

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.