WooCommerce

Building a B2B Wholesale Store on WooCommerce

Role-based pricing, minimum order quantities, GST-compliant quotes, credit terms and a catalogue that hides prices until login.

Four product cards in a grid above a shopping cart outline
In this article

A wholesale buyer does not shop like a retail customer. They know what they want, they reorder the same forty lines every month, they expect their own price, and they would rather pay in thirty days than today. A standard WooCommerce store fights every one of those habits: one price for everyone, a quantity box that starts at one, and a checkout that wants a card.

That does not mean you need a separate B2B platform. WooCommerce sits on WordPress's user system, and that is the foundation trade selling needs, because every B2B rule depends on who the buyer is. It is also where WooCommerce quietly beats hosted platforms: you own the data model, so the rules can match your trade instead of the other way round. This guide covers the pieces in the order I build them for Indian distributors and manufacturers, including the two most write-ups skip, GST and selling on credit.

Modelling customer groups on top of WordPress roles

Start with the data model, because every later rule depends on it. Each rule asks 'what kind of buyer is this?', and WordPress already has an answer built in: roles. A wholesale role, a distributor role and perhaps a key-account role, each created with add_role() and given the same capabilities as WooCommerce's customer role, give you groups that every plugin and every line of custom code can read.

Roles answer 'what kind of buyer', not 'which buyer'. Per-customer details, such as a negotiated discount, a credit limit, the GSTIN or an assigned sales rep, belong in user meta on the individual account. Keep the two layers separate: group rules on the role, exceptions on the user, and a written order of precedence for when both apply.

Decide how people get into a group, too. Most B2B stores do not let anyone register as a wholesaler. A trade application form creates an ordinary customer account with a pending flag; someone checks the GSTIN and business details, then changes the role. Until that happens, the account sees retail prices and terms.

Price tiers: per role, per quantity, per customer

Wholesale pricing usually comes in three layers. A role price is the trade price list: everyone in the wholesale group pays ₹420 for a product that retails at ₹600. A quantity tier rewards volume: ₹400 each for 10 or more, ₹380 for 50 or more. A customer price is the negotiated exception for the one account that buys a truckload a quarter.

Store role and tier prices as product meta, on each variation rather than only the parent, because trade prices nearly always differ by size or pack. Then apply them in two places. The woocommerce_product_get_price filter and its variation equivalents change what the shop and product pages show the logged-in buyer. Quantity tiers depend on the cart, so they belong in woocommerce_before_calculate_totals, which sets each line's price from its quantity.

Variable products have a trap that catches nearly everyone. WooCommerce caches each variable product's price range, and unless the cache key includes the buyer's role, the first group to load the page decides what everyone else sees. The woocommerce_get_variation_prices_hash filter is where you add the role. A retail customer seeing the trade price range is the kind of bug a competitor finds first.

Full-page caching needs the same care. Prices that depend on who is logged in must never come from a page cache. Most caching plugins already bypass the cache for logged-in users; check that yours does before launch, and check any CDN in front of the site as well.

Gating the catalogue without wrecking SEO

Most wholesalers want prices hidden from the public, either because trade prices are confidential or because retail customers should not see them. There are two ways to do it, and they have very different effects on search.

A login wall, where every shop and product URL sends visitors to a login page, hides the whole catalogue. Google does not log in, so it sees the same login page at every URL. If organic search is how new trade buyers find you, a login wall throws that away.

The better pattern is a public catalogue with private prices. Product pages stay indexable, with full descriptions, specifications, images and pack sizes. For guests, only the price and the add-to-cart button are replaced, with a prompt to log in or apply for a trade account. The woocommerce_get_price_html filter swaps the price, and returning false from woocommerce_is_purchasable stops guests adding anything to the cart by any route.

One detail is easy to miss. WooCommerce prints Product structured data with the price in it, whatever the page shows. If you hide prices from guests, also filter woocommerce_structured_data_product to remove the offer, or the price you hid is sitting in the page source, and structured data that contradicts the visible page is against Google's guidelines. The cost is that a page without an offer is not eligible for price-rich results. For a wholesaler, that is usually the right trade.

A catalogue that is genuinely confidential, such as custom parts made for named clients, is the exception. Put it entirely behind login, mark it noindex, and accept that it will never rank.

Minimum and step quantities that survive the cart page

Trade orders come with quantity rules: a minimum of 12, sold in cartons of 6, or a minimum order of ₹10,000. Each rule should apply only to the group it belongs to. Setting the quantity box is the easy half. The woocommerce_quantity_input_args filter sets min_value, max_value and step on the product page, and for variable products the same values go through woocommerce_available_variation.

The hard half is that these are only attributes on an HTML input. A buyer can type a different number on the cart page, an old link can add a single unit, and the cart and checkout blocks talk to WooCommerce through the Store API, which ignores the classic input entirely. So the rule has to be enforced on the server, in more than one place:

  • At add to cart, in woocommerce_add_to_cart_validation, with a message that states the rule rather than just refusing.
  • On every cart and checkout load, in woocommerce_check_cart_items, which catches quantities edited on the cart page.
  • In the Store API's own quantity filters if the store uses the cart and checkout blocks, so the block quantity selector also steps in cartons.
  • For a minimum order value, in the same cart check, comparing the subtotal before GST against the group's threshold.

Quotes, purchase orders and payment on credit terms

Larger B2B orders rarely go straight through checkout. The buyer wants a quote, approves it internally, raises a purchase order and pays on the agreed terms, often 30 or 45 days from the invoice. WooCommerce can carry all of that without a separate quoting system.

