Documentation · Cartnorm B2B

Store modes and caching

Cartnorm B2B is not on WordPress.org yet. These pages describe version 0.3.0, the version that will be published. Pro features are marked as planned.

Store modes

Choose the mode in WooCommerce › Settings › B2B › Store mode & caching.

ModeWho sees pricesWho can buy
Hybrid (default)Everyone. Consumers and guests see your normal WooCommerce prices; B2B groups see their own prices, net if the group is set to “excluding VAT”.Everyone
B2B onlyOnly approved business customers (customers in a B2B group). Guests and customers without a group see no prices, also not in product blocks, the Store API or the structured data.Only approved business customers

In B2B only mode, other visitors see a notice with links to log in or to apply for a business account. A customer whose application is still under review is told so.

Restrict the catalog (B2B only mode): shows the shop, categories and products only to approved business customers. Other visitors are sent to the login page (My account) or the wholesale registration page, as you choose. Logged-in customers without a group always go to the registration page.

Showing only prices without VAT is allowed only if consumers are effectively excluded from buying, for example because every customer has to register with a VAT ID or company details that you check before you approve the account. A notice or a checkbox such as "I am a business customer" is not enough, and consumers who can still order must see prices including VAT. In Hybrid mode consumers see your normal prices, and only approved B2B groups see net prices. This is not legal advice; please ask your lawyer or tax adviser.

For a B2B-only shop this means: use B2B only mode, restrict the catalog, and use wholesale registration with manual approval, or automatic approval by VIES. Also check your other shop texts (for example consumer withdrawal information) with your lawyer, because a shop that addresses consumers can be treated as a consumer shop.

Deactivating the plugin makes your normal product prices visible to everyone again, because WooCommerce itself does not know the store mode. Do not deactivate Cartnorm B2B on a live B2B-only shop unless the shop is closed.

Caching

Why this matters

Cartnorm B2B can show prices excluding VAT to business customer groups. In Germany the price indication rules (PAngV) require prices shown to consumers to include VAT; other EU countries have similar rules. If a page cache saved a page that was rendered for a wholesale customer and later served that copy to a consumer, the consumer would see net prices. This is a legal risk for the shop, so Cartnorm B2B makes sure such pages are never cached and that caches which do cache logged-in users keep the groups apart.

What Cartnorm B2B does

Pages that are never cached

Store modePages that are never cached
Hybrid (default)Every page for a logged-in customer in a B2B group
B2B onlyEvery page, for everyone (guests, customers without a group and group members)

For these pages Cartnorm B2B

  1. sends Cache-Control: private, no-store, max-age=0 (plus Pragma: no-cache and an Expires date in the past),
  2. defines the DONOTCACHEPAGE constant, which page cache plugins respect,
  3. tells LiteSpeed Cache directly (do_action( 'litespeed_control_set_nocache', … )),
  4. answers Store API product requests (/wc/store/v1/products…, used by product blocks) with the same Cache-Control header,
  5. adds <meta name="cartnorm-b2b-view" content="group"> to the page head, so the self-test can recognise a group page that was cached.

Guests and customers without a group in Hybrid mode see your normal prices (gross, as configured in WooCommerce). Their pages stay cacheable, so your shop stays fast.

Members of a group get a cookie named cartnorm_b2b_group. Its value is an HMAC (SHA-256, 32 hex characters) of the group slug and the group configuration, keyed with the site’s secret AUTH_KEY/AUTH_SALT. So:

  • every member of the same group has the same value (a cache can share one copy per group),
  • the value changes when you change the group settings (old cached copies are not used any more),
  • it cannot be computed without your secret keys, and it does not contain the group name.

The cookie is never used to decide prices. Prices, net/gross display, order rules and VAT always come from the group stored on the server for the logged-in user. Setting or forging the cookie in the browser does not change any price. The cookie is HttpOnly, SameSite=Lax, Secure on HTTPS, and expires with the browser session. It is set on login, kept in line on every request, and removed on logout or when the customer leaves the group.

Cache plugins

Cartnorm B2B detects the plugins below and registers its integration only when the plugin is active. WooCommerce → Settings → B2B → Store mode & caching shows what was detected and what Cartnorm does.

WP Rocket

  • Cartnorm adds cartnorm_b2b_group to WP Rocket’s dynamic cookies (rocket_cache_dynamic_cookies). If you enabled “Enable caching for logged-in WordPress users”, WP Rocket keeps one cache per cookie value (per group).
  • Cartnorm does not add a mandatory cookie (rocket_cache_mandatory_cookies), because that would stop caching for all guests.
  • WP Rocket writes the cookie list into its config file. Cartnorm regenerates that file (rocket_generate_config_file()) when WP Rocket is detected for the first time, when you save the “Store mode & caching” settings, and on deactivation (to remove the cookie again).
  • Pages for group members carry DONOTCACHEPAGE, which WP Rocket respects (“DONOTCACHEPAGE is defined. Page cannot be cached.”).
  • Recommended: keep “Enable caching for logged-in WordPress users” off (default).

