Update Carts

Explore various update actions to dynamically modify and enhance your customer Carts.

After completing this page, you should be able to:

  • Apply a variety of update actions using the SDK to manage a Cart's contents, shipping details, and tax configuration.
  • Incorporate optimistic locking by providing the correct Cart version in every update request to prevent data conflicts.
Once a Cart exists, you modify it by sending a POST request containing an array of update actions. Every update request must include the Cart's current version to ensure optimistic concurrency control, preventing race conditions and accidental overwrites.

Manage Line Items

Add a Product to the Cart (addLineItem)

This action adds a product from your catalog to the Cart.

import { apiRoot } from "../ctp-root";
import { Cart, CartUpdateAction } from "@Composable Commerce/platform-sdk";

/**
 * Adds a product (Line Item) to an existing Cart.
 * @param storeKey The key of the Store where the cart exists.
 * @param cartId The ID of the cart to update.
 * @param version The current version of the Cart for optimistic locking.
 * @param sku The SKU of the Product Variant to add.
 * @param quantity The number of items to add.
 * @returns A Promise resolving to the updated Cart.
 */
async function addLineItemToCart(
  storeKey: string,
  cartId: string,
  version: number,
  sku: string,
  quantity: number
): Promise<Cart> {
  const addLineItemAction: CartUpdateAction = {
    action: "addLineItem",
    sku, // The SKU of the Product Variant
    quantity,
  };

  const { body } = await apiRoot
    .inStoreKeyWithStoreKeyValue({ storeKey })
    .carts()
    .withId({ ID: cartId })
    .post({
      body: {
        version: version,
        actions: [addLineItemAction],
      },
    })
    .execute();

  return body;
}

Line Items are merged (increase the quantity of the existing Line Item) when you add a Line Item that already exists in the Cart if the following attributes match:

  • Line Item Key
  • Product Variant (productId and variantId, or sku)
  • Supply, Distribution Channels
  • externalPrice, externalTotalPrice
  • externalTaxRate, perMethodExternalTaxRate
  • inventoryMode
  • shippingDetails
  • Custom Fields

Remove a Product from the Cart (removeLineItem)

This action removes a specified quantity of a Line Item. If quantity is omitted, the entire Line Item is removed.
/**
 * Removes a specific quantity of a Line Item from an existing Cart.
 * @param lineItemId The ID of the Line Item to remove from.
 * @param quantity The number of items to remove. If omitted, removes the entire Line Item.
 */
async function removeLineItemFromCart(
  storeKey: string,
  cartId: string,
  version: number,
  lineItemId: string,
  quantity?: number
): Promise<Cart> {
  const removeLineItemAction: CartUpdateAction = {
    action: "removeLineItem",
    lineItemId,
    quantity, // Omitting this removes the full quantity
  };
  // ... execute post request
}

Add an external Line Item (addCustomLineItem)

For products or services not in your Project catalog (for example, installation fees, replacement parts from a third party), use addCustomLineItem. This creates a CustomLineItem in the Cart.
import {
  Cart,
  CartUpdateAction,
  TypedMoneyDraft,
} from "@Composable Commerce/platform-sdk";

/**
 * Adds an external item (as a Custom Line Item) to an existing Cart.
 * @param name Localized name of the external item, for example, { "en-US": "Installation Fee" }.
 * @param quantity The quantity of the external item.
 * @param money The price of the external item.
 * @param slug A unique, URL-friendly identifier for the custom Line Item.
 */
async function addExternalLineItem(
  storeKey: string,
  cartId: string,
  version: number,
  name: { [key: string]: string },
  quantity: number,
  money: TypedMoneyDraft,
  slug: string
): Promise<Cart> {
  const addCustomLineItemAction: CartUpdateAction = {
    action: "addCustomLineItem",
    name,
    quantity,
    money,
    slug,
  };
  // ... execute post request
}

Manage Addresses

Set Shipping (setShippingAddress)