For quotes, the most reliable flow uses WooCommerce's own orders. The buyer submits their cart as a quote request, and a custom order status such as 'Quote requested' keeps it out of the fulfilment queue. Someone adjusts prices or freight in the order editor, sets the order to pending payment and sends it with the 'send order details to customer' action, which includes a link to the payment page WooCommerce creates for every pending order. Accepting the quote means paying it, or choosing purchase order.

Purchase orders and credit terms are best built as a payment gateway. A small custom gateway, offered only to approved trade accounts through the woocommerce_available_payment_gateways filter, captures the buyer's PO number at checkout, lets the order go through without payment and records a due date from the account's terms. The order then moves into whatever status your warehouse ships from, while payment is tracked separately.

A credit limit is what stops terms becoming bad debt. Store a limit on each account, total its unpaid credit orders at checkout, and stop offering the gateway when the new order would go past it. Action Scheduler, which ships with WooCommerce, can send reminders as due dates approach and flag overdue accounts for a phone call.

In India most trade payments still arrive by NEFT or RTGS against the invoice, so reconciliation matters more than a card gateway. Print your bank details on every credit invoice, ask buyers to use the invoice number as the payment reference, and mark orders paid when the money lands. If you are registered as a micro or small enterprise, the MSMED Act caps agreed payment terms at 45 days, and your buyers have tax reasons to respect that. Check with your accountant how to show your registration on the invoice.

GST, HSN codes and the invoice a buyer's accountant will accept

For a retail customer an invoice is a receipt. For a business buyer it is how they claim input tax credit, so it is the document their accountant actually reads. Sequential numbering and the CGST, SGST and IGST split by place of supply work as they do in any Indian store.

Price display is the first B2B decision. Trade buyers think in prices before GST, retail customers in prices including it. WooCommerce's display setting is global, but filtering the woocommerce_tax_display_shop and woocommerce_tax_display_cart options per role lets each group see the convention it expects, while the tax charged on the order stays the same.

Beyond that, a B2B invoice needs a few things a retail store can skip. These are the rules at the time of writing; thresholds change, and your accountant is the authority on what applies to you.

  • The buyer's GSTIN, captured when the trade account is approved rather than typed at checkout, validated for format, and printed on every invoice. Its first two digits are the state code, a useful cross-check against the billing address.
  • The buyer's legal name as registered for GST, which is not always the trading name they entered on the account form.
  • HSN codes on every line. Businesses with aggregate turnover up to ₹5 crore need at least four digits on B2B invoices; larger businesses need six.
  • E-invoicing, once aggregate turnover is over ₹5 crore. B2B invoices must be registered on the government's Invoice Registration Portal and carry its IRN and QR code, which takes an integration, not a PDF template.
  • An e-way bill for consignments of goods generally above ₹50,000, generated before the goods move.
  • An export of B2B invoices with GSTINs for your GST return. The buyer's credit depends on your filing: an invoice missing from your GSTR-1 is missing from their GSTR-2B, and you will hear about it.

What to buy off the shelf and what to build

Most of this exists as plugins, and for a new trade channel buying is usually right. All-in-one B2B plugins, of which B2BKing and Wholesale Suite are the established names, cover roles, price tiers, hidden prices, quantity rules and trade registration in one place. WooCommerce's own Min/Max Quantities extension handles quantity rules alone, if that is all you need.

Build where the rules are your business's own: credit limits tied to your accounting system, quotes that follow your approval chain, prices that come from an ERP rather than the product screen, and the GST pieces, such as e-invoicing and GSTR-1 exports, that generic plugins skip or approximate. These are also the parts most likely to change, so they belong in a small plugin of your own rather than in functions.php.

Whichever way you go, test as a buyer, not as an admin. An administrator is not in any trade group, so testing while logged in as one only tests the retail store. Create one test account per group and walk the whole path in each: browse, add to cart, edit quantities, request a quote, check out on credit and open the invoice. Most B2B bugs live in the gap between what the admin saw and what the buyer did.

Frequently asked questions

Can WooCommerce handle B2B wholesale pricing?

Yes. Put trade buyers in their own WordPress role, store role prices and quantity tiers on each product or variation, and apply them with WooCommerce's price filters for display and in the cart calculation for volume tiers. B2B plugins package the same approach; custom code makes sense when prices come from an ERP or per-customer agreements.

How do I hide prices from visitors who are not logged in?

Filter the price HTML to show a log-in prompt instead of the price, and make products non-purchasable for guests so nothing can be added to the cart. Keep product pages public and indexable, and remove the offer from WooCommerce's Product structured data as well, or the hidden price stays in the page source.

Can I set minimum order quantities per customer group?

Yes. Set the minimum and step on the quantity input for the buyer's role, then enforce the same rule on the server at add to cart and again on the cart and checkout, because the input alone is easy to bypass. Stores using the cart and checkout blocks need the rule applied through the Store API too.

How do credit terms and purchase orders work in WooCommerce?

Through a payment gateway offered only to approved trade accounts. It records the buyer's PO number, lets the order through without payment and stores a due date from the account's terms. A credit limit per account, checked at checkout against unpaid orders, keeps terms from turning into bad debt.

Do B2B invoices in India need the buyer's GSTIN?

Yes, if the buyer is GST-registered and wants to claim input tax credit. Capture the GSTIN when the trade account is approved, validate its format, print it with the buyer's registered legal name on every invoice, and report the invoice against that GSTIN in your GST return.

Topics

  • WooCommerce B2B wholesale
  • WooCommerce wholesale pricing
  • WooCommerce role based pricing
  • B2B ecommerce India WooCommerce
Share