by InlinexDev

Building a B2B Wholesale Ordering Portal from Scratch

How we built a custom B2B wholesale portal for an inline skate manufacturer with tiered pricing, bulk orders, and dealer management.

B2BwholesaleNode.jse-commercePostgreSQL

Why Not Just Use Shopify Wholesale?

Shopify offers B2B features through Shopify Plus, but at $2,000+/month, it's out of reach for many manufacturers. FlyingEagle, an inline skate manufacturer, needed a wholesale portal for their dealer network — but the budget didn't justify an enterprise Shopify plan.

We built a custom B2B portal that handles everything their dealers need, at a fraction of the cost.

Core Requirements

The portal needed to support:

  • Dealer authentication — only approved dealers can access wholesale pricing
  • Tiered pricing — different price levels based on dealer volume
  • Bulk ordering — CSV upload and quick-order forms
  • Order management — status tracking and order history
  • Product catalog — browsable with filters and search

Technical Stack

  • Backend: Node.js with Express
  • Database: PostgreSQL
  • Frontend: Vanilla JavaScript (no framework)
  • Hosting: Railway

Why Vanilla JS?

For a B2B portal where the user base is small (dozens of dealers, not thousands of consumers), React or Vue would add unnecessary complexity. Vanilla JS with modern DOM APIs keeps the bundle tiny and the maintenance burden low.

// Simple component pattern without a framework
function renderProductCard(product) {
  const card = document.createElement('div');
  card.className = 'product-card';
  card.innerHTML = `
    <img src="${product.image}" alt="${product.name}" loading="lazy">
    <h3>${product.name}</h3>
    <p class="sku">${product.sku}</p>
    <p class="price">$${product.wholesalePrice.toFixed(2)}</p>
    <div class="quantity-controls">
      <button onclick="adjustQty('${product.sku}', -1)">-</button>
      <input type="number" id="qty-${product.sku}" value="0" min="0">
      <button onclick="adjustQty('${product.sku}', 1)">+</button>
    </div>
  `;
  return card;
}

Tiered Pricing System

Dealers are categorized into tiers based on their annual order volume. Each tier unlocks better pricing:

CREATE TABLE price_tiers (
  id SERIAL PRIMARY KEY,
  name VARCHAR(50) NOT NULL,
  min_annual_volume INTEGER NOT NULL,
  discount_percentage DECIMAL(5,2) NOT NULL
);

INSERT INTO price_tiers (name, min_annual_volume, discount_percentage) VALUES
  ('Bronze', 0, 30.00),
  ('Silver', 5000, 35.00),
  ('Gold', 15000, 40.00),
  ('Platinum', 50000, 45.00);

Pricing is calculated server-side to prevent manipulation:

function calculateDealerPrice(retailPrice, dealer) {
  const tier = dealer.priceTier;
  const discount = tier.discountPercentage / 100;
  return Math.round(retailPrice * (1 - discount) * 100) / 100;
}

Bulk Order via CSV Upload

Dealers frequently reorder the same SKUs. Rather than clicking through a catalog, they can upload a CSV:

sku,quantity
FE-HAWK-42,10
FE-HAWK-44,8
FE-WHEEL-80,50
FE-BEARING-608,100

The backend validates each SKU, checks stock availability, and returns a price summary before the dealer confirms:

app.post('/api/orders/bulk-upload', upload.single('csv'), async (req, res) => {
  const rows = parseCSV(req.file.buffer.toString());
  const validated = [];
  const errors = [];

  for (const row of rows) {
    const product = await Product.findBySku(row.sku);
    if (!product) {
      errors.push({ sku: row.sku, error: 'SKU not found' });
      continue;
    }
    if (product.stock < row.quantity) {
      errors.push({ sku: row.sku, error: `Only ${product.stock} in stock` });
      continue;
    }
    validated.push({ product, quantity: parseInt(row.quantity) });
  }

  res.json({ validated, errors });
});

Authentication & Security

Since this is a closed portal, security is straightforward:

  • JWT-based auth with httpOnly cookies
  • Role-based access — admin vs. dealer
  • Rate limiting on login endpoints
  • Dealer approval workflow — new registrations require admin approval

Key Learnings

  1. B2B UX is different from B2C — dealers want efficiency, not aesthetics. Quick-order forms and CSV uploads matter more than product photography.
  2. Pricing must be server-side — never send discount percentages to the client.
  3. Order minimums prevent headaches — enforce minimum order quantities per SKU and minimum order values.
  4. Email notifications are critical — dealers expect order confirmations, shipping updates, and payment receipts.

Results

The portal reduced order processing time from days (email back-and-forth) to minutes, while giving dealers 24/7 access to the catalog and their order history. Total hosting cost on Railway: under $20/month.

Related Project

Flying Eagle B2B Wholesale Portal

A B2B wholesale ordering portal for Flying Eagle inline skates, enabling distributors to browse catalogs, manage inventory, and place bulk orders.