{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-developer-portal/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown","redocly_category":"Development","owner":"MB"},"seo":{"title":"Building B2B Storefronts with Norce Commerce","description":"How to build a B2B storefront using Norce Commerce — covering login, company context, company-specific pricing and assortment, and the B2B purchase flow.","keywords":"technical, b2b, company, customer, login, pricing, assortment, basket, storm-api, guide, front-end"},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"building-b2b-storefronts"},"children":["Building B2B Storefronts"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This guide covers the patterns and APIs needed to build a business-to-business (B2B) storefront with Norce Commerce. It picks up where the general ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/developer-portal/app-development/working-with-customers-and-the-login-process"},"children":["customer login guide"]}," leaves off and focuses on the company context that makes B2B different from B2C."]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For basket management once company context is established, see ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/developer-portal/app-development/working-with-b2b-baskets"},"children":["Working with B2B Baskets"]},"."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-b2b-model-in-brief"},"children":["The B2B Model in Brief"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A B2B order in Norce Commerce always involves two entities:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Customer"]}," — the individual placing the order (the contact person)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Company"]}," — the organization buying on behalf of which the order is placed"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A customer can be linked to multiple companies. The active company context determines which prices, price lists, assortment restrictions, and company discounts apply during the session."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A B2C order has only a Customer and no Company. The same person can place both B2C and B2B orders — Norce does not require separate accounts."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"login-and-company-resolution"},"children":["Login and Company Resolution"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-1--authenticate-the-customer"},"children":["Step 1 — Authenticate the customer"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["B2B login uses the same mechanism as B2C. Call ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/api-reference/services/customerservice/openapi/accounts/loginpost"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["LoginPost"]}]}," with the customer's credentials. A full ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Customer"]}," object is returned, including a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Companies"]}," list."]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you use a third-party identity provider, authenticate externally and then look up the customer in Norce by email address or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustomerCode"]}," using ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/api-reference/services/customerservice/openapi/customers/getcustomerbyemail"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GetCustomerByEmail"]}]}," or ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/api-reference/services/customerservice/openapi/customers/getcustomerbycode"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GetCustomerByCode"]}]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-2--resolve-the-active-company"},"children":["Step 2 — Resolve the active company"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After login, check the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Companies"]}," list on the returned ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Customer"]}," object:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["No companies:"]}," The customer is B2C only. Proceed with a standard session."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["One company:"]}," Set that company as the active context automatically."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Multiple companies:"]}," Present a company selector and let the customer choose which company to buy for. Store the selected ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CompanyId"]}," in the session."]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"// Customer object after login — Companies list\n\"Companies\": [\n  {\n    \"Id\": 334815,\n    \"Code\": \"test0043\",\n    \"Name\": \"Acme Corp\",\n    \"PricelistIds\": [4521, 4654]\n  },\n  {\n    \"Id\": 334816,\n    \"Code\": \"test0044\",\n    \"Name\": \"Acme Nordic AB\",\n    \"PricelistIds\": [4521]\n  }\n]\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Store both ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustomerId"]}," and the selected ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CompanyId"]}," in the session — you will need both for all subsequent product, pricing, and basket calls."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-3--fetch-full-company-details-optional"},"children":["Step 3 — Fetch full company details (optional)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For rich B2B UIs (showing company address, flags, available price lists), fetch the full company object:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"GET customer/1.1/GetCompany?companyId=334815\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This returns the complete company including delivery addresses, flags, info types, and price list associations."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"company-specific-pricing"},"children":["Company-Specific Pricing"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"how-pricing-works"},"children":["How pricing works"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CompanyId"]}," is set in the session, Norce Commerce automatically selects the best available price from the price lists connected to that company. This happens both in product listing and in the basket."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pass both ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["customerId"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["companyId"]}," as context parameters when fetching products or price data:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"GET product/1.1/GetProduct?partNo=PRD001&customerId=12345&companyId=334815\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The returned price reflects the company's best available price across all connected price lists."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"company-discounts"},"children":["Company discounts"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Company discounts are category- or flag-based discount rules configured on the company. They are applied on top of the price list price and calculated in real time. They are ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["not stored as explicit prices"]}," and do not appear in standard price exports — the discounted price is only visible when products are fetched in an authenticated company context."]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Note:"]}," Company discounts are applied automatically when ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["companyId"]}," is passed in context. No extra API calls are required."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"fetching-available-price-lists"},"children":["Fetching available price lists"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To let the customer explicitly choose which agreement (price list) to use for their purchase, fetch the company's available price lists:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"GET customer/1.1/ListCompanyPricelists?companyId=334815\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["See ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/developer-portal/app-development/working-with-b2b-baskets#pricing-scenarios"},"children":["Working with B2B Baskets"]}," for how to use these in the basket."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"company-specific-assortment"},"children":["Company-Specific Assortment"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Some products are restricted to specific companies — they are invisible or unbuyable for customers without the right company context. This is configured in Norce via company-specific price lists that control assortment visibility."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When fetching product lists with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["companyId"]}," in context, Norce automatically filters to show only products available to that company. No special handling is required in the frontend beyond passing the correct ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["companyId"]},"."]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Tip:"]}," If a product appears in one company context but not another for the same customer, it is likely an assortment restriction rather than a data issue."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-b2b-purchase-flow"},"children":["The B2B Purchase Flow"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"creating-the-basket"},"children":["Creating the basket"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Always create the basket with both ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CustomerId"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CompanyId"]}," set from the start. This ensures company pricing and business rules apply to every item added:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"CustomerId\": 12345,\n  \"CompanyId\": 334815\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["See ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/developer-portal/app-development/working-with-b2b-baskets"},"children":["Working with B2B Baskets"]}," for full basket management, pricing scenarios, approval workflows, and procurement fields (PO number, cost centre, invoice mark)."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"checkout"},"children":["Checkout"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The checkout object in a B2B session populates Buyer, Payer, and ShipTo from the customer and company data. Common patterns:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Payer"]}," is typically the company (company invoice address)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["ShipTo"]}," can be one of the company's delivery addresses, selected by the customer"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Buyer"]}," is the individual customer (the contact person)"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Populate the checkout with the company's invoice address as the default payer address, and offer the company's delivery addresses as options for ShipTo."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"new-company-registration"},"children":["New Company Registration"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Some B2B storefronts allow companies to self-register. The typical flow:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Collect company details (name, organization number, contact person) from the registration form."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Create the company in Norce using ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/api-reference/services/customerservice/openapi/companies/updatecompany"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["UpdateCompany"]}]}," from the Customer Service API. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["UpdateCompany"]}," creates a new company when no matching company exists, making it the standard call for storefront-driven company creation."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Send the new company to the ERP for approval (credit check, contract setup)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["ERP returns a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CompanyCode"]}," and optionally a verification flag (e.g. \"approved for contract purchasing\")."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Update the company in Norce with the ERP code and linked price lists using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["UpdateCompany"]}," again."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Until the company is approved and has a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CompanyCode"]}," set, the customer can still browse — but restrict B2B checkout to approved companies. Use a company flag (e.g. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["IsApproved"]},") to gate access."]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For the integration side of this flow, see ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/developer-portal/system-integration/customer-and-company-integrations"},"children":["Customer and Company Integrations"]},"."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"common-patterns-and-pitfalls"},"children":["Common Patterns and Pitfalls"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Pattern"},"children":["Pattern"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Notes"},"children":["Notes"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Always pass both ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["customerId"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["companyId"]}]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Both are required for any call where company pricing or assortment should apply — passing only ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["companyId"]}," is not enough. Without ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["customerId"]},", Norce cannot resolve customer-specific entitlements; without ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["companyId"]},", prices and assortment restrictions fall back to B2C rules"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Company selector on login"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["If a customer has multiple companies, present a picker before loading any prices — fetching prices without ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["companyId"]}," and then re-fetching after selection doubles API calls"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Clone the basket on context change"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["When a customer logs in after browsing anonymously, or switches active company mid-session, create a new basket based on the existing one rather than modifying it in place. Norce can carry over items while starting a clean session — this avoids stale anonymous-session data affecting pricing and order history, and preserves the old basket in history"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Checking company approval state"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Use a company flag to prevent unapproved companies from completing checkout"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Company discounts not in list prices"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Don't cache prices fetched without ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["companyId"]}," and apply them in a company session — the discount won't be included"]}]}]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"further-reading"},"children":["Further Reading"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/developer-portal/app-development/working-with-customers-and-the-login-process"},"children":["Customer Accounts and Login"]}," — authentication, account creation, password management"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/developer-portal/app-development/working-with-b2b-baskets"},"children":["Working with B2B Baskets"]}," — basket creation, pricing scenarios, approval workflows, procurement fields"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/developer-portal/system-integration/customer-and-company-model"},"children":["The Customer and Company Model"]}," — entity model, identification keys, import behavior"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/developer-portal/system-integration/customer-and-company-integrations"},"children":["Customer and Company Integrations"]}," — integration patterns for keeping company data in sync"]}]}]},"headings":[{"value":"Building B2B Storefronts","id":"building-b2b-storefronts","depth":1},{"value":"The B2B Model in Brief","id":"the-b2b-model-in-brief","depth":2},{"value":"Login and Company Resolution","id":"login-and-company-resolution","depth":2},{"value":"Step 1 — Authenticate the customer","id":"step-1--authenticate-the-customer","depth":3},{"value":"Step 2 — Resolve the active company","id":"step-2--resolve-the-active-company","depth":3},{"value":"Step 3 — Fetch full company details (optional)","id":"step-3--fetch-full-company-details-optional","depth":3},{"value":"Company-Specific Pricing","id":"company-specific-pricing","depth":2},{"value":"How pricing works","id":"how-pricing-works","depth":3},{"value":"Company discounts","id":"company-discounts","depth":3},{"value":"Fetching available price lists","id":"fetching-available-price-lists","depth":3},{"value":"Company-Specific Assortment","id":"company-specific-assortment","depth":2},{"value":"The B2B Purchase Flow","id":"the-b2b-purchase-flow","depth":2},{"value":"Creating the basket","id":"creating-the-basket","depth":3},{"value":"Checkout","id":"checkout","depth":3},{"value":"New Company Registration","id":"new-company-registration","depth":2},{"value":"Common Patterns and Pitfalls","id":"common-patterns-and-pitfalls","depth":2},{"value":"Further Reading","id":"further-reading","depth":2}],"frontmatter":{"seo":{"title":"Building B2B Storefronts with Norce Commerce","description":"How to build a B2B storefront using Norce Commerce — covering login, company context, company-specific pricing and assortment, and the B2B purchase flow.","keywords":"technical, b2b, company, customer, login, pricing, assortment, basket, storm-api, guide, front-end"},"metadata":{"redocly_category":"Development","owner":"MB"}},"lastModified":"2026-07-10T06:04:35.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/developer-portal/app-development/working-with-b2b-storefronts","userData":{"isAuthenticated":false,"teams":["anonymous"]}}