Cart Module Backend Documentation
Backend architecture and data contracts for cart, quantity operations, manual address shipping projection, caching, and admin support reads.
Cart Module - Backend Documentation
1. Domain Scope
Cart manages:
- active cart creation/fetch
- item add/remove
- quantity operations (increase/decrease/set)
- applied coupon persistence + post-mutation revalidation
- shipping location projection (
district + address + fee) - public guest quote (read-only pricing + shipping projection)
- cache invalidation
Cart does not initiate payment directly. Payment initiation is owned by order endpoints.
2. Data Model
Source of truth:
packages/db/src/schema/cart/cart.tspackages/db/src/schema/config/shipping-fee.schema.ts
2.1 cart table
Important fields:
iduser_idstatusapplied_promotion_idapplied_promotion_codeapplied_discountshipping_districtshipping_mobile_numshipping_instructionsshipping_feeshipping_address_line_1shipping_address_line_2shipping_cityshipping_stateshipping_postal_codeshipping_landmarkshipping_latitudeshipping_longitudereservation_expired_at— set when cart stock reservation expires due to inactivity. Items are preserved; checkout re-reserves stock.created_atupdated_at
Important constraints/indexes:
- one active cart per user (
cart_one_active_per_userpartial unique) - non-negative
applied_discount - non-negative
shipping_fee
2.2 cart_item table
Important fields:
idcart_idproduct_idunit_mrpunit_spquantitycreated_atupdated_at
Important constraints/indexes:
- unique cart-product pair (
cart_item_cart_product_idx) - non-negative
unit_mrpandunit_sp
2.3 cart_care_package_items table
Important fields:
idcart_id(FK →cart.id, CASCADE DELETE)cart_item_id(FK →cart_item.id, CASCADE DELETE)care_package_id(FK →care_packages.id, RESTRICT)pricing_tier_id(FK →care_package_pricing_tiers.id, RESTRICT)unit_price(integer, in paisa) — effective price at add timecreated_at,updated_at
Important constraints/indexes:
- Unique
(cart_item_id)— enforces 1 care package per cart item - FK index on
care_package_id - FK index on
pricing_tier_id
3. Service Contracts
Primary service:
CartCustomerService
Key methods:
getCart(userId)addItem(userId, dto)— guards viaisSellableProductKind(productKind); onlyphysical,digital, andcare_packageproduct kinds are addableremoveItem(userId, itemId)increaseItemQuantity(userId, itemId)decreaseItemQuantity(userId, itemId)setItemQuantity(userId, itemId, quantity)getLocation(userId)listAvailableShippingDistricts()quoteGuestCart(dto)setLocationManual(userId, dto)setLocationAuto(userId, geocodeDto)clearLocation(userId)clearCart(userId)— removes all items from the active cartcompleteCart(cartId, userId)— clears items from a specific"checkout"-status cart, releases inventory reservations, resets promotion fields, and marks the cart"completed". Called by thecart.clear_after_order_confirmedjob on payment success. Idempotent: no-ops if the cart is not in"checkout"status
4. Response Mapping Notes
buildCartResponse(...) composes mobile/customer cart response:
- includes per-line
quantity - includes per-line
productKind(physical,digital,care_package) resolved via JOIN withproducts - maps each item
discountas per-unit discount:unitMrp - effectiveUnitSp - computes totals using quantity-aware aggregation
- can include
appliedCouponwhen an active cart-level promotion is present - can include
promoNoticewhen a previously applied coupon was removed during the current cart mutation - includes shipping projection fields when present; shipping fields are absent when the cart has no shipping location set (e.g. digital-only carts)
When cart.reservation_expired_at is set, the response includes:
reservationExpired: true— signals the frontend to show a banner that the stock reservation expired and will be re-attempted at checkout.
Post-mutation coupon flow:
addItem,removeItem,increaseItemQuantity,decreaseItemQuantity,setItemQuantity, andsyncCartmutate the cart first- then
finalizeCartResponse()re-evaluates the currentappliedCouponagainst the updated cart - if still valid, the stored discount is refreshed when needed
- if invalid, cart promo fields are cleared and the rebuilt response includes:
promoNotice.status = "promo_removed"promoNotice.reason(promotion_inactive,promotion_expired,promotion_not_started,usage_limit_reached,per_user_limit_reached,min_cart_value_not_met,scope_mismatch,cart_type_mismatch)promoNotice.message = "This promo is no longer valid for your current cart."
Money guardrails:
- cart/order calculations use shared minor-unit validation before aggregation
- effective sell price is resolved with fallback:
effectiveUnitSp = unitSp > 0 ? unitSp : unitMrp - invalid monetary inputs (
NaN, float, negative) fail fast before persistence/payment paths
Final payable logic:
finalPayable = totalSp - appliedDiscount + shippingFee
where null values fallback to 0.
Guest quote uses the same shared pricing helper as authenticated cart mapping, but with:
finalPayable = totalSp + shippingFee
because coupon application is not part of guest quote flow.
Checkout boundary:
- cart responses own coupon maintenance after cart mutations
- checkout/order creation still performs final validation as a safety guard
- payment-method mismatch remains a checkout concern, not a cart
promoNoticereason in the current flow
5. Location + Shipping Behavior
- location is cart-level, not line-level
- available district list reads active rows from
shipping_feeordered by district name - manual location flow resolves fee by district name from
shipping_fee - guest quote resolves fee by district name from
shipping_feeand combines it with backend-priced product lines plus optionalitems[].carePackageadd-ons - manual flow persists full address snapshot on cart
- auto location endpoint is disabled by default and returns
410 - if shipping fee does not exist for district: throws
CART_LOCATION_DISTRICT_NOT_AVAILABLE
5.1 Digital Product Handling
- Digital products (
productKind = "digital") and care packages (productKind = "care_package") do not require shipping. - When a cart contains only non-physical items, shipping fields (
shippingDistrict,shippingFee, address) are omitted from the cart response. - The checkout flow reads
hasPhysicalItemfrom cart items and skipsshippingDistrictvalidation when all items are digital/care-package.
6. Caching
Cache prefix:
cart:user:
Behavior:
- reads use cache-aside
- cart/location/item mutations invalidate user cart key pattern
- Redis failures degrade gracefully to DB behavior with warning logs
7. Rate Limiting
Controller-level Redis sliding window for mutating endpoints.
Defaults:
CART_CUSTOMER_OPERATION_RATE_LIMIT=60CART_CUSTOMER_RATE_WINDOW_SECONDS=60
Exceeded threshold throws RateLimitExceededException with standard envelope.
8. API Surfaces
Customer/mobile
/api/cart/*/api/mobile/cart/*- includes public
POST /cart/guest/quotefor unauthenticated pricing preview, including optional care package add-ons
Admin
/api/admin/cart/users/:userId
Admin read uses CartAdminService and returns active cart for support/debug.
9. Cart Lifecycle & Async Integration
9.1 Cart Status Lifecycle
A cart transitions through these statuses during its lifecycle:
"active"— default state for an in-progress cart. Only one active cart per user (cart_one_active_per_userpartial unique index)."checkout"— set during order creation (createFromCart). The cart is locked but still visible/queryable. Used to link the order to its cart viaorders.cartId. Seefix-cart-clear-timingfeature."completed"— set byCartCustomerService.completeCart()on payment success. The cart's items have been cleared, inventory reservations released, and promotion fields reset."abandoned"— set by payment-failure restore when the user already has a different active cart (collision case).
9.2 Async Queue Jobs
Cart queue jobs:
cart.clear_after_order_confirmed— on payment success, resolvesorders.cartIdfrom the order, callsCartCustomerService.completeCart(cartId, userId)to clear items and mark the cart"completed". Skips cleanly when the order has no linked cart (buy-now/POS/pre-migration orders).cart.expire— releases stock reservation for idle carts after configurable inactivity TTL (CART_EXPIRE_IDLE_MS, default 30 min). Setsreservation_expired_aton the cart and clears promotion fields. Does not delete items or abandon the cart — items remain visible so the user sees what they added. On checkout, stock is re-reserved; if unavailable, the user gets per-item errors. Cache is invalidated so the frontend reflects the new state.
9.3 Payment-Failure Cart Restore
On payment failure, OrderProcessor.processPaymentFailed() restores the linked cart:
- If the cart is still
"checkout"and the user has no other active cart: restored to"active". - If the cart is still
"checkout"but the user already has a different active cart: marked"abandoned"(collision case). - If the order has no linked cart (
cartId = null): skipped. - The restore uses an atomic
NOT EXISTS-guarded conditional update — no exception-based handling, safe to replay.
9.4 clearCart vs completeCart
clearCart(userId)— legacy method, clears the user's active cart (resolved viagetActiveCart). Used by manual cart-clear flows.completeCart(cartId, userId)— new method, clears a specific cart identified bycartId, conditional on it being in"checkout"status. Used by the payment-success job. Does not replaceclearCart(both coexist).
10. Care-Package Cart Module
Service: CarePackageCartService
Key methods:
addCarePackage(cartItemId, dto, customerId)— validates cart item belongs to user, product is physical, care package is active, eligibility rules match the product, tier is active, and no existing attachment; writescartCarePackageItemsrowremoveCarePackage(cartItemId, customerId)— validates cart item belongs to user; deletescartCarePackageItemsrow; returns confirmation
Response integration:
- Cart response from
buildCartResponse()includescarePackageItemsfield — array ofCartCarePackageDtowith{ id, cartItemId, carePackageId, carePackageName, pricingTierId, durationMonths, unitPrice } finalPayableaggregation includes care packageunitPricecontributions viacalculateCartPricing
11. Error Codes (Cart-Relevant)
Cart Codes
CART_NOT_FOUNDCART_ITEM_NOT_FOUNDCART_PRODUCT_NOT_FOUNDCART_PRODUCT_NOT_ELIGIBLECART_INVALID_ITEMCART_LOCATION_SHIPPING_NOT_AVAILABLECART_LOCATION_DISTRICT_NOT_AVAILABLECART_LOCATION_AUTO_DISABLEDRATE_LIMIT_EXCEEDED
Care-Package Cart Codes
CARE_PACKAGE_CART_ITEM_NOT_FOUND— cart item not found or does not belong to userCARE_PACKAGE_CART_ITEM_ALREADY_HAS_PACKAGE— unique constraint violationCARE_PACKAGE_NOT_ELIGIBLE_FOR_PRODUCT— selected package eligibility rules do not match the cart productCARE_PACKAGE_CART_PACKAGE_NO_LONGER_AVAILABLE— package deactivated after initial addCARE_PACKAGE_CART_PRICING_TIER_NO_LONGER_ACTIVE— tier deactivated after initial add
Cart Module API & Integration Guide
API contracts for customer/mobile cart operations, manual delivery address flow, quantity operations, and admin cart read.
Cart Module Feature List
Functional behavior of cart operations, quantity management, manual delivery address flow, and async cart lifecycle hooks.