Tom Sailors
· Refreshed
Brief · Anonymized case study

Wholesale Portal & Contract Pricing

Build a B2B wholesale system where each buyer company is tagged with a tier and product-specific pricing stored as metafields. Route wholesale customers to a private portal that displays contract prices via a discount or cart function, then swap the standard checkout for a PO submission form. Route submitted orders through an approval workflow where operations staff review, adjust, and convert draft orders to confirmed sales.

A mid-market wholesale-focused merchant needed to offer multiple tiers of buyers their own contract prices without exposing those discounts on the public catalog. The merchant also required a private portal where wholesale customers could browse inventory at negotiated rates, submit purchase orders, and have those orders routed through an internal approval queue before converting to fulfillable transactions.
Four pieces
B2B / Wholesale

Company Tier & Pricing Setup

Create wholesale companies in Shopify, assign them to tiers (tier 1, tier 2, etc.), and attach contract-specific prices per product without changing the public catalog.

Admin dashboard + metafields
B2B / Wholesale

Contract Price Override

Store per-company product pricing on each company as metafields, so one buyer sees $50 while another sees $45 for the same variant.

Metafields + Shopify Functions
extensions/discount-or-cart-transform/src/run.graphql function
# Function input query — Cart Transform or Discount Function
query Input {
  cart {
    lines {
      id
      quantity
      merchandise {
        __typename
        ... on ProductVariant {
          id
          sku
          product {
            id
            handle
          }
        }
      }
      cost {
        amountPerQuantity { amount }
        totalAmount { amount }
      }
    }
    buyerIdentity {
      customer {
        id
        metafield(namespace: "wholesale", key: "company_id") { value }
      }
    }
  }
}

# In your function logic:
# 1. Read cart.buyerIdentity.customer.metafield("wholesale", "company_id")
# 2. Fetch that company's contract prices from Shopify Admin
# 3. For each line, if a contract price exists, apply it via Cart Transform or Discount
Requires Shopify Plus for Cart Transform Function; Discount Function available on all plans.
B2B / Wholesale

Wholesale Buyer Portal

Private storefront where only authenticated wholesale customers land, browse products at contract prices, and submit orders as POs instead of completing checkout.

Custom Shopify app (Remix) + theme gating
theme/snippets/wholesale-gate.liquid liquid
{% comment %}
Place this at the top of your product/collection/cart pages.
It redirects non-wholesale customers to public storefront.
{% endcomment %}

{% if customer and customer.metafields.wholesale.company_id %}
  {% comment %} Wholesale customer — show contract price {% endcomment %}
  {% assign contract_price = customer.metafields.wholesale.product_price %}
  {% if contract_price %}
    <div style="background: #f0f0f0; padding: 1rem; margin-bottom: 1rem;">
      <strong>Contract Price:</strong> {{ contract_price | money }}
    </div>
  {% endif %}
  
  {% comment %} Show PO submission form button instead of "Add to Cart" {% endcomment %}
  <form method="post" action="/apps/wholesale-portal/submit-po">
    <input type="hidden" name="product_id" value="{{ product.id }}" />
    <input type="hidden" name="variant_id" value="{{ variant.id }}" />
    <label for="qty">Quantity:</label>
    <input type="number" id="qty" name="quantity" value="1" min="1" />
    <button type="submit">Add to Purchase Order</button>
  </form>

{% else %}
  {% comment %} Not a wholesale customer — redirect {% endcomment %}
  <script>
    window.location.href = "{{ shop.secure_url }}";
  </script>
{% endif %}
Customize the redirect URL and styling to match your brand.
Operations

Draft Order Approval Queue

Every PO submission creates a draft order tagged for review; operations staff approves, adjusts line items or pricing, then converts it to a real order. Buyer sees status updates in the portal.

Shopify Flow + admin dashboard
Shopify Flow editor: When → Then flow
Trigger: When a draft order is created

Condition: draft order has tag "wholesale_pending_approval"

Actions:
  1. Send Slack/email notification to #wholesale-team or approval-email@company.com
     Message: "New PO from [company name]: [order total]. Review in admin."
  2. Tag draft order with "wholesale_pending_approval"
  3. (Optional) Create a task or alert in your ticketing system

— 

Second Flow (on approval):

Trigger: When a draft order is completed

Condition: draft order tag contains "wholesale_approved"

Actions:
  1. Send email to customer with order confirmation and estimated ship date
  2. Tag the resulting order with "wholesale_order"
  3. (Optional) Log to a webhook to sync fulfillment to your warehouse system
Draft order creation can be done via the portal app or Admin API; Flow listens for the tag change.

Got a similar problem?

Sketch your build in 30 seconds — voice, type, or attach a screenshot.

Sketch the build →