Happy House - Ecommerce Docs
Developer ResourcesCheckout

Checkout API Reference

Complete API contracts for the Checkout module, including routes, auth, DTOs, responses, errors, examples, and integration notes.

Checkout - API Reference

Audience: Frontend engineers, mobile engineers, backend engineers, QA, and API consumers. Scope: The three customer checkout routes and the four admin session routes. Payment has no HTTP surface.

1. Documentation Evidence

AreaFiles InspectedWhat Was Verified
Controllersapps/api/src/modules/checkout/customer/checkout-customer.controller.ts, admin/checkout-admin.controller.tsRoutes, methods, guards, permissions, rate limits
DTOsdto/*.tsValidation, strict unknown-property rejection
Servicesshared/checkout-session.service.ts, checkout-payment.service.ts, checkout-release.service.tsBehavior, errors
Schemapackages/db/src/schema/checkout/*.tsTotals CHECK, partial uniques, snapshots
Error registryapps/api/src/common/types/error-codes.ts (// CHECKOUT)CHECKOUT_* codes

2. Module Summary

FieldValue
Module namecheckout
Module slugcheckout
Primary actorscustomer, admin, payment (future, internal)
API surfacesmobile (customer), admin
Base route prefixes/api/mobile/checkout, /api/checkout/sessions
Auth modelJwtAuthGuard (customer); JwtAuthGuard + RoleGuard (admin)
PersistencePostgreSQL (three session tables + snapshots); no cache
Runtime source of truthcheckout_session rows + live inventory/promotion ledgers
Sibling docsBackend, Features and flows

3. Concepts and Terminology

TermMeaningSource FileUsed By
attemptThe payment-attempt handle; every payment-side transition is scoped to itschemaPayment contract
expiresAt / expiresInSecondsSession expiry; a read predicate, never a stored statusschemaAll routes
statuspending_payment / payment_in_progress / completed / cancelled / expiredschemaAll routes
grandTotalsubtotal - discount + shipping - shippingDiscount — database-enforcedschemaResponse
reservedfalse on a line = untracked product, nothing to hold — normalsession serviceItems
priceChangeWarning, not a blocker — the cart's price vs the frozen pricesession serviceItems
cartVersionOptional optimistic token for the basket you displayedsession serviceStart

4. API Surface Map

SurfaceMethodPathActorAuth/GuardPermissionControllerPurpose
MobilePOST/api/mobile/checkoutCustomerJWT + IpThrottleCheckoutCustomerControllerStart checkout
MobileGET/api/mobile/checkout/activeCustomerJWT + IpThrottlesameMy live session
MobilePOST/api/mobile/checkout/:id/cancelCustomerJWT + IpThrottlesameCancel my pending session
AdminGET/api/checkout/sessionsAdminJWT+RoleCheckout_READCheckoutAdminControllerList sessions
AdminGET/api/checkout/sessions/:idAdminJWT+RoleCheckout_READsameSession detail
AdminPOST/api/checkout/sessions/:id/cancelAdminJWT+RoleCheckout_UPDATEsameCancel (fraud/duplicate)
AdminPOST/api/checkout/sessions/:id/expireAdminJWT+RoleCheckout_UPDATEsameForce-expire (stuck gateway)

{id} is always a uuid7 public_id; no integer PK is exposed.

5. Auth, Identity, and Permissions

SurfaceGuard/DecoratorIdentity ShapePermissionGuest AllowedNotes
CustomerJwtAuthGuard, IpThrottlerGuardreq.user.idNoCUSTOMER_CHECKOUT_ATTEMPT 10/min account-keyed (start — a checkout is a money-bearing attempt), CUSTOMER_READ 60/min (active), CUSTOMER_WRITE 20/min (cancel)
AdminJwtAuthGuard, RoleGuard, IpThrottlerGuardreq.userCheckout_READ / Checkout_UPDATENoADMIN_READ 30/min, ADMIN_WRITE 10/min

Another customer's session and a session that does not exist both return 404 with the same code — a 403 would confirm the id is real. Do not distinguish them.

6. DTO and Model Reference

6.1 StartCheckoutDto

FieldTypeRequiredValidationNotes
addressIdUUID v7Yes@IsUUID("7")
couponCodestringNo3–64 chars
cartVersionnumberNo>= 1Send when you have one — a stale version is a 409, not a silent repurchase

Do not send prices, discounts, shipping or totals. The server recalculates all of them, and the request is rejected outright if any unknown property is present — not silently stripped.

6.2 Params DTO

CheckoutSessionParamsDto { id } — uuid7.

6.3 Admin query DTO

status, customerId, createdFrom, createdTo, minTotal, maxTotal, offset pagination (page/size), sort by createdAt / updatedAt / expiresAt / grandTotal.

6.4 Response DTOs

Start/active/detail share the session shape:

{
  "checkout": { "id": "0198f2c1-…", "status": "pending_payment",
                "expiresAt": "…", "expiresInSeconds": 900, "version": 1 },
  "pricing": { "currency": "NPR", "subtotal": 250000, "discountAmount": 20000,
               "shippingAmount": 15000, "shippingDiscountAmount": 0, "grandTotal": 245000,
               "anyPriceChanged": true },
  "items": [{ "id": "…", "productId": "…", "name": "…", "sku": "ABC-1",
              "unitPrice": 100000, "mrp": 120000, "quantity": 2, "lineTotal": 200000,
              "priceChange": { "changed": true, "previousPrice": 95000, "currentPrice": 100000 },
              "reserved": true }],
  "promotions": [{ "id": "…", "promotionId": "…", "name": "Festival 20%",
                   "couponCode": "SAVE20", "discountAmount": 20000, "shippingDiscountAmount": 0 }],
  "shipping": { "districtName": "Kathmandu", "municipalityName": "…", "ward": 5,
                "street": "…", "landmark": null, "postalCode": null,
                "recipientName": "…", "recipientPhone": "+977…", "fee": 15000 },
  "payment": { "ready": true, "amountDue": 245000, "currency": "NPR" }
}

All money is integer minor units (245000 = NPR 2,450.00) — never parse as float. grandTotal is database-enforced; display any part without recomputing.

7. Enum Reference

EnumValueMeaningRuntime EffectSource
checkout_session_statuspending_paymentCreated; holds takenCancellable; payment may startenums.ts
checkout_session_statuspayment_in_progressAt the gatewaySweep-exempt; not cancellable from the app
checkout_session_statuscompleted / cancelled / expiredTerminalexpired is derived from expires_at — the status is only written to record the release

8. Endpoint Reference

8.1 POST /api/mobile/checkout

Purpose

Turn the cart into a validated, price-frozen purchase attempt that holds stock. 201 on create; 200 when an existing live session was returned.

Auth and Permissions

JwtAuthGuard; CUSTOMER_CHECKOUT_ATTEMPT 10/min (account-keyed).

Request

{ "addressId": "0198f2c1-…", "couponCode": "SAVE20", "cartVersion": 7 }

Prices, discounts, shipping and totals must not be sent — the server recalculates; any unknown property rejects the request.

Response

201 (create) or 200 (existing live session — the response is identical either way, so a repeat POST, retry after timeout, or two tabs are all safe). Session shape per §6.4.

Side Effects

One transaction: inventory + promotion reservations, price/shipping/promotion freeze, session + snapshot rows. A failed checkout leaves nothing behind.

Error Cases

HTTPCodeCondition
400validationUnknown property sent (rejected, not stripped)
404CHECKOUT_ADDRESS_NOT_FOUNDUnknown, archived, or not theirs
409CHECKOUT_CART_EMPTYNo cart or no lines
409CHECKOUT_CART_CHANGEDcartVersion stale
409CHECKOUT_CART_NOT_READYBlocking reasons — details.blockingReasons
409CHECKOUT_PRODUCT_UNAVAILABLEdetails.unavailableVariantIds — VARIANT public ids, not product ids: two configurations of one product can have only one unavailable
409CHECKOUT_INSUFFICIENT_STOCKdetails.shortfalls[]{productId, variantId, requested, available}; stock is held per variant, so variantId is the identifier that matters
409CHECKOUT_ADDRESS_NOT_SERVICEABLENo delivery to that district
409CHECKOUT_COUPON_NOT_APPLICABLEdetails.reasonretry without the coupon is a valid recovery

CHECKOUT_CART_NOT_READY, CHECKOUT_PRODUCT_UNAVAILABLE and CHECKOUT_INSUFFICIENT_STOCK are one builder (checkoutNotReady) choosing between three codes by precedence — every one of them carries all three detail fields (blockingReasons, unavailableVariantIds, shortfalls), not only the one its name suggests. A client can read whichever fields are non-empty regardless of which code came back.

The five things that shape the UI

  1. A repeat POST returns the SAME session with a 200, not an error — no button guard needed.
  2. expiresInSeconds is server-computed — run the countdown from it, never from the device clock. At zero, re-fetch: the session reports status: "expired".
  3. priceChange.changed is a warning, not a blocker — show "the price of X changed from A to B"; the customer has not paid yet.
  4. reserved: false on a line is normal — the product is not stock-tracked; nothing to hold.
  5. status is already correct for expiry — never compare expiresAt yourself; a session past expiry reads expired regardless of any background job.

8.2 GET /api/mobile/checkout/active

Purpose

Fetch my live session (for countdown polling or resume). CUSTOMER_READ 60/min.

Response

200 — session shape. An expired session reads expired; the customer starts again.

8.3 POST /api/mobile/checkout/:id/cancel

Purpose

Cancel my own pending session. Releases the held stock and the coupon slot, unlocks the cart. CUSTOMER_WRITE 20/min.

Error Cases

HTTPCodeCondition
404CHECKOUT_SESSION_NOT_FOUNDUnknown session, or not theirs (same code for both)
409CHECKOUT_SESSION_EXPIREDIt lapsed — offer to start again
409CHECKOUT_SESSION_NOT_CANCELLABLEAlready terminal — refresh
409CHECKOUT_PAYMENT_IN_PROGRESSA payment is at the gateway — tell them to finish or contact support

8.4 GET /api/checkout/sessions

Purpose

Admin session list. Checkout_READ; ADMIN_READ 30/min. Filters: status, customerId, createdFrom, createdTo, minTotal, maxTotal; offset pagination; sort by createdAt/updatedAt/expiresAt/grandTotal.

8.5 GET /api/checkout/sessions/:id

Detail. 404 CHECKOUT_SESSION_NOT_FOUND.

8.6 POST /api/checkout/sessions/:id/cancel

Purpose

Cancel a pending_payment session an operator judges fraudulent or duplicated. Releases holds + coupon slot, unlocks the cart, records which administrator acted. Checkout_UPDATE; ADMIN_WRITE 10/min.

8.7 POST /api/checkout/sessions/:id/expire

Purpose

Force-expire a session stuck at a payment gateway that never called back — the only lever for that state, because the automatic sweep deliberately never touches payment_in_progress. Same release semantics as cancel, with the administrator recorded.

9. Flow Diagrams

9.1 Route Ownership

9.2 Request Sequence (start)

9.3 Error Branch (start)

EndpointPagination TypeDefault SizeMax SizeSort FieldsFiltersResult Cap
GET /api/checkout/sessionsoffset page/size20100createdAt, updatedAt, expiresAt, grandTotalstatus, customerId, createdFrom, createdTo, minTotal, maxTotal

Customer endpoints are single-session (never paginated).

11. Caching, Jobs, and External Integrations

IntegrationUsed?Details
Redis cacheNo — checkout caches nothingEvery read is live or frozen
BullMQYescheckout queue — the expiry sweep, enqueued by a plain cron scheduler (no outbox row, no accompanying DB write)
External APINoPayment is a separate module, not yet built

13. Mandatory Deep API Documentation Pack

13.1 Route-by-Route Completeness Matrix

RouteController MethodDTOsService MethodGuardsPermissionsCacheJobsDB TouchesErrorsDocumented?
POST /api/mobile/checkoutstartStartCheckoutDtoCheckoutSessionService.startJWT+IpThrottlecart, address, inventory, promotion, session + snapshots400/404/409 (11 codes)Yes
GET /api/mobile/checkout/activegetActive…getActiveJWT+IpThrottlesessionsYes
POST /:id/cancelcancelparams DTO…cancelJWT+IpThrottlesession + releases404/409Yes
GET /api/checkout/sessionsfindAllquery DTOCheckoutAdminService.listJWT+Role+IpThrottleCheckout_READsessionsYes
GET /api/checkout/sessions/:idfindByIdparams DTO…findOneJWT+Role+IpThrottleCheckout_READsession404Yes
POST /:id/cancel (admin)cancelparams DTOCheckoutReleaseService.releaseAndCloseJWT+Role+IpThrottleCheckout_UPDATEsession + releases + audit404/409Yes
POST /:id/expireexpireparams DTO…releaseAndCloseJWT+Role+IpThrottleCheckout_UPDATEsession + releases + audit404/409Yes

13.2 Request/Response Exhaustiveness

Covered in §8: minimal start request (§6.1/8.1), full session response (§6.4), the 200-on-repeat behavior (§8.1 point 1), the expiry-read-predicate behavior (§8.1 point 5), domain errors per endpoint (§8 error tables), the same-404-for-others/unknown rule (§5), rate-limit behavior (10/min checkout budget).

13.3 API Diagram Pack

Route ownership (§9.1), request sequence (§9.2, backend §7.1), error decision tree (§9.3), payment-transition flow (backend §7.2 — no HTTP surface).

13.4 Consumer Integration Notes

ConsumerRequired KnowledgeFailure HandlingContract Stability
Web frontend200-on-repeat, server expiresInSeconds, priceChange warning, reserved:false normal409 codes → specific UI; coupon retry-without is validStable
Mobile appCUSTOMER_CHECKOUT_ATTEMPT 10/min; countdown from server429 → back off; re-fetch at zeroStable
Admin panelCancel vs expire semantics; read-only otherwise409 NOT_CANCELLABLE / PAYMENT_IN_PROGRESS → refreshStable
QAExpiry read predicate, sweep exemption, attempt scopingReproduce via exact codesStable
Payment (future)markPaymentStarted/complete/fail contract; own the failure edgesAttempt-scoped transitionsStable

13.5 API Tradeoffs and Rationale

DecisionChosen BehaviorAlternatives ConsideredWhy This TradeoffRiskMitigation
One transactionAtomic everythingSaga/coordinatorNo orphan stateModules must accept DbExecutorAlready true
200-on-repeatSame session409 duplicateDouble-click safeAmbiguityIdentical response
Expiry as predicateAlways correctStored status + flip jobNo stale pay-for-stock windowSweep writes only to record
Sweep exempts gatewayNo unfulfillable chargeSweep everythingStuck sessionsSilent gatewayAdmin force-expire
Attempt-scoped transitionsSafe retriesIdempotency onlyCyclic machineAttempt number
Snapshots everywhereImmutable historyReferencesReceipt integrityStorageAccepted
Admin read-onlyContract integrityAdmin editNo repriced contractsCan't fix errorsCancel/expire only

13.6 API Change Impact

ChangeAffected ConsumersBackend ImpactData ImpactMigration Needed?Compatibility Plan
Payment module landsCheckout clientsCalls markPaymentStarted/complete/failNoneNoContract already frozen + tested

14. Zero-Omission API Checklist

  • Every controller route is documented (§4, §8, §13.1).
  • Every parent route prefix and runtime URL is documented (§2, §4).
  • Every DTO field, enum, default, transform and validator is documented (§6, §7).
  • Every response field and nullable field is documented (§6.4, §8).
  • Every auth, guard, permission and guest identity branch is documented (§5).
  • Every success, validation, not-found, conflict, rate-limit and server-error branch is documented (§8).
  • Every DB read/write, queue job and external call is documented (§11, backend §9).
  • Every route has examples for minimal request, success response and representative failures (§8).
  • Every endpoint family has route, sequence and error diagrams (§9, backend §7).
  • Every tradeoff and compatibility risk is documented (§13.5, §13.6).
  • The API doc links to backend and features/flows (§1, See Also).

15. Integration Checklist

  • Every route from controllers is documented.
  • Every DTO field is documented.
  • Every enum value is documented.
  • Every response envelope is documented.
  • Every error code is documented.
  • Every auth guard and permission is documented.
  • Every cache key, queue job and external call is documented.
  • Every diagram matches the current code.
  • The API doc links to backend and features/flows.

See Also