Running retail and wholesale from one Shopify store is normal and sensible — but it opens two ways for money to leak. First, a wholesale price shows to a retail shopper (usually a caching or customer-state problem: the wrong price gets rendered or served to the wrong visitor). Second, a wholesale discount gets bypassed at checkout, so a trade buyer is charged retail, or a wholesale code escapes into public hands. Both come from the same root cause: pricing that’s displayed in the theme instead of gated and enforced server-side. The fix is access control plus checkout enforcement — show retail by default, reveal wholesale only to authenticated eligible buyers, and validate the tier where it can’t be gamed.
The two ways pricing leaks
There are really only two leak types, and it’s worth naming them precisely because they have different causes and different fixes.
Leak 1 — wholesale price shown to retail. A visitor who should see the public price sees the trade price instead. Your margins get quoted to the wrong audience, and once your wholesale pricing is public it’s hard to put back in the box.
Leak 2 — wholesale discount bypassed at checkout. The opposite direction: the trade buyer (or a leaked code) gets a wholesale price they display, but the store either charges them the wrong amount or lets a public shopper claim it. Here you undercharge — the discount fires when it shouldn’t, or the “wholesale price” the buyer saw isn’t what checkout actually validates.
One overcharges the wrong people and burns trust; the other undercharges and burns margin. A store with a sloppy dual setup often has both at once.
Why it matters
- Lost margin. Every retail order that slips through at a wholesale price, or every public shopper who claims a trade discount, is straight off the bottom line.
- Unfair pricing and channel conflict. If retail shoppers discover your wholesale prices, your trade partners are undercut and your full-price customers feel cheated. That’s a trust problem, not just a pricing one.
- Support and reconciliation cost. Leaks surface as “why was I charged this?” tickets and manual refunds — the expensive, slow kind of cleanup.
What actually causes the leaks
Almost every dual-pricing leak traces to one of four causes. Understanding Shopify’s caching and customer-state model is what separates a setup that holds from one that leaks.
Caching serving the wrong price. Shopify (and any CDN or app cache in front of it) can cache a rendered page. If a product page rendered with wholesale pricing for a logged-in trade buyer gets cached and then served to an anonymous retail visitor, the wrong price leaks. Full-page caching and customer-specific content are fundamentally at odds — pricing that varies per customer must not sit in a shared, cacheable response.
Theme rendering before customer state is known. Liquid renders on the server with the customer object available, but a lot of “wholesale pricing” is bolted on with JavaScript that runs after the page loads and then checks who the customer is. For a moment the page shows one price, then flips — and if the check is client-side, anyone can read or skip it. Pricing decided in the browser is a display trick, not a gate.
Storefront-only discounts. A discount that’s applied only in the theme or cart (painting a lower price onto the page) but not enforced as a real, validated discount will be ignored at checkout — or, if it’s a shareable code, used by people it wasn’t meant for. The storefront and the checkout are different systems; a price that lives only in the storefront doesn’t survive.
Weak access control. If the “wholesale” collection, price, or catalog isn’t gated behind an approved, authenticated account — only hidden with CSS, an unlisted URL, or a client-side tag check — it isn’t actually protected. Anyone with the link, or anyone who opens dev tools, can reach it.
Leak → cause → fix
| Leak | Root cause | Fix |
|---|---|---|
| Wholesale price shown to retail | Page cached with wholesale pricing, then served to anonymous visitors | Never cache customer-specific pricing in a shared response; keep per-customer pricing out of cacheable full-page HTML |
| Wholesale price flashes then changes | Theme/JS renders pricing before customer state is resolved | Resolve customer state server-side (Liquid customer / B2B context) before rendering price; default to retail |
| Retail shopper reaches trade prices | Access control done with CSS, unlisted URLs, or client-side tag checks | Gate at the data layer — approved, logged-in accounts only; B2B catalog or server-side eligibility |
| Trade buyer charged retail | ”Wholesale price” only painted in the storefront | Enforce the tier at checkout with a price list or Shopify Function |
| Wholesale code used by the public | Shareable discount code, not identity-bound | Bind pricing to the authenticated customer/company, not a code anyone can paste |
How to prevent each leak
Gate wholesale pricing behind authenticated eligibility. The rule is simple: a visitor sees wholesale pricing only if they are logged in and approved (a wholesale customer tag, a B2B company membership, or a catalog assignment). Eligibility is checked server-side, not with a CSS class or a JavaScript flag anyone can flip.
Default to retail, reveal wholesale. Design the page so the safe state — retail pricing — is what renders when the store doesn’t yet know, or can’t confirm, who the customer is. Wholesale is the exception you opt into after confirming identity, never the default that has to be hidden.
Be deliberate about caching. Treat customer-specific pricing as uncacheable. Don’t let per-customer prices sit in a response that a CDN or app cache could hand to someone else. If you use an app that injects wholesale prices, confirm how it interacts with caching before you trust it in production.
Enforce the tier at checkout. The price a trade buyer sees must be the price checkout validates. On Shopify Plus, native B2B price lists and catalogs are enforced natively at checkout. On any plan, a discount or cart-transform Shopify Function re-validates the correct price server-side, tied to the authenticated customer — no JavaScript painting, nothing to game. This is the single most important control: it’s what makes the displayed price true.
The customer-state and caching gotcha, plainly
Here’s the trap in one paragraph. Shopify wants pages fast, so responses can be cached. But wholesale pricing is personal — it depends on who’s asking. The instant you cache a personalized response, you risk serving one person’s price to another. And if you dodge caching by computing the price in the browser instead, you’ve moved the pricing decision to a place the customer fully controls. The only stable resolution is: decide pricing on the server, from confirmed customer state, and keep anything personalized out of shared caches — then enforce the same decision again at checkout so the storefront and the charge can never disagree.
Native vs tags vs custom
- Plus B2B (native). Companies, catalogs and price lists, with access control and checkout enforcement built in. The cleanest dual setup if you’re on Plus — retail catalog untouched, wholesale gated to logged-in company buyers. See running B2B without Plus if you’re weighing the upgrade.
- Customer tags + discounts. Tag approved buyers and gate a hidden wholesale experience behind login. Workable for a few accounts, but the burden of doing access control and checkout enforcement correctly falls on you — this is where most DIY leaks happen. Our guide to tiered pricing by customer group covers the tag approach in depth.
- Custom logic / Shopify Functions. When the rules are yours — negotiated per-SKU rates, complex eligibility — a Function enforces the correct wholesale price server-side on any plan. This is what we build in Custom Apps & Shopify Functions when apps can’t express the logic or can’t be trusted at checkout.
Whichever you pick, judge it on two questions: who can see wholesale pricing, and is that price enforced at checkout. If a method can’t answer both cleanly, it will leak.
How to test for leaks
Test both directions, in a real browser, not just admin:
- As an anonymous retail visitor, open your top products in a fresh incognito window. Do you ever see a wholesale price — even for a flash before it changes? Reload a few times to smoke out cache-served wholesale pages.
- Share a wholesale product/collection URL to a logged-out session. Can a non-approved visitor reach trade pricing directly?
- As an approved wholesale buyer, add to cart and go all the way to the checkout summary. Is the wholesale price the price actually charged — or does it revert to retail at checkout?
- Try the discount out of context — if wholesale relies on a code, paste it as a retail shopper. Does it apply when it shouldn’t? If it’s a wholesale discount that isn’t working even for the right buyers, that’s the enforcement layer failing.
- Check caching directly — hit a personalized page repeatedly and after logging out; watch for a stale price that belongs to a different customer state.
Common mistakes
- Hiding wholesale pricing with CSS or an unlisted link. Hidden is not gated — the data is still reachable.
- Deciding the price in JavaScript. Anything computed in the browser can be read and changed in the browser.
- Caching personalized prices. The fastest way to serve the wrong customer the wrong price.
- Displaying a discount you don’t enforce. If checkout doesn’t validate it, the storefront price is fiction.
- Using shareable codes for wholesale. Bind pricing to identity, not to a string anyone can forward.
When to get help
If your store serves both audiences and you can’t confidently answer “who sees wholesale pricing, and is it enforced at checkout?”, it’s worth a proper review before a leak costs you margin or a partner’s trust. The durable fixes — server-side eligibility, cache-safe rendering, and checkout enforcement via B2B price lists or a Shopify Function — are exactly the kind of work covered in our B2B & wholesale solution and built in Custom Apps & Shopify Functions. A quick profit audit can also pinpoint whether you’re leaking, and in which direction, before you commit to a rebuild.
Worried wholesale prices are leaking to retail? Send us your store URL — we’ll test for pricing and cache leaks, check checkout enforcement, and tell you how to lock it down. See Custom Apps & Shopify Functions or get a free profit audit.