LiteSpeed Cache

  • litespeed_vary_cookies: the cookie is registered for the rewrite rules (.htaccess), so the LiteSpeed server varies by it.
  • litespeed_vary_curr_cookies: the current response varies by the cookie when the visitor has it.
  • litespeed_control_set_nocache: pages for group members (and all pages in “B2B only” mode) are marked not cacheable. LiteSpeed also honours DONOTCACHEPAGE.
  • After installing Cartnorm B2B, LiteSpeed rewrites its rules the next time its settings are saved. Save LiteSpeed Cache → Cache once so the cookie is in .htaccess.
  • Recommended: keep “Cache Logged-in Users” off. If you need it on, the vary cookie keeps groups apart.

W3 Total Cache

  • W3 Total Cache does not store pages when DONOTCACHEPAGE is defined (“DONOTCACHEPAGE defined”).
  • Recommended: keep Performance → Page Cache → “Don’t cache pages for logged in users” on (default).
  • If you use “Disk: Enhanced”, W3TC serves cached files from .htaccess rules for visitors without the WordPress login cookie. Group members are always logged in, so they never get a guest copy, and their own pages are never stored.

WP Super Cache

  • Cartnorm adds cartnorm_b2b_group to WP Super Cache’s cookie list (do_action( 'wpsc_add_cookie', 'cartnorm_b2b_group' )), so the cookie becomes part of the cache key. It is removed again on deactivation (wpsc_delete_cookie).
  • WP Super Cache honours DONOTCACHEPAGE.
  • Recommended: keep “Disable caching for visitors who have a cookie set in their browser” or “Disable caching for logged in visitors” on.

FlyingPress

  • Cartnorm returns false from the flying_press_is_cacheable filter for group pages and defines DONOTCACHEPAGE.
  • Not verified: FlyingPress is commercial and its source was not available for testing. Please run the self-test after installing.

Other caches (hosting, Varnish, nginx fastcgi_cache, Cloudflare)

Server and CDN caches outside WordPress cannot be configured by a plugin. They usually respect Cache-Control: private, no-store, but some are set up to ignore it.

  • Bypass the cache when the request has a cookie wordpress_logged_in_*, cartnorm_b2b_group, woocommerce_items_in_cart or wp_woocommerce_session_*.
  • Cloudflare APO / “Cache Everything” rules: add a bypass on the cookies above. APO bypasses wordpress_logged_in_* by default.
  • B2B only mode: all pages are sent with no-store. If your host caches HTML regardless, ask your host to exclude the shop, or the whole site.

Example for nginx fastcgi_cache:

if ($http_cookie ~* "wordpress_logged_in_|cartnorm_b2b_group|woocommerce_items_in_cart|wp_woocommerce_session_") {
    set $skip_cache 1;
}

Example for Varnish (VCL, vcl_recv):

if (req.http.Cookie ~ "wordpress_logged_in_|cartnorm_b2b_group") {
    return (pass);
}

Self-test

WooCommerce → Settings → B2B → Store mode & caching → Run self-test loads your shop page like an anonymous visitor (no cookies) from your server and checks:

  • that the page is not a page rendered for a B2B group (marker cartnorm-b2b-view),
  • that it contains no B2B net price markup (the “excl. VAT” suffix of groups or a quantity price table),
  • in “B2B only” mode: that no prices are shown and that the response has a no-store header; a redirect to the login page counts as “passed” when the catalog is restricted,
  • which cache headers it saw (for example x-litespeed-cache, cf-cache-status, age).

The test is best effort: it cannot see caches that only apply to visitors from outside (for example a CDN in front of your server), and it only checks one page. To test fully: log in as a wholesale customer in one browser, open some product pages, then open the same pages in a private window without logging in and check the prices.

Developer reference

HookTypeUse
cartnorm_b2b_private_pagefilterWhether the current page must not be cached (bool).
cartnorm_b2b_cache_pluginsfilterDetected cache plugins (id => [ name, detected ]).
cartnorm_b2b_store_lockedfilterWhether the current visitor is kept out of a “B2B only” shop.

Cookie name: Cartnorm\B2B\Cache::COOKIE (cartnorm_b2b_group). Value for a group: Cartnorm\B2B\Cache::cookie_value( $slug ).

What was tested

  • Headers, DONOTCACHEPAGE, the cookie (value, update, removal, forging) and the Store API header: automated tests.
  • WP Rocket, LiteSpeed Cache and WP Super Cache: the filters and actions were checked against the plugins’ source code (GitHub) and tested with stubs. Not tested with the real plugins running.
  • W3 Total Cache: DONOTCACHEPAGE handling checked in its source code.
  • FlyingPress: not verified (no source available).

Something unclear or missing? Write to [email protected].