# Cart preparation and review The Cart is the single source of truth for all checkout calculations in commercetools. Before a customer proceeds to shipping, payment, and Order finalization, the Cart must be up to date. - **The Cart must be linked to a user**: For guest users, the `anonymousId` is set on the Cart. For registered users, the `customerId` is set on the Cart. - **Prices, promotions, and totals must be up to date**: The Cart totals (subtotal, taxes, shipping, discounts) must be accurate. When you change item quantities or apply a discount code, commercetools automatically recalculates prices. To update a stale Cart, trigger a [recalculate](/api/projects/carts.md#recalculate) action manually. Prices inside the `variant` are only updated if `updateProductData` is set to `true`. - **Custom business rule validations must be enforced**: commercetools prevents invalid actions like creating an Order with a deleted Product. However, you are responsible for implementing most business rule validations, such as minimum order values or item stock requirements. At the review stage, resolve any discrepancies to avoid issues during checkout—for example, if a discount is no longer valid or a cart is empty, resolve the issue before the customer proceeds to payment. Such "sanity checks" ensure the Cart contents are valid and ready for checkout. ## Retrieving the active Cart Cart retrieval differs for registered and guest (anonymous) users. In both cases, use a single active Cart for the session to reduce complexity, even though commercetools technically allows multiple active Carts per customer. ### Guest (anonymous) users The Cart is linked to a guest user using the `anonymousId`, which is an arbitrary identifier, such as a UUID, that you store in the user's browser. When creating a Cart for the guest, set the `anonymousId` so you can query it later. For the retrieval patterns and query examples, see [Find and manage existing Carts](/learning-implement-carts-and-shopping-lists/implement-carts/find-and-manage-existing-carts.md). ### Registered users The Cart is linked to a registered user, or logged-in Customer, using the `customerId`, which is associated with a Customer account. If a Customer has multiple active Carts, merge them or select the most recent one, based on your business requirements. For the retrieval decision points, see [Find and manage existing Carts](/learning-implement-carts-and-shopping-lists/implement-carts/find-and-manage-existing-carts.md) and [Manage cart sessions and merges](/learning-implement-carts-and-shopping-lists/implement-carts/manage-cart-sessions-and-merges.md). ### Merging Carts on login If a user starts as a guest and then signs in, define the merge behavior before checkout begins. The important decision is whether the anonymous Cart should merge into the existing Customer Cart or replace it. For merge strategies and implementation tradeoffs, see [Manage cart sessions and merges](/learning-implement-carts-and-shopping-lists/implement-carts/manage-cart-sessions-and-merges.md). ## Displaying Cart contents When displaying the Cart in the UI, map the API response to only the fields your frontend requires. This prevents over-fetching and unnecessary data transfer. You can achieve this effectively by using the GraphQL API to query the key fields. The following are key fields for a Cart summary view: - **Line Items**: Map to `cart.lineItems`. It is an array of Products, and each Line Item includes the Product `name`, `variant`, `quantity`, per-unit `price`, and `totalPrice` for that line, which accounts for quantity and any line item-specific discounts. - **Discounts**: Map to `cart.discountCodes`, if discount codes are applied. To display the code string, expand the reference. - **Taxed Price**: Map to `cart.taxedPrice`, if taxes are configured. It provides a breakdown of the net, gross, and tax portions of the price. - **Shipping Info**: Map to `cart.shippingInfo`, if a shipping method is set. It contains the method name and cost, which you can display in the order summary. ## Cart modifications (pre-checkout) Users often make last-minute changes during the cart review stage; common modifications include changing item quantities, removing items, and applying or removing discount codes. Use Cart update actions that match the user intent, such as changing a Line Item quantity (`changeLineItemQuantity`), removing a Line Item (`removeLineItem`), or applying (`addDiscountCode`) and removing discount codes (`removeDiscountCode`). For the full action list and request details, see [Cart update actions](/api/projects/carts.md#update-actions). Always read the latest Cart version before performing an update, and verify that the `anonymousId` or `customerId` matches the current user. This "read-before-write" pattern prevents conflicting updates. ```ts title="Apply a discount code to a Cart" async function applyDiscountCode( storeKey: string, cartId: string, version: number, code: string ) { return apiRoot .inStoreKeyWithStoreKeyValue({ storeKey }) .carts() .withId({ ID: cartId }) .post({ body: { version, actions: [{ action: "addDiscountCode", code }], }, }) .execute(); } ``` ### Handling discount code failures Discount codes fail in two ways: - **Immediate failure**: If you call `addDiscountCode` with an invalid code or if the Cart does not meet the promotion's conditions, commercetools returns an error. Alternatively, the discount code appears in `discountCodes` with a `DoesNotMatchCart` [state](/api/projects/carts.md#discountcodestate). - **Delayed invalidation**: A discount can become invalid after being applied. This can happen if the user removes an item required for the discount, the promotion expires, or a usage limit is reached. commercetools re-evaluates discount validity on every Cart update. When a discount code is invalid or has expired, provide clear, user-friendly feedback to help users correct the issue. Explain why the code failed instead of a generic error message—for example, "This code has expired" or "This code is not valid for the items in your cart." As a best practice, recheck the `discountCodes` state after every Cart modification. If a discount code state is not `MatchesCart`, remove the discount code and inform the user. This ensures the final price is accurate before payment. ### Handling guest checkout limitations While guest checkouts reduce friction, they have limitations—for example, since a guest is not associated with a Customer Group, Cart Discounts that target specific groups won't apply automatically. You can work around this limitation using one of the following strategies: - **Set a Custom Field on the Cart**: Configure the Cart Discount to check a Custom Field on the Cart instead of the customer's `customerGroup`. This allows you to apply targeted discounts to guest users without requiring them to register. - **Create a placeholder Customer**: Create a placeholder Customer entity in commercetools that contains a link to the external customer, identifiable through a unique identifier. This allows you to associate the guest with a Customer Group. ## Validating the Cart Before proceeding to checkout, ensure the Cart contains all necessary data and meets your business requirements. ### Required Cart data before checkout The Cart must have the following data before the user can proceed to the shipping stage: - At least one `lineItem` - A `currency` and `country` (the country is important for tax calculations) - A valid, non-zero `totalPrice` (unless you allow free Orders) - All prices recalculated after the last update ### Custom business rule validations Beyond commercetools built-in checks, you might need to implement custom validations: - **Minimum or maximum order values**: For example, requiring a Cart total of at least US$20. - **Cart not empty**: Prevent users from proceeding to checkout with an empty Cart. - **Inventory availability**: By default, commercetools does not reserve stock when a Cart is created. Depending on the Cart `inventoryMode`, stock is only checked and decremented when the Order is created. You might want to check commercetools inventories on add to Cart and perform a real-time stock check before payment to inform the user if an item has gone out of stock. The ultimate check occurs at Order creation, which fails if stock is insufficient in `ReserveOnOrder` mode. - **Shipping eligibility or other business rules**: For example, restricting shipment of certain items to specific regions. ```ts title="Server-side validation logic" function validateCart(cart) { if (cart.totalPrice.centAmount < 2000) { // e.g. $20 throw new Error('Minimum order value is $20. Add more items to your Cart'); } if (!cart.lineItems.length) { throw new Error('Cart is empty'); } // Example, if using inventoryMode = ReserveOnOrder, optionally check stock for (const item of cart.lineItems) { if (item.variant.availability.availableQuantity !== undefined && item.variant.availability.availableQuantity < item.quantity) { throw new Error(`Not enough stock for item ${item.name.en}. Available: ${item.variant.availability.availableQuantity}`); } } } ``` In the above example, `availableQuantity` is not a default field on Line Items. To perform a stock check, expand inventory data or manage it separately. ### Use BFF and API Extensions for cross-system validation Use your BFF for most validations. Categorize your validations into two groups: early blockers and late critical checks. Check early blockers (like an empty Cart) on the Cart page for immediate feedback. Perform critical checks (like final inventory) just before Order creation. In some cases, API Extensions are appropriate for server-side validations, such as checking inventory on Order creation. Use them when: - No BFF, API gateway, or proxy is in place. - You need to intercept changes applied in the Merchant Center (like placing an order on behalf of a customer) and no Custom Application is in place. - Many touchpoints (like POS, webshop, and mobile) must share the same validation logic and you can guarantee its performance. - You are building a third-party integration (like with PSPs and discounting engines) that is frontend agnostic and can be used by many different projects or customers. ### Implementation example: Cart retrieval and validation flow The following diagram shows how the client, BFF, and commercetools interact during Cart retrieval and validation: ```mermaid sequenceDiagram participant Client participant BFF participant commercetools Client->>BFF: GET /cart (request active cart) BFF->>commercetools: Query Cart (by `customerId` or `anonymousId`, `cartState`="Active") commercetools-->>BFF: Returns Cart data BFF->>BFF: Run validation checks on Cart BFF-->>Client: Respond with Cart data or errors ``` A GET request to fetch a Cart does not trigger a recalculation. If prices or discounts have changed since the last update to the Cart, the Cart's totals might be stale. To update to the latest state, use the [recalculate](/api/projects/carts.md#recalculate) action. ## Key takeaways - Maintain a single active Cart per session to simplify the user experience. - Fetch the Cart after every modification to get the latest version and prevent conflicts. - Define Cart merge rules for users who transition from guest to registered (logged-in) status. - Use the Cart as the single source of truth for pricing, promotions, and items. - Validate discount codes after every Cart modification to ensure they remain applicable. - Implement explicit validation logic for business rules at early and late stages of checkout. - Ensure the Cart contains all required data (line items, currency, country, and total price) before proceeding to checkout. Next, we will proceed to shipping information, where you'll learn to collect and validate addresses, select shipping methods, and update Cart totals accordingly. ## Related pages - [Area overview page with navigation](/learning-implement-checkout.md) - [Previous page: The checkout flow](/learning-implement-checkout/custom-checkout/the-checkout-flow.md) - [Next page: Shipping information](/learning-implement-checkout/custom-checkout/shipping.md) - [Search documentation and API specs](/search.md)