Skip to content

Cart Domain

The cart domain provides shopping cart management for TypeScript backends. It handles item management, price verification at add time, quantity validation, total calculation, currency enforcement, and cart lifecycle (active → abandoned/converted).

Terminal window
npx @backcap/cli add cart

The Cart entity is the aggregate root. All item operations go through Cart methods — never directly on CartItem. Each cart has a single currency enforced across all items.

import { Cart } from "./domains/cart/domain/entities/cart.entity";
const result = Cart.create({
id: crypto.randomUUID(),
userId: "user-123",
currency: "USD", // all items must match
});
if (result.isOk()) {
const cart = result.unwrap();
console.log(cart.status.value); // "active"
console.log(cart.totalCents); // 0
console.log(cart.itemCount); // 0
console.log(cart.currency); // "USD"
}
FieldTypeDescription
idstringUnique identifier (UUID)
userIdstring | nullOptional user reference
statusCartStatusactive, abandoned, or converted
itemsreadonly CartItem[]Cart items
totalCentsnumberComputed sum of all line totals
maxItemsnumberMaximum items allowed (default 50)
currencystringISO 4217 currency code (default USD)

When adding an item with the same variantId, quantities are merged and price is updated to the latest value:

const updated = cart.addItem({
id: crypto.randomUUID(),
productId: "prod-123",
variantId: "var-456",
quantity: 2,
unitPriceCents: 1999,
});
if (updated.isOk()) {
console.log(updated.unwrap().totalCents); // 3998
}

Adding an item with a different currency than the cart’s will be rejected.

Carts follow a strict state machine:

  • activeabandoned via cart.abandon()
  • activeconverted via cart.convert()
  • No reverse transitions
  • Items can only be added/removed/cleared on active carts
import { CartItem } from "./domains/cart/domain/entities/cart-item.entity";
const item = CartItem.create({
id: crypto.randomUUID(),
productId: "prod-123",
variantId: "var-456",
quantity: 2,
unitPriceCents: 1999,
currency: "USD",
});
if (item.isOk()) {
console.log(item.unwrap().lineTotal); // 3998
}
VODescription
QuantityPositive integer with configurable max (default 99)
CartStatusEnum: active, abandoned, converted with state machine
Use CaseDescription
AddToCartAdd item with price verification via IProductPriceLookup
RemoveFromCartRemove item by variant ID
UpdateQuantityUpdate item quantity
GetCartGet cart with all items and totals
ClearCartRemove all items from cart
AbandonCartTransition cart to abandoned status
ConvertCartTransition cart to converted status
import { createCartService } from "./domains/cart/contracts";
import type { ICartService, CartOutput } from "./domains/cart/contracts";
const cart: ICartService = createCartService({
cartRepository,
productPriceLookup,
});
// Add item to cart
await cart.addToCart({
cartId: "cart-123",
productId: "prod-456",
variantId: "var-789",
quantity: 2,
});
// Get cart
const result = await cart.getCart("cart-123");
// Lifecycle transitions
await cart.abandonCart("cart-123");
await cart.convertCart("cart-123");
PortDescription
ICartRepositoryCart persistence (findById, findByUserId, save, update)
IProductPriceLookupPrice verification at add time — prevents stale price attacks

The IProductPriceLookup port is satisfied by the catalog domain’s contract.