Cart Service
Overview
The Cart Service manages shopping carts and their line items. Carts support anonymous guest flows, authenticated user sessions, and seamless merge on login.
Each cart is identified by a client-generated GUID and can be associated with a user after login.
Cart types are seeded as follows:
| Id | Title |
|---|---|
| 1 | Guest |
| 2 | Wishlist |
| 3 | Saved For Later |
| 4 | Quote |
All cart endpoints are [AllowAnonymous] with rate limiting applied per IP (AnonCart policy), except cart deletion which requires FullManage.
All operations are tenant-aware.
Database tables
sales.Cartsales.CartItemref.CartType
Endpoints
GET
/api/v1/Cart
Returns a cart by its GUID or by user GUID.
Query parameters:
guid(string, optional) — cart GUIDuserGuid(Guid, optional) — looks up the cart associated with the user
Behavior:
- Returns
404if not found - Only one of
guidoruserGuidshould be supplied
Response:
CartModel(includesItemslist)
Authorization:
[AllowAnonymous]— no token required
POST
/api/v1/Cart
Unified cart session endpoint — creates, claims, or merges a cart depending on the fields supplied.
Request body (CartSessionDTO):
UserGuid(Guid?, optional) — authenticated user's GUID. If present, the request must be authenticated.CartTypeId(int?, optional) — cart type to assign on creationMergeFromGuid(string?, optional) — GUID of a guest cart to merge into the user's cart
Behavior modes:
| Scenario | Fields supplied | Result |
|---|---|---|
| Create (guest) | {} |
Creates a new anonymous cart |
| Create (logged-in) | { "userGuid": "..." } |
Creates a cart owned by the user |
| Claim | { "userGuid": "...", "mergeFromGuid": "..." } |
Assigns the guest cart to the user if they have no existing cart |
| Merge | { "userGuid": "...", "mergeFromGuid": "..." } |
Merges the guest cart into the user's existing cart |
Response:
200 OK—CartModel
Authorization:
[AllowAnonymous]— but ifUserGuidis provided the request must include a valid Bearer token
POST
/api/v1/Cart/items
Adds, updates, or removes cart items by cart GUID.
Request body (UpsertCartItemsDTO):
Guid(string, required) — cart GUIDItems(List, required) — items to upsert: Sku(string, required) — product SKUQuantity(int, required) — new quantity. Set to0or negative to remove the item.
Behavior:
- An empty
Itemslist deletes the entire cart - Quantity
<= 0removes that item from the cart
Response:
200 OK—CartModel
Authorization:
[AllowAnonymous]— no token required
POST
/api/v1/Cart/guid/{guid}/preview
Calculates prices, tax, and totals for a cart without creating a document.
Route parameters:
guid(string, required) — cart GUID
Request body (PreviewCartRequest):
EntityId(long, required) — customer entity to price againstUserId(long?, optional) — user placing the orderPriceListId(long?, optional) — price list to apply
Behavior:
- Resolves product prices using the given entity and price list
- Applies price tier fallback: uses the closest tier at or below the requested quantity
- Returns a fully calculated document preview — no data is written
Response:
200 OK—DocumentModel(unsaved,Id = 0)
Authorization:
[AllowAnonymous]— no token required
POST
/api/v1/Cart/guid/{guid}/convert
Converts a cart into a saved document (sales order). The cart is deleted on success.
Route parameters:
guid(string, required) — cart GUID
Request body (ConvertCartRequest — extends PreviewCartRequest):
EntityId(long, required) — customer entityUserId(long?, optional) — user placing the orderPriceListId(long?, optional) — price list to applyBillingAddressSnapshots(DocumentBillingAddressSnapshotModel?, optional) — full billing address to stamp on the documentShippingAddressSnapshots(DocumentShippingAddressSnapshotModel, required) — full shipping address to stamp on the documentNotes(string?, optional) — order notesPurchaseOrderRef(string?, optional) — customer PO referenceIsBillingSameAsShipping(bool) — iftrue, billing snapshot is copied from shipping
Behavior:
- Creates a
DocumentandDocumentLinerecords from cart contents - Address snapshots are saved and linked to the document — they are immutable copies of the address at time of conversion, independent of any future address changes
- Prices are resolved using the same tier logic as preview
- WarehouseId is taken from the stock item associated with each variant
- Source cart is deleted on success
Response:
201 Created—DocumentModel(fully populated, same shape as/api/v1/Documents/{id})
Authorization:
[AllowAnonymous]— no token required
DELETE
/api/v1/Cart/{id}
Deletes a cart by its internal numeric ID.
Route parameters:
id(long, required) — cart identifier
Response:
204 No Content
Authorization:
- Requires Bearer Token
- Permission:
FullManage
Models
CartModel
Fields:
Guid (type: string?)— client-assigned GUID (used for all cart operations)DateAdded (type: DateTime?)— creation timestampDeviceId (type: string?)— device identifierUserId (type: long?)— associated userCartTypeId (type: int?)— FK toCartTypeDateLastUpdated (type: DateTime?)— last update timestampItems (type: List<CartItemModel>?)— cart line items
Note: Id is present in the DB but omitted from JSON responses.
CartItemPostDTO
Fields used when upserting items via POST /api/v1/Cart/items:
Sku (type: string)— product SKUQuantity (type: int)— quantity (<= 0removes the item)
PreviewCartRequest
Fields used for preview and as base for convert:
EntityId (type: long)— customer entityUserId (type: long?)— user placing the orderPriceListId (type: long?)— price list to apply
ConvertCartRequest
Extends PreviewCartRequest. Additional fields:
BillingAddressSnapshots (type: DocumentBillingAddressSnapshotModel?)— billing address snapshotShippingAddressSnapshots (type: DocumentShippingAddressSnapshotModel)— shipping address snapshotNotes (type: string?)— order notesPurchaseOrderRef (type: string?)— customer PO referenceIsBillingSameAsShipping (type: bool)— copy billing from shipping
DocumentAddressSnapshotModel
Used in both billing and shipping snapshot fields:
Name (type: string?)— address name / companyLine1 (type: string?)— street address line 1Line2 (type: string?)— street address line 2City (type: string?)— cityStateRegion (type: string?)— state or regionPostalCode (type: string?)— postal/ZIP codeCountryCode (type: string?)— ISO country codePhone (type: string?)— phone numberEmail (type: string?)— email
Notes
- All cart endpoints are rate-limited via the
AnonCartpolicy - Cart
Idis hidden from API responses; useGuidfor all client-side operations POST /api/v1/CartwithUserGuidrequires a valid JWT — the controller returns401ifUserGuidis provided but no token is present- Convert and preview use the same price resolution logic — preview is a dry run of convert
- Address snapshots are point-in-time copies; they do not link back to a live address record
- All data is tenant-scoped
- Internal errors are logged but not exposed to API consumers