To associate an existing Customer Address with a Cart, you will need to use the action setBillingAddress and pass the whole address record from the Customer record (including the ID). This will help you avoid data duplication.
import { Cart, CartUpdateAction } from '@Composable Commerce/platform-sdk';

// Define a type for the address data for better type-safety and readability
type CartAddress = {
  id: string;
  firstName: string;
  lastName: string;
  streetName: string;
  streetNumber: string;
  postalCode: string;
  city: string;
  mobile?: string;
  country: string;
};

/**
 * Sets the shipping on a cart by providing the full address details.
 * @param storeKey The key of the Store.
 * @param cartId The ID of the Cart to update.
 * @param version The current version of the Cart.
 * @param shippingAddress The full address object for shipping.
 */
async function setAddressWithDetails(
  storeKey: string,
  cartId: string,
  version: number,
  shippingAddress: CartAddress,
): Promise<Cart> {
  const actions: CartUpdateAction[] = [
    {
      action: 'setShippingAddress',
      address: {
        id: shippingAddress.id,
        firstName: shippingAddress.firstName,
        lastName: shippingAddress.lastName,
        streetName: shippingAddress.streetName,
        streetNumber: shippingAddress.streetNumber,
        postalCode: shippingAddress.postalCode,
        city: shippingAddress.city,
        mobile: shippingAddress.mobile,
        country: shippingAddress.country,
      },
    },
  ];

  // The body of your API request would be:
  const requestBody = {
    version,
    actions,
  };

  // This is a placeholder return for the example
  return {} as Cart;
}

// --- How to use the function ---

// 1. Define your address data based on the customer record
const myAddress: CartAddress = {
  id: 'ZKCtEGyo',
  firstName: 'Example',
  lastName: 'Person',
  streetName: 'Down Under st',
  streetNumber: '360',
  postalCode: '3000',
  city: 'Melbourne',
  mobile: '+61434126439',
  country: 'AU',
};

// 2. Call the function (assuming shipping and billing addresses are the same)
setAddressWithDetails(
  'my-store',
  'zen-electron-us',
  5, // The current cart version
  myAddress,
);

Advanced Cart features

Add a Shopping List to the Cart (addShoppingList)

This action efficiently adds all items from a specified Shopping List to the Cart.
/**
 * Adds all items from a Shopping List to an existing Cart.
 * @param shoppingListId The ID of the Shopping List to add.
 */
async function addShoppingListToCart(
  storeKey: string,
  cartId: string,
  version: number,
  shoppingListId: string
): Promise<Cart> {
  const addShoppingListAction: CartUpdateAction = {
    action: "addShoppingList",
    shoppingList: {
      typeId: "shopping-list",
      id: shoppingListId,
    },
  };
  // ... execute post request
}

Handle external tax calculations

To use an external tax service (for example, Avalara or Vertex), you must first change the Cart's taxMode. There are two modes to consider:
  1. taxMode: 'External': use this when your service provides tax rates per Line Item. You then use the setLineItemTaxRate action to apply these rates.
  2. taxMode: 'ExternalAmount': use this when your service provides a single, total tax amount for the entire Cart. You then use the setCartTotalTax action.
Step 1: Change the Tax Mode: this is a prerequisite for all external tax actions.
const changeTaxModeAction: CartUpdateAction = {
  action: "changeTaxMode",
  taxMode: "ExternalAmount", // or 'External'
};
Step 2: Set the external tax: after changing the mode, apply the tax data from your external service.
import {
  Cart,
  CartUpdateAction,
  Money,
  TaxPortionDraft,
  ExternalTaxRateDraft,
  CartSetCartTotalTaxAction,
  CartSetLineItemTaxAmountAction,
} from "@Composable Commerce/platform-sdk";

// --- Helper Interface for clarity ---
/**
 * Defines the data needed to set the external tax for a single Line Item.
 */
export interface LineItemTaxDetails {
  lineItemId: string;
  totalGross: Money;
  taxRate: ExternalTaxRateDraft;
}

