Skip to main content

B2B & B2C architecture

Evolve runs B2B and B2C commerce from the same backend. A single set of services handles both models, so you can build a site that is B2C-only, B2B-only, or (increasingly common) a combination of both. B2B functionality layers on top of the standard commerce model, adding business units, associates with roles, quote workflows, and shared shopping lists. The path through the code depends on whether the authenticated user has an active business context.

How it builds on StoreContext

Every request in Evolve carries a StoreContext containing a store key, locale, and currency that scope the request to the right store. For B2C customers this is all that's needed: the StoreContext determines which assortment, prices, and content they see.

B2B adds a second layer. When a user belongs to a business unit, Evolve attaches a BusinessContext to their session. The BusinessContext can override the store key from the StoreContext with the business unit's own store, giving B2B customers store-specific pricing and assortments without requiring a separate deployment.

BusinessContext

When a B2B user logs in, Evolve loads their business units and selects a default. The active business unit and its associated store are stored in the user's session token as the BusinessContext:

type BusinessContext = {
businessUnitId: string;
businessUnitKey: string;
storeKey?: string;
storeId?: string;
channelId?: string;
};

The BusinessContext affects the entire request:

  • Store key override: the business unit's store replaces the default StoreContext store key, giving B2B customers store-specific assortments and pricing
  • Distribution channel: the business unit's channel enables B2B-specific pricing through commercetools price channels
  • API scoping: operations route through the Associate API instead of the customer me endpoint

Users with access to multiple business units can switch between them using the setActiveBusinessContext mutation:

mutation {
setActiveBusinessContext(
businessUnitId: "acme-corp"
storeKey: "b2b-store"
) {
success
}
}

Dual-path routing

Every operation that can be performed in both B2C and B2B contexts uses a dual-path pattern. The service checks whether the user has an active business context and routes accordingly:

  • B2C path: uses the customer's own OAuth token via the me API. Operations are scoped to the individual customer.
  • B2B path: uses system credentials via the Associate API, scoped to the associate's ID and business unit key. This enforces the commerce backend's built-in permission model for associates.

This pattern applies to orders, shopping lists, quotes, carts, and other operations that differ between B2C and B2B.

Business units

A business unit represents a company or organizational entity. It contains associates (users), addresses, and is linked to one or more stores.

type BusinessUnit {
id: ID!
name: String!
stores: [Store!]
addresses(page: Int, pageSize: Int): AddressConnection!
billingAddress: Address
associates(page: Int, pageSize: Int): BusinessUnitAssociateConnection!
availableRoles: [BusinessUnitRole!]!
orders(page: Int, pageSize: Int): OrderResult
shoppingLists(page: Int, pageSize: Int): ShoppingListResult
quoteRequests(page: Int, pageSize: Int): QuoteRequestResult
quotes(page: Int, pageSize: Int): QuoteResult
}

The BusinessUnit type is a federated entity extended by multiple services. The account service owns the core fields (name, stores, addresses, associates), while the order, quote, and shopping list services extend it with their respective fields using GraphQL Federation.

Associates and roles

Associates are users linked to a business unit. Each associate has one or more roles that determine their permissions:

type BusinessUnitAssociate {
id: ID!
email: EmailAddress!
givenName: String
familyName: String
roles: [BusinessUnitRole!]!
}

Associates can be managed through mutations:

  • businessUnitAssociateInvite: invite a new associate by email with specific roles
  • businessUnitAssociateRolesUpdate: change an associate's roles
  • businessUnitAssociateRemove: remove an associate from the business unit

Quotes

The quote workflow allows B2B customers to request pricing for a cart before placing an order.

A quote request is created from an existing cart:

mutation {
quoteRequestCreate(
id: "cart-id:cart-version"
args: {
comment: "Requesting volume discount"
purchaseOrderNumber: "PO-2024-001"
}
) {
quoteRequest { id { id } status }
}
}

Quote requests and quotes are accessed through the BusinessUnit type:

query {
businessUnit(id: "acme-corp") {
quoteRequests(page: 1, pageSize: 10) {
results {
id { id }
status
comment
lineItems { name, quantity, price { gross { centAmount } } }
}
}
}
}

Shopping lists

Shopping lists work for both B2C and B2B, but with different scoping. B2C customers have a single implicit shopping list. B2B users can create multiple named shopping lists that are shared within their business unit.

type ShoppingList {
id: ID!
name: String!
description: String
items: [ShoppingListLineItem!]!
}

In a B2B context, shopping lists are accessed through the business unit:

query {
businessUnit(id: "acme-corp") {
shoppingLists(page: 1, pageSize: 10) {
results { id, name, items { variant { sku }, quantity } }
}
}
}

Authentication flow

Authentication (login and registration) is identical for B2C and B2B customers. See Authentication & tokens for the full token system. The differentiation happens after login:

  1. The user logs in with the standard customerLogin mutation
  2. The account service loads the user's business units
  3. If business units exist, the first one is selected as the default business context
  4. The business context (business unit key + store key) is stored in the session token
  5. On subsequent requests, services detect the business context and route operations through the Associate API

The Customer type exposes B2B fields for customers with business units:

type Customer {
# ... standard fields ...
businessUnit(id: ID): BusinessUnit
businessUnits: [BusinessUnit!]
}

The frontend uses businessUnits to determine whether to show the B2C or B2B account experience.