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).
Install
Section titled “Install”npx @backcap/cli add cartDomain Model
Section titled “Domain Model”Cart Entity
Section titled “Cart Entity”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"}| Field | Type | Description |
|---|---|---|
id | string | Unique identifier (UUID) |
userId | string | null | Optional user reference |
status | CartStatus | active, abandoned, or converted |
items | readonly CartItem[] | Cart items |
totalCents | number | Computed sum of all line totals |
maxItems | number | Maximum items allowed (default 50) |
currency | string | ISO 4217 currency code (default USD) |
Adding Items
Section titled “Adding Items”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.
State Machine
Section titled “State Machine”Carts follow a strict state machine:
- active → abandoned via
cart.abandon() - active → converted via
cart.convert() - No reverse transitions
- Items can only be added/removed/cleared on active carts
CartItem Entity
Section titled “CartItem Entity”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}Value Objects
Section titled “Value Objects”| VO | Description |
|---|---|
Quantity | Positive integer with configurable max (default 99) |
CartStatus | Enum: active, abandoned, converted with state machine |
Use Cases
Section titled “Use Cases”| Use Case | Description |
|---|---|
AddToCart | Add item with price verification via IProductPriceLookup |
RemoveFromCart | Remove item by variant ID |
UpdateQuantity | Update item quantity |
GetCart | Get cart with all items and totals |
ClearCart | Remove all items from cart |
AbandonCart | Transition cart to abandoned status |
ConvertCart | Transition cart to converted status |
Contract
Section titled “Contract”import { createCartService } from "./domains/cart/contracts";import type { ICartService, CartOutput } from "./domains/cart/contracts";
const cart: ICartService = createCartService({ cartRepository, productPriceLookup,});
// Add item to cartawait cart.addToCart({ cartId: "cart-123", productId: "prod-456", variantId: "var-789", quantity: 2,});
// Get cartconst result = await cart.getCart("cart-123");
// Lifecycle transitionsawait cart.abandonCart("cart-123");await cart.convertCart("cart-123");| Port | Description |
|---|---|
ICartRepository | Cart persistence (findById, findByUserId, save, update) |
IProductPriceLookup | Price verification at add time — prevents stale price attacks |
The IProductPriceLookup port is satisfied by the catalog domain’s contract.