/**
 * Sets an external tax amount for the entire cart and for each Line Item.
 * This is used when the Cart's taxMode is 'ExternalAmount'.
 *
 * @param storeKey The key of the store.
 * @param cartId The ID of the Cart to update.
 * @param version The current version of the Cart.
 * @param externalTotalGross The total gross amount of the Cart (including all taxes) from the external service.
 * @param lineItemTaxDetails An array containing the tax details for each Line Item.
 * @param externalTaxPortions An optional breakdown of the total Cart tax into different portions (for example, state or federal).
 */
async function setExternalTaxForCart(
  storeKey: string,
  cartId: string,
  version: number,
  externalTotalGross: Money,
  lineItemTaxDetails: LineItemTaxDetails[],
  externalTaxPortions?: TaxPortionDraft[]
): Promise<Cart> {
  // This function will generate multiple update actions
  const updateActions: CartUpdateAction[] = [];

  // 1. Action to set the total tax for the entire cart
  const setCartTotalTaxAction: CartSetCartTotalTaxAction = {
    action: "setCartTotalTax",
    externalTotalGross,
    externalTaxPortions,
  };
  updateActions.push(setCartTotalTaxAction);

  // 2. Actions to set the tax for each individual Line Item
  for (const item of lineItemTaxDetails) {
    const setLineItemTaxAction: CartSetLineItemTaxAmountAction = {
      action: "setLineItemTaxAmount",
      lineItemId: item.lineItemId,
      externalTaxAmount: {
        totalGross: item.totalGross,
        taxRate: item.taxRate,
      },
    };
    updateActions.push(setLineItemTaxAction);
  }

  /*
 // Example of executing the request with the Composable Commerce SDK client
 const updatedCart = await apiRoot
   .inStore({ storeKey })
   .carts()
   .withId({ ID: cartId })
   .post({
     body: {
       version,
       actions: updateActions,
     },
   })
   .execute();

 return updatedCart.body;
 */

  // For demonstration purposes, we'll return a mock Cart.
  // Replace this with the actual API call above.
}

async function runExample() {
  // --- Cart & Tax Service Data (Example) ---
  const cartId = "c7a3b1e0-f9d5-4a2c-8e1d-9b4c0f8a7b6d";
  const cartVersion = 5;
  const storeKey = "zen-electron-us";

  // Imagine your external tax service returned the following calculations for a Cart
  // with two Line Items.
  // Item 1: T-Shirt ($25.00) -> Gross Price: $26.88 (with 7.5% tax)
  // Item 2: Jeans ($70.00) -> Gross Price: $75.25 (with 7.5% tax)
  // Total Gross: $26.88 + $75.25 = $102.13

  // Total gross price for the entire Cart
  const cartTotalGross: Money = {
    currencyCode: "USD",
    centAmount: 10213, // $102.13
  };

  // Tax details for each Line Item
  const lineItemsToTax: LineItemTaxDetails[] = [
    {
      lineItemId: "6f9f5a83-808c-49f9-a3ef-5e32cd377042",
      totalGross: {
        currencyCode: "USD",
        centAmount: 2688, // $26.88
      },
      taxRate: {
        name: "CA Sales Tax",
        amount: 0.075, // 7.5%
        country: "US",
        state: "CA", // It's good practice to include the state for US taxes
      },
    },
    {
      lineItemId: "7f9f5a83-808c-49f9-a3ef-5e32cd377042",
      totalGross: {
        currencyCode: "USD",
        centAmount: 7525, // $75.25
      },
      taxRate: {
        name: "CA Sales Tax",
        amount: 0.075, // 7.5%
        country: "US",
        state: "CA",
      },
    },
  ];

  // Call the updated function with all the necessary data
  await setExternalTaxForCart(
    storeKey,
    cartId,
    cartVersion,
    cartTotalGross,
    lineItemsToTax
  );
}

runExample();

Test your knowledge