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 five 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 |
readiness | On the quote only — ready/blockingReasons in place of a 409, so a client can render a disabled state with a reason | checkout-quote.service.ts | Quote |
expectedGrandTotal | The figure the customer was shown; the pay route refuses when the server computes a different one | checkout-session.service.ts | Pay |
4. API Surface Map
| Surface | Method | Path | Actor | Auth/Guard | Permission | Controller | Purpose |
|---|---|---|---|---|---|---|---|
| Mobile | POST | /api/mobile/checkout/quote | Customer | JWT + IpThrottle | — | CheckoutCustomerController | Price the basket, writes nothing |
| Mobile | POST | /api/mobile/checkout | Customer | JWT + IpThrottle | — | same | Start checkout |
| Mobile | POST | /api/mobile/checkout/pay | Customer | JWT + IpThrottle | — | same | Freeze the basket and start paying, in one request |
| 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_QUOTE 40/min account-keyed (quote — writes nothing), CUSTOMER_CHECKOUT_ATTEMPT 10/min account-keyed (start — a checkout is a money-bearing attempt), CUSTOMER_CHECKOUT_PAY 10/min account-keyed (pay — freezes the basket and reaches a gateway), 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.1a QuoteCheckoutDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
fulfilmentType | delivery | pickup | No | @IsIn | Defaults to delivery |
addressId | UUID v7 | No | @IsUUID("7") | Required for a delivery, refused for a pickup |
couponCode | string | No | trimmed + upper-cased, promotion module's min/max length | Compared against a stored code, unlike StartCheckoutDto's |
No cartVersion — a quote writes nothing, so there is no lost update to guard, and requiring it
would fail the first quote a client makes, before it has read the cart at all.
6.1b PayCheckoutDto
The same four fields StartCheckoutDto carries, plus:
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
method | payment method enum | Yes | @IsIn | How the customer is paying |
expectedGrandTotal | integer, minor units | Yes | @IsInt, @Min(0) | The total the customer was shown. The request is refused if the server computes a different one. @Min(0), not @Min(1) — a fully discounted pickup basket legitimately costs nothing |
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.
6.4a Quote response — CheckoutQuoteResponseDto
{
"pricing": { "currency": "NPR", "subtotal": 89000, "discountAmount": 5000,
"shippingAmount": 15000, "shippingDiscountAmount": 0, "grandTotal": 99000 },
"shipping": { "fulfilmentType": "delivery", "serviceable": true, "fee": 15000,
"districtName": "Kathmandu" },
"coupon": { "code": "SAVE20", "applied": true, "discountAmount": 5000,
"shippingDiscountAmount": 0, "message": "…" },
"readiness": { "ready": true, "blockingReasons": [], "liveSessionId": null,
"liveSessionStatus": null }
}coupon is null when the request carried no coupon code. shipping.fee is null exactly when
shipping.serviceable is false; shipping.districtName is always null for a pickup.
readiness.ready is exactly readiness.blockingReasons.length === 0. readiness.liveSessionId /
liveSessionStatus are non-null when the customer already has a live session, so a client can offer
a release rather than showing a blocked cart with no action.
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 | |
readiness.blockingReasons (quote) | empty / cart_locked / items_unavailable / insufficient_stock / address_not_serviceable | Why this basket cannot be committed yet | Its own vocabulary, one wider than the cart's — the cart has no address to be unserviceable | checkout.constants.ts (CHECKOUT_QUOTE_BLOCKING_REASONS) |
8. Endpoint Reference
8.0a POST /api/mobile/checkout/quote
Purpose
Price the basket for a chosen address and coupon without writing anything — no session, no
stock hold, no promotion hold, no cart lock, no transaction. Everything the create path THROWS for
as a business refusal (unserviceable district, inapplicable coupon, locked cart, unavailable items)
comes back here as readiness and coupon data instead, so a client can show a disabled state with
a sentence rather than an error. POST for the body, not for the effect — a coupon code does not
belong in a query string, and the response must never be cached, since it is a price.
Auth and Permissions
JwtAuthGuard; CUSTOMER_CHECKOUT_QUOTE 40/min (account-keyed) — four times the pay budget, because
this writes nothing, holds nothing and takes no lock.
Request
{ "fulfilmentType": "delivery", "addressId": "0198f2c1-…", "couponCode": "SAVE20" }All fields optional; fulfilmentType defaults to delivery.
Response
200. Shape per §6.4a.
Side Effects
None.
Error Cases
What it still refuses is malformed or unauthorised input, not a business condition — those come back
as readiness/coupon data with a 200.
| HTTP | Code | Condition |
|---|---|---|
| 400 | CHECKOUT_ADDRESS_NOT_ALLOWED_FOR_PICKUP | Pickup carrying an address |
| 400 | CHECKOUT_ADDRESS_REQUIRED_FOR_DELIVERY | Delivery carrying no address |
| 404 | CHECKOUT_ADDRESS_NOT_FOUND | The address is unknown, archived, or not theirs |
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.1a POST /api/mobile/checkout/pay
Purpose
Freeze the basket and start the payment, in one request. Revalidates the cart, holds the stock,
freezes the amount payable and starts the payment. This is what closes the gap the old two-step flow
left open: POST /checkout used to be fired from the review step, minutes before payment, so the
cart sat locked and stock held while the customer was still deciding how to pay. Both routes still
exist and still work — this route exists so the freeze and the payment start happen together.
Auth and Permissions
JwtAuthGuard; CUSTOMER_CHECKOUT_PAY 10/min (account-keyed).
Request
The StartCheckoutDto fields plus method and expectedGrandTotal — DTO per §6.1b.
{ "addressId": "0198f2c1-…", "couponCode": "SAVE20", "method": "esewa", "expectedGrandTotal": 99000 }Response
200 — PaymentResponseDto, the same shape POST /api/payments returns.
Side Effects
The create transaction's usual holds (inventory, promotion, session, snapshot rows), plus starting
the payment attempt. No compensating write on a payment-start failure: create refuses inside
its own transaction and rolls back cleanly when the total or the destination does not match, leaving
no session, no hold and an untouched cart. A failure inside the payment start is repaired by
PaymentAttemptService itself, which returns the session to pending_payment with its holds intact
so the customer can press Pay again — see backend §6.x PaymentAttemptService.
Error Cases
| HTTP | Code | Condition |
|---|---|---|
| 400 | validation | Unknown property, or a pickup/delivery address mismatch |
| 404 | CHECKOUT_ADDRESS_NOT_FOUND | Unknown, archived, or not theirs |
| 409 | CHECKOUT_TOTAL_CHANGED | The server's computed total differs from expectedGrandTotal — rolled back, nothing held |
| 409 | CHECKOUT_SESSION_MISMATCH | A live session already exists for a different address/total/cart version than this request asked for |
| 409 | CHECKOUT_CART_EMPTY / CHECKOUT_CART_CHANGED / CHECKOUT_CART_NOT_READY | Same conditions as POST /checkout |
| 409 | CHECKOUT_PRODUCT_UNAVAILABLE / CHECKOUT_INSUFFICIENT_STOCK | Same conditions as POST /checkout |
| 409 | CHECKOUT_ADDRESS_NOT_SERVICEABLE | No delivery to that district |
| 409 | CHECKOUT_COUPON_NOT_APPLICABLE | Retry without the coupon is a valid recovery |
| 409 | CHECKOUT_PAYMENT_IN_PROGRESS | A payment for this account is already at a gateway. If the resumed session's fulfilment/address/cart-version/total match this request, it is handed to the payment module to heal or refuse; otherwise this code is returned |
CHECKOUT_SESSION_MISMATCH exists specifically for two tabs pressing Pay with different addresses:
equal totals do not make it safe to resume the other tab's session, because two addresses in the
same district can cost the same and are not the same place.
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/quote | quote | QuoteCheckoutDto | CheckoutQuoteService.quote | JWT+IpThrottle | — | — | — | cart, address, promotion (reads only) | 400/404 | Yes |
POST /api/mobile/checkout | create | CreateCheckoutDto | CheckoutSessionService.create | JWT+IpThrottle | — | — | — | cart, address, inventory, promotion, session + snapshots | 400/404/409 (11 codes) | Yes |
POST /api/mobile/checkout/pay | pay | PayCheckoutDto | CheckoutPayService.pay → CheckoutSessionService.create → PaymentCustomerService.start | JWT+IpThrottle | — | — | — | cart, address, inventory, promotion, session + snapshots, payment attempt | 400/404/409 (13 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 |
| Quote writes nothing | No session, no holds, no lock, no transaction | Reuse create and release on cancel | Sidebar render never enters the reservation lock convoy | Quote and commit can disagree on a promotion right at its usage limit | Commit path re-checks and refuses inside its own transaction |
| Pay is one route, not two | create + payment start in one request | Keep the two-step flow | Closes the gap where a locked cart sat waiting on payment method choice | Total can move between quote and press | expectedGrandTotal refuses a mismatch and rolls back |
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