Cartnorm B2B lets one WooCommerce shop sell to consumers and to businesses. Consumers keep seeing your normal prices. Business customers in a group see their own prices, net or gross, can be held to minimum order values and pack sizes, can request quotes, and can apply for a wholesale account themselves. EU businesses from other member states with a valid VAT ID buy without VAT, with the right legal note on the order.
For each group, the price of a product is found in this order:
A group price is never higher than the current normal or sale price.
Add quantity prices per product and group, for example from 10 pieces €7, from 50 pieces €6. They are applied in the cart and shown as a small table on the product page.
Works with simple and variable products, in the shop, on the product page, in the mini cart, in the classic cart and checkout and in the cart and checkout blocks. Your stored product prices are never changed.
/wc/store/v1/products, including the price filter range) and from the product’s structured data (JSON-LD), so they cannot be read from the page source. Customers who cannot see prices cannot buy, also not through the Store API.[cartnorm_b2b_quote]) where customers change quantities and send their details: name, company, email, country, VAT ID (prefilled) and a message. Spam protection with a honeypot field and a rate limit.[cartnorm_b2b_registration]) with company name, contact name, email, password (or a “set your password” email link), country, VAT ID, phone and message, plus a privacy consent checkbox.Showing only net prices is allowed only if consumers are effectively excluded, for example because every account is checked (VAT ID, company details) before it is approved. A checkbox “I am a business customer” is not enough. The settings screen explains this.
A consumer must never see a cached page with net prices that was made for a wholesale customer. Pages for logged-in B2B customers (and every page in “B2B only” mode) are sent with Cache-Control: private, no-store and DONOTCACHEPAGE. Group members get a signed cookie (cartnorm_b2b_group) so caches that cache logged-in users can keep the groups apart; prices are never taken from that cookie.
Built-in support for WP Rocket, LiteSpeed Cache, W3 Total Cache, WP Super Cache and FlyingPress. The settings show which cache plugin was found and what Cartnorm B2B did, and a self-test loads your shop like an anonymous visitor and checks that no B2B prices leak.
When you create or edit an order in WooCommerce › Orders for a customer in a group, added items get the customer’s group price (with quantity prices). “Recalculate” re-prices lines that still have the normal price, for example when you added the items before choosing the customer. Prices you changed by hand are kept.
Groups set to “excluding VAT” see shop and cart prices without VAT and with a clear “excl. VAT” suffix. Only the display changes; VAT is calculated as usual. Retail always uses your store settings.
The wording is stored on the order in the language of the order and shown on the thank-you page, in My Account, in the order emails and in the admin order screen. Both phrases are built in for all 24 official EU languages (for example “Steuerfreie innergemeinschaftliche Lieferung (Art. 138 MwStSystRL)” and “Steuerschuldnerschaft des Leistungsempfängers (Reverse Charge, Art. 196 MwStSystRL)” in German, “Autoliquidation” in French, “Odwrotne obciążenie” in Polish), so they do not depend on installed translations. The language comes from WPML or Polylang, or from the language of the shop at checkout. The phrases and the language can be changed with filters.
Translations come from translate.wordpress.org as language packs. So that shops do not have to wait for a language pack, the texts your customers see (price suffix, VAT ID field and its messages, order rule messages, quote and registration pages, customer emails) have built-in wording for German (Germany and Austria, formal “Sie”), Polish, French (France and Belgium), Italian, Spanish and Dutch (Netherlands and Belgium). The built-in wording is used only for strings that no installed language pack translates. The settings screens are in English until a language pack is available.
Every order with a VAT ID stores the VAT ID, its country, the VIES result, the consultation number, the time of the check and the decision. You see it in a box on the order screen, and invoice plugins can read it from the order meta (see “For developers”).
No database queries per cart line, also for the order rules. In our test a cart with 300 lines in a B2B group is calculated in about 40 ms.
Cartnorm B2B applies the general EU rules for business-to-business sales (Articles 138 and 196 of Directive 2006/112/EC). It is not tax advice. Special cases – for example triangular trade, goods that are not dispatched to another member state, special schemes, or national rules – are not covered. Please confirm with your tax adviser how these rules apply to your business.
This plugin connects to VIES (VAT Information Exchange System), a service of the European Commission, to check whether a VAT identification number is valid.
https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-numberNo data is sent anywhere else. The plugin does not contact cartnorm.com.
Order meta (read with $order->get_meta()):
_cartnorm_b2b_vat_id – VAT ID with country prefix, e.g. DE123456789_cartnorm_b2b_vat_country – country of the VAT ID_cartnorm_b2b_vat_destination – country that decided about VAT_cartnorm_b2b_vies_status – valid, invalid, unavailable or format_invalid_cartnorm_b2b_vies_consultation – VIES consultation number_cartnorm_b2b_vies_checked_at – time of the VIES check (UTC, ISO 8601)_cartnorm_b2b_vies_name, _cartnorm_b2b_vies_address – trader data returned by VIES_cartnorm_b2b_vat_treatment – standard, intra_community_supply, reverse_charge, mixed or pending_verification_cartnorm_b2b_vat_reason – cross_border, same_country, outside_eu, country_mismatch, invalid, unavailable, verified_after_order …_cartnorm_b2b_vat_notes – array of legal notes to print on the invoice_cartnorm_b2b_customer_group – customer group at the time of the order_cartnorm_b2b_locale – language of the legal notes_cartnorm_b2b_quote_id – quote the order was created fromFilters (selection): cartnorm_b2b_group_price, cartnorm_b2b_never_above_retail, cartnorm_b2b_tier_quantity, cartnorm_b2b_user_group, cartnorm_b2b_can_assign_group, cartnorm_b2b_vat_decision, cartnorm_b2b_require_matching_country, cartnorm_b2b_is_service, cartnorm_b2b_legal_note_goods, cartnorm_b2b_legal_note_services, cartnorm_b2b_legal_notes, cartnorm_b2b_vies_timeout, cartnorm_b2b_vies_cache_ttl, cartnorm_b2b_keep_data_on_uninstall, cartnorm_b2b_group_order_rules, cartnorm_b2b_product_quantity_rule, cartnorm_b2b_min_order_cart_amount, cartnorm_b2b_cart_rule_messages, cartnorm_b2b_quote_mode, cartnorm_b2b_hide_prices, cartnorm_b2b_add_to_quote_label, cartnorm_b2b_registration_errors, cartnorm_b2b_auto_approve, cartnorm_b2b_rate_limit, cartnorm_b2b_store_locked, cartnorm_b2b_store_notice, cartnorm_b2b_is_catalog_request, cartnorm_b2b_private_page, cartnorm_b2b_cache_plugins, cartnorm_b2b_legal_phrases, cartnorm_b2b_order_language, cartnorm_b2b_convert_price, cartnorm_b2b_show_excl_suffix, cartnorm_b2b_units.
Order item meta _cartnorm_b2b_group_price marks lines that were priced for a group in the order editor.
Actions (selection): cartnorm_b2b_quote_created, cartnorm_b2b_quote_after_answer, cartnorm_b2b_quote_converted, cartnorm_b2b_quote_status_changed, cartnorm_b2b_application_submitted, cartnorm_b2b_application_approved, cartnorm_b2b_application_rejected.
Quote statuses are post statuses of the private post type cartnorm_b2b_quote: cnb2b-new, cnb2b-answered, cnb2b-accepted, cnb2b-expired, cnb2b-converted.
Assign a group from your own code: \Cartnorm\B2B\Groups::assign( $user_id, 'wholesale' );