Cart preparation and review

Learn how to prepare and review a customer's cart within Composable Commerce.

Copy for LLM
View as Markdown

After completing this page, you should be able to:

  • Retrieve the active Cart for guest and registered users, and display its contents, including Line Items and discounts.

  • Implement Cart modifications and validate the Cart to enforce business rules before checkout.

The Cart is the single source of truth for all checkout calculations in Composable Commerce. 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, Composable Commerce automatically recalculates prices. To update a stale Cart, trigger a recalculate action manually.
  • Custom business rule validations must be enforced: Composable Commerce 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 Composable Commerce 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 (like 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.

Registered users

The Cart is linked to a registered user (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.

Merging Carts on login

If a user begins as a guest and then logs in, they might end up with two Carts: the guest Cart and the Cart tied to their account. To avoid losing items or discounts, define merge rules—for example, prioritize the customer's Cart or keep the guest Cart, based on your business requirements.

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.

To update the Cart, use the following Cart update actions:
  • changeLineItemQuantity: updates the quantity of a Line Item
  • removeLineItem: removes a Line Item from the Cart
  • addDiscountCode or removeDiscountCode: adds or removes a discount code
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.
Apply a discount code to a Cartts
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, Composable Commerce returns an error. Alternatively, the discount code appears in discountCodes with a DoesNotMatchCart state.
  • 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. Composable Commerce 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 Composable Commerce 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 Composable Commerce 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, Composable Commerce 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.
Server-side validation logicts
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:

  • There is no BFF, API gateway, or proxy in place.
  • There is a need to intercept changes applied in the Merchant Center (like placing an order on behalf of a customer) and there is no custom application in place.
  • There are many touchpoints in play (like POS, webshop, and mobile) that go through the same validation logic and the performance of the logic can be guaranteed.
  • 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 Composable Commerce interact during Cart retrieval and validation:

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 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.

Test your knowledge