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
| Area | Files Inspected | What Was Verified |
|---|---|---|
| Controllers | apps/api/src/modules/checkout/customer/checkout-customer.controller.ts, admin/checkout-admin.controller.ts | Routes, methods, guards, permissions, rate limits |
| DTOs | dto/*.ts | Validation, strict unknown-property rejection |
| Services | shared/checkout-session.service.ts, checkout-payment.service.ts, checkout-release.service.ts | Behavior, errors |
| Schema | packages/db/src/schema/checkout/*.ts | Totals CHECK, partial uniques, snapshots |
| Error registry | apps/api/src/common/types/error-codes.ts (// CHECKOUT) | CHECKOUT_* codes |
2. Module Summary
| Field | Value |
|---|---|
| Module name | checkout |
| Module slug | checkout |
| Primary actors | customer, admin, payment (future, internal) |
| API surfaces | mobile (customer), admin |
| Base route prefixes | /api/mobile/checkout, /api/checkout/sessions |
| Auth model | JwtAuthGuard (customer); JwtAuthGuard + RoleGuard (admin) |
| Persistence | PostgreSQL (three session tables + snapshots); no cache |
| Runtime source of truth | checkout_session rows + live inventory/promotion ledgers |
| Sibling docs | Backend, Features and flows |
3. Concepts and Terminology
| Term | Meaning | Source File | Used By |
|---|---|---|---|
attempt | The payment-attempt handle; every payment-side transition is scoped to it | schema | Payment contract |
expiresAt / expiresInSeconds | Session expiry; a read predicate, never a stored status | schema | All routes |
status | pending_payment / payment_in_progress / completed / cancelled / expired | schema | All routes |
grandTotal | subtotal - discount + shipping - shippingDiscount — database-enforced | schema | Response |
reserved | false on a line = untracked product, nothing to hold — normal | session service | Items |
priceChange | Warning, not a blocker — the cart's price vs the frozen price | session service | Items |
cartVersion | Optional optimistic token for the basket you displayed | session service | Start |
4. API Surface Map
| Surface | Method | Path | Actor | Auth/Guard | Permission | Controller | Purpose |
|---|---|---|---|---|---|---|---|
| Mobile | POST | /api/mobile/checkout | Customer | JWT + IpThrottle | — | CheckoutCustomerController | Start checkout |
| Mobile | GET | /api/mobile/checkout/active | Customer | JWT + IpThrottle | — | same | My live session |
| Mobile | POST | /api/mobile/checkout/:id/cancel | Customer | JWT + IpThrottle | — | same | Cancel my pending session |
| Admin | GET | /api/checkout/sessions | Admin | JWT+Role | Checkout_READ | CheckoutAdminController | List sessions |
| Admin | GET | /api/checkout/sessions/:id | Admin | JWT+Role | Checkout_READ | same | Session detail |
| Admin | POST | /api/checkout/sessions/:id/cancel | Admin | JWT+Role | Checkout_UPDATE | same | Cancel (fraud/duplicate) |
| Admin | POST | /api/checkout/sessions/:id/expire | Admin | JWT+Role | Checkout_UPDATE | same | Force-expire (stuck gateway) |
{id} is always a uuid7 public_id; no integer PK is exposed.
5. Auth, Identity, and Permissions
| Surface | Guard/Decorator | Identity Shape | Permission | Guest Allowed | Notes |
|---|---|---|---|---|---|
| Customer | JwtAuthGuard, IpThrottlerGuard | req.user.id | — | No | CUSTOMER_CHECKOUT_ATTEMPT 10/min account-keyed (start — a checkout is a money-bearing attempt), CUSTOMER_READ 60/min (active), CUSTOMER_WRITE 20/min (cancel) |
| Admin | JwtAuthGuard, RoleGuard, IpThrottlerGuard | req.user | Checkout_READ / Checkout_UPDATE | No | ADMIN_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
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
addressId | UUID v7 | Yes | @IsUUID("7") | |
couponCode | string | No | 3–64 chars | |
cartVersion | number | No | >= 1 | Send 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
| Enum | Value | Meaning | Runtime Effect | Source |
|---|---|---|---|---|
checkout_session_status | pending_payment | Created; holds taken | Cancellable; payment may start | enums.ts |
checkout_session_status | payment_in_progress | At the gateway | Sweep-exempt; not cancellable from the app | |
checkout_session_status | completed / cancelled / expired | Terminal | expired 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
| HTTP | Code | Condition |
|---|---|---|
| 400 | validation | Unknown property sent (rejected, not stripped) |
| 404 | CHECKOUT_ADDRESS_NOT_FOUND | Unknown, archived, or not theirs |
| 409 | CHECKOUT_CART_EMPTY | No cart or no lines |
| 409 | CHECKOUT_CART_CHANGED | cartVersion stale |
| 409 | CHECKOUT_CART_NOT_READY | Blocking reasons — details.blockingReasons |
| 409 | CHECKOUT_PRODUCT_UNAVAILABLE | details.unavailableVariantIds — VARIANT public ids, not product ids: two configurations of one product can have only one unavailable |
| 409 | CHECKOUT_INSUFFICIENT_STOCK | details.shortfalls[] — {productId, variantId, requested, available}; stock is held per variant, so variantId is the identifier that matters |
| 409 | CHECKOUT_ADDRESS_NOT_SERVICEABLE | No delivery to that district |
| 409 | CHECKOUT_COUPON_NOT_APPLICABLE | details.reason — retry 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
- A repeat POST returns the SAME session with a 200, not an error — no button guard needed.
expiresInSecondsis server-computed — run the countdown from it, never from the device clock. At zero, re-fetch: the session reportsstatus: "expired".priceChange.changedis a warning, not a blocker — show "the price of X changed from A to B"; the customer has not paid yet.reserved: falseon a line is normal — the product is not stock-tracked; nothing to hold.statusis already correct for expiry — never compareexpiresAtyourself; a session past expiry readsexpiredregardless 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
| HTTP | Code | Condition |
|---|---|---|
| 404 | CHECKOUT_SESSION_NOT_FOUND | Unknown session, or not theirs (same code for both) |
| 409 | CHECKOUT_SESSION_EXPIRED | It lapsed — offer to start again |
| 409 | CHECKOUT_SESSION_NOT_CANCELLABLE | Already terminal — refresh |
| 409 | CHECKOUT_PAYMENT_IN_PROGRESS | A 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)
10. Pagination, Sorting, Filtering, and Search
| Endpoint | Pagination Type | Default Size | Max Size | Sort Fields | Filters | Result Cap |
|---|---|---|---|---|---|---|
GET /api/checkout/sessions | offset page/size | 20 | 100 | createdAt, updatedAt, expiresAt, grandTotal | status, customerId, createdFrom, createdTo, minTotal, maxTotal | — |
Customer endpoints are single-session (never paginated).
11. Caching, Jobs, and External Integrations
| Integration | Used? | Details |
|---|---|---|
| Redis cache | No — checkout caches nothing | Every read is live or frozen |
| BullMQ | Yes | checkout queue — the expiry sweep, enqueued by a plain cron scheduler (no outbox row, no accompanying DB write) |
| External API | No | Payment is a separate module, not yet built |
13. Mandatory Deep API Documentation Pack
13.1 Route-by-Route Completeness Matrix
| Route | Controller Method | DTOs | Service Method | Guards | Permissions | Cache | Jobs | DB Touches | Errors | Documented? |
|---|---|---|---|---|---|---|---|---|---|---|
POST /api/mobile/checkout | start | StartCheckoutDto | CheckoutSessionService.start | JWT+IpThrottle | — | — | — | cart, address, inventory, promotion, session + snapshots | 400/404/409 (11 codes) | Yes |
GET /api/mobile/checkout/active | getActive | — | …getActive | JWT+IpThrottle | — | — | — | sessions | — | Yes |
POST /:id/cancel | cancel | params DTO | …cancel | JWT+IpThrottle | — | — | — | session + releases | 404/409 | Yes |
GET /api/checkout/sessions | findAll | query DTO | CheckoutAdminService.list | JWT+Role+IpThrottle | Checkout_READ | — | — | sessions | — | Yes |
GET /api/checkout/sessions/:id | findById | params DTO | …findOne | JWT+Role+IpThrottle | Checkout_READ | — | — | session | 404 | Yes |
POST /:id/cancel (admin) | cancel | params DTO | CheckoutReleaseService.releaseAndClose | JWT+Role+IpThrottle | Checkout_UPDATE | — | — | session + releases + audit | 404/409 | Yes |
POST /:id/expire | expire | params DTO | …releaseAndClose | JWT+Role+IpThrottle | Checkout_UPDATE | — | — | session + releases + audit | 404/409 | Yes |
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
| Consumer | Required Knowledge | Failure Handling | Contract Stability |
|---|---|---|---|
| Web frontend | 200-on-repeat, server expiresInSeconds, priceChange warning, reserved:false normal | 409 codes → specific UI; coupon retry-without is valid | Stable |
| Mobile app | CUSTOMER_CHECKOUT_ATTEMPT 10/min; countdown from server | 429 → back off; re-fetch at zero | Stable |
| Admin panel | Cancel vs expire semantics; read-only otherwise | 409 NOT_CANCELLABLE / PAYMENT_IN_PROGRESS → refresh | Stable |
| QA | Expiry read predicate, sweep exemption, attempt scoping | Reproduce via exact codes | Stable |
| Payment (future) | markPaymentStarted/complete/fail contract; own the failure edges | Attempt-scoped transitions | Stable |
13.5 API Tradeoffs and Rationale
| Decision | Chosen Behavior | Alternatives Considered | Why This Tradeoff | Risk | Mitigation |
|---|---|---|---|---|---|
| One transaction | Atomic everything | Saga/coordinator | No orphan state | Modules must accept DbExecutor | Already true |
| 200-on-repeat | Same session | 409 duplicate | Double-click safe | Ambiguity | Identical response |
| Expiry as predicate | Always correct | Stored status + flip job | No stale pay-for-stock window | — | Sweep writes only to record |
| Sweep exempts gateway | No unfulfillable charge | Sweep everything | Stuck sessions | Silent gateway | Admin force-expire |
| Attempt-scoped transitions | Safe retries | Idempotency only | Cyclic machine | — | Attempt number |
| Snapshots everywhere | Immutable history | References | Receipt integrity | Storage | Accepted |
| Admin read-only | Contract integrity | Admin edit | No repriced contracts | Can't fix errors | Cancel/expire only |
13.6 API Change Impact
| Change | Affected Consumers | Backend Impact | Data Impact | Migration Needed? | Compatibility Plan |
|---|---|---|---|---|---|
| Payment module lands | Checkout clients | Calls markPaymentStarted/complete/fail | None | No | Contract 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
- Backend doc: /docs/developer/checkout/backend
- Features and flows doc: /docs/developer/checkout/feature
- TDD: not yet published