# The B2B Cart A Horizon Hotels procurement officer reopens last month's basket, bumps two quantities, and submits it. An Atlas Corporate buyer assembles a 180-line requisition that ships to four sites, attaches a purchase order number their finance team will reconcile against, and hands the basket to a buying committee before anyone places the order. Both purchases start as a [Cart](/api/projects/carts.md), but the B2B Cart carries organizational context, multi-site delivery needs, and review steps that a single shopper does not need. For the general Cart lifecycle, including sessions, merges, and updates, see the [Implement carts and shopping lists](/learning-implement-carts-and-shopping-lists/implement-carts/overview.md) path. ## Business Unit context on the Cart A B2B Cart is created through the `as-associate` endpoint you met in [Configure associate access](/learning-model-b2b-commerce/configure-associate-access/api-endpoint-patterns-for-b2b.md), scoped to a Business Unit. The platform records the Business Unit on the Cart's `businessUnit` field and enforces the acting Associate's permissions server-side through the same Permission Gate. ```http POST /{projectKey}/as-associate/{associateId}/in-business-unit/key=atlas-apac/carts HTTP/1.1 Content-Type: application/json { "currency": "AUD", "store": { "typeId": "store", "key": "atlas-apac-store" }, "purchaseOrderNumber": "PO-2026-04-1180", "lineItems": [ { "sku": "ZET-MON-27", "quantity": 40, "distributionChannel": { "typeId": "channel", "key": "atlas-apac-pricing" } } ] } ``` Two fields connect this Cart back to earlier modules: - **`store`** sets the commercial context (assortment and Channel) you designed in [Design B2B catalogs](/learning-model-b2b-commerce/design-b2b-catalogs/overview.md). - **`distributionChannel`** on each Line Item is the link to [Configure B2B pricing](/learning-model-b2b-commerce/configure-b2b-pricing/company-specific-pricing.md): it makes each item resolve the buyer's negotiated, Channel-scoped Price rather than an unscoped one. The `businessUnit` is taken from the URL path, not the request body, so the Cart always belongs to the Business Unit you addressed. ## Native purchase order tracking B2B buyers reconcile orders against a purchase order (PO) raised in their own procurement system. commercetools models this natively with the `purchaseOrderNumber` field. It exists on Cart, Quote Request, Staged Quote, Quote, and Order, and a value set on the Cart is **inherited by the Order or Quote Request created from it**, so you set it once at the start of the flow. Use the native `purchaseOrderNumber` field rather than a Custom Field for the PO. Custom Fields remain the right tool for other B2B context that has no native field, such as a cost-center reference on a Line Item or an internal requisition label. ## Payment by invoice Most B2B orders are settled by **invoice on account** against agreed net terms, not by card capture at checkout. The `purchaseOrderNumber` ties the Order to the buyer's procurement record, and a downstream financial system issues the invoice and reconciles payment on those terms. Model the settlement with the [Payments](/api/projects/payments.md) resource, and treat card authorization at checkout as the exception for B2B rather than the default. ## Modeling B2B-specific data The `purchaseOrderNumber` shows the first rule of B2B data modeling: prefer a native field whenever one exists. When a requirement has no native field, commercetools offers two extension tools, [Custom Fields](/api/projects/custom-fields.md) and [Custom Objects](/api/projects/custom-objects.md), and choosing between them is a recurring decision. - **Custom Fields** attach typed data to an **existing** resource. Use them when the data belongs to a Cart, Order, Line Item, Customer, Business Unit, or Quote, such as payment terms on a Cart, a cost-center reference on a Line Item, or an internal requisition label on an Order. - **Custom Objects** are **standalone** key-value records with no host resource. Use them for data that does not belong to a single resource instance, such as a buyer's credit limit, organization-wide configuration, or lookup tables that several resources reference. The rule of thumb: if the data describes this Cart, Order, or Line Item, reach for a Custom Field; if it stands on its own and is referenced from elsewhere, model it as a Custom Object. ## Shipping one Cart to many destinations A single B2B order frequently ships to several sites: a head office places one order, but pallets go to four hotels. The Cart's `shippingMode` controls this. Use the default `Single` [ShippingMode](/urn?urn=ctp%3Aapi%3Atype%3AShippingMode) when the whole Cart ships to one destination. Use `Multiple` when one order must deliver quantities to several registered `itemShippingAddresses`. With `Multiple`, register the destinations in `itemShippingAddresses` and then allocate each Line Item's quantity across those addresses through `shippingDetails`. Until every Line Item is fully allocated, the platform does not calculate `taxedPrice`, so an unallocated quantity is a common cause of a Cart that "won't show tax". ## Freezing a Cart for committee review B2B purchases often pass through a buying committee before submission. While the committee reviews, you do not want the catalog price to move underneath the agreed basket. [Freezing the Cart](/api/carts-orders-overview.md#freeze-a-cart) holds the agreed amounts in place. The `freezeStrategy` field chooses how strict that hold is. Use the default `SoftFreeze` [FreezeStrategy](/urn?urn=ctp%3Aapi%3Atype%3AFreezeStrategy) when the committee only needs Line Item prices to hold. Use `HardFreeze` when the final total must stay unchanged because Cart Discounts, Discount Codes, and Shipping Methods must also remain fixed. ```http POST /{projectKey}/as-associate/{associateId}/in-business-unit/key=atlas-apac/carts/{cartId} HTTP/1.1 Content-Type: application/json { "version": 7, "actions": [ { "action": "freezeCart", "freezeStrategy": "HardFreeze" } ] } ``` Freezing blocks only the updates that would change the Cart's total price; a buyer can still adjust quantities that do not affect price. To prevent **all** edits, for example while a support agent works on the Cart, [lock the Cart](/api/carts-orders-overview.md#lock-a-cart) instead. A lock records which API Client holds it, and is released with `unlockCart`. Freeze and lock solve different problems. A freeze protects the agreed price during committee review but still allows price-neutral edits. A lock prevents every change while one actor operates on the Cart. Reaching for a freeze when you need a lock (or the reverse) is a common B2B design mistake. ## Reordering with Cart replication High-frequency buyers resubmit a standing basket. Two mechanisms serve this, and they are different tools: - **Reorder** is buyer-driven and one-off. To repeat a past purchase, [replicate](/api/projects/carts.md#replicate-cart) an existing Cart or Order into a fresh, active Cart, then adjust quantities before checkout. A saved [Shopping List](/learning-model-b2b-commerce/discover-and-order-products-in-b2b/shopping-lists-for-b2b-purchasing.md) serves the same goal from a curated template. - **Recurring Order** is scheduled and automated. For a fixed cadence, model the basket as a [Recurring Order](/learning-model-b2b-commerce/implement-b2b-purchase-flows/recurring-orders-for-b2b.md) instead, which you design later in this module. Reach for a reorder when a person decides to buy again, and for a Recurring Order when the schedule should decide. ## Supporting punch-out procurement Large buyers often shop from their own procurement portal rather than the seller's storefront, a pattern called **punch-out**. The buyer's system connects to the seller's catalog, the buyer assembles a basket, and the order returns to the buyer's system for internal approval before it is sent to the seller. commercetools does not ship a punch-out connector, but you can support a Level 1 punch-out flow by combining features you have already met: - **[Product Selections](/learning-model-b2b-commerce/design-b2b-catalogs/company-specific-product-catalogs.md)** expose only the buyer's contracted catalog and negotiated prices through their Store, so the punch-out session shows the right assortment. - **`as-associate` Carts** assemble the basket in the buyer's Business Unit context, carrying the Store and distribution Channel so prices resolve correctly. - **[Approval Flows](/learning-model-b2b-commerce/configure-approval-workflows/overview.md)** enforce the buyer's internal spending thresholds before an order is committed. The integration work sits in the middleware that bridges the buyer's procurement protocol to these resources; the platform supplies the scoped catalog, the contextual Cart, and the governance a punch-out workflow needs. ## Direct Order or Quote: the decision point A filled B2B Cart leads to one of two paths, and choosing between them is the architectural decision this module turns on: - **Direct Order**: the buyer is entitled to a price they accept as-is, so the Cart becomes an Order directly. This fits self-service reordering. - **Quote**: the basket needs negotiation, such as volume pricing or custom terms, before it becomes an Order. A supported Cart becomes a Quote Request. This fits large or bespoke purchases. These paths have one platform constraint that shapes the design: a Quote Request cannot be created from a Cart with `shippingMode: Multiple`. It also cannot be created from an anonymous Cart or a Cart with Discount Codes, and the source Cart must have a `shippingAddress`. If a buyer needs both negotiated terms and delivery to several sites, design separate single-shipping Quote Requests per destination, or negotiate the terms first and then use Direct Orders for the multi-site purchasing pattern. ```mermaid flowchart TD A[B2B Cart with BU context] --> B{Price accepted as-is?} B -->|Yes, including multi-site delivery| C[Create Order from Cart] B -->|No, needs negotiation| D[Use a supported single-shipping Cart] D --> E[Create Quote Request from Cart] E --> F[Quote lifecycle] F --> G[Create Order from Quote] ``` The rest of this module follows the [Quote lifecycle](/learning-model-b2b-commerce/implement-b2b-purchase-flows/the-quote-lifecycle.md) in detail, then returns to automated reordering with Recurring Orders. ## Worked example: choosing the right Atlas Cart path Atlas Corporate's APAC division has two related purchasing needs: one order must ship to several sites, and another large basket needs negotiation. Keep those paths separate: 1. **For a multi-site Direct Order, create the Cart with context.** `POST` to the `as-associate` / `in-business-unit/key=atlas-apac` Carts endpoint, in `AUD`, against the `atlas-apac-store`, setting `purchaseOrderNumber` to the buyer's PO. Each Line Item carries the `atlas-apac-pricing` distribution Channel so prices resolve to Atlas's negotiated rates. 2. **Distribute the shipment for the Direct Order.** Set `shippingMode: Multiple`, register the four hotel addresses in `itemShippingAddresses`, and allocate each Line Item's quantity across them with `shippingDetails`. Confirm `taxedPrice` now appears, proving every quantity is allocated. 3. **Freeze for the committee.** Apply `freezeCart` with `HardFreeze` so neither the catalog price nor any discount or shipping cost can move while the committee reviews the agreed total. When the committee approves, create the Order from this Cart. 4. **For negotiated terms, create a supported Quote Request Cart.** Use a separate Cart with `shippingMode: Single`, a `shippingAddress`, and no Discount Codes. Keep the Business Unit context, purchase order number, Store, and distribution Channel, then create the Quote Request from that Cart. This design uses multi-shipping where the platform supports it, and uses the Quote flow only from a Cart shape that Quote Requests support. ## Key takeaways - A B2B Cart is created through the `as-associate` and `in-business-unit` path; the Business Unit comes from the URL, and Line Item `distributionChannel` resolves the buyer's negotiated Price. - Use the native `purchaseOrderNumber` for PO tracking; it propagates from Cart to the Order or Quote Request created from it. Reserve Custom Fields for context with no native field. - For B2B data with no native field, use a Custom Field for data that belongs to a resource and a Custom Object for standalone data referenced from elsewhere. - `shippingMode: Multiple` ships one Cart to many `itemShippingAddresses`; unallocated quantities suppress `taxedPrice`. - `SoftFreeze` locks prices; `HardFreeze` also locks discounts and shipping. A freeze allows price-neutral edits, while a lock blocks all edits. - Reorder a past purchase by replicating a Cart or Order; use a Recurring Order for a fixed, scheduled cadence instead. - Most B2B orders settle by invoice on account against net terms; the `purchaseOrderNumber` links the Order to the buyer's procurement record. - Support punch-out procurement by combining Product Selections, `as-associate` Carts, and Approval Flows through middleware, not a built-in connector. - A filled Cart becomes either a Direct Order (price accepted) or, when the Cart uses a supported single-shipping shape, a Quote Request (price negotiated). ## Related pages - [Area overview page with navigation](/learning-model-b2b-commerce.md) - [Previous page: Overview](/learning-model-b2b-commerce/implement-b2b-purchase-flows/overview.md) - [Next page: The Quote lifecycle](/learning-model-b2b-commerce/implement-b2b-purchase-flows/the-quote-lifecycle.md)