Payment API Reference
Complete API contracts for the Payment module, including routes, auth, DTOs, responses, errors, examples, and integration notes.
Payment - API Reference
Audience: Frontend engineers, mobile engineers, backend engineers, QA, and API consumers. Scope: The four customer payment routes, the three admin routes, and the gateway-return routes (which are eSewa's, not yours).
1. Documentation Evidence
| Area | Files Inspected | What Was Verified |
|---|---|---|
| Controllers | apps/api/src/modules/payment/customer/payment-customer.controller.ts, admin/payment-admin.controller.ts, gateway-return/gateway-return.controller.ts | Routes, methods, guards, permissions |
| DTOs | dto/*.ts | Validation |
| Services | shared/*.ts | Behavior, transitions, statuses |
| Schema | packages/db/src/schema/payment/*.ts | Six-status enum, flag columns, CHECKs |
| Error registry | apps/api/src/common/types/error-codes.ts (// PAYMENT) | PAYMENT_* codes |
2. Module Summary
| Field | Value |
|---|---|
| Module name | payment |
| Module slug | payment |
| Primary actors | customer, admin, gateway (eSewa) |
| API surfaces | mobile (customer), admin, public (gateway return only) |
| Base route prefixes | /api/mobile/payments, /api/payments, /api/payments/esewa/return |
| Auth model | JwtAuthGuard (customer/admin); public + four defences (gateway return) |
| Persistence | PostgreSQL (payment_attempt, payment_event); no cache |
| Runtime source of truth | payment_attempt rows + the checkout's frozen grand_total |
| Sibling docs | Backend, Features and flows |
3. Concepts and Terminology
| Term | Meaning | Source File | Used By |
|---|---|---|---|
attempt | One row per interaction with a method | schema | All routes |
status | One of six attempt statuses | schema | All routes |
| Business status | One of seven client-facing statuses (awaiting_gateway … already_settled) | attempt service | Responses |
reconciliation_flagged_at | The flag — "a human must look", orthogonal to status | schema | Admin queue |
redirect.fields | The signed form to POST exactly as given | gateway | Start |
retryAfterSeconds | When to poll a processing attempt | attempt service | Poll |
shortVariants | VARIANT public ids that will not be included (stock ran out between checkout freeze and settlement) | completion | Paid |
4. API Surface Map
| Surface | Method | Path | Actor | Auth/Guard | Permission | Controller | Purpose |
|---|---|---|---|---|---|---|---|
| Mobile | GET | /api/mobile/payments/methods | Customer | JWT + IpThrottle | — | PaymentCustomerController | Enabled methods |
| Mobile | POST | /api/mobile/payments | Customer | JWT + IpThrottle | — | same | Start a payment (201/200) |
| Mobile | GET | /api/mobile/payments/:id | Customer | JWT + IpThrottle | — | same | Poll an attempt |
| Mobile | POST | /api/mobile/payments/:id/cancel | Customer | JWT + IpThrottle | — | same | Cancel |
| Gateway | GET/POST | /api/payments/esewa/return/:attemptId/:token/success | Gateway | public | — | GatewayReturnController | eSewa success redirect |
| Gateway | GET/POST | /api/payments/esewa/return/:attemptId/:token/failure | Gateway | public | — | same | eSewa failure redirect |
| Admin | GET | /api/payments | Admin | JWT+Role | Payments_READ | PaymentAdminController | List |
| Admin | GET | /api/payments/:id | Admin | JWT+Role | Payments_READ | same | Timeline |
| Admin | POST | /api/payments/:id/resolve | Admin | JWT+Role | Payments_UPDATE | same | Resolve a flag |
Do not call the gateway-return routes — they exist for eSewa's redirect and carry a per-attempt secret. They appear in neither Swagger document, by design.
5. Auth, Identity, and Permissions
| Surface | Guard/Decorator | Identity Shape | Permission | Guest Allowed | Notes |
|---|---|---|---|---|---|
| Customer | JwtAuthGuard, IpThrottlerGuard | req.user.id | — | No | Ownership-scoped: another customer's attempt is 404, never 403 |
| Admin | JwtAuthGuard, RoleGuard, IpThrottlerGuard | req.user | Payments_READ / Payments_UPDATE | No | |
| Gateway return | public | — | — | n/a | Four defences: per-attempt token (SHA-256 only), HMAC, server-to-server status check, database |
6. DTO and Model Reference
6.1 StartPaymentDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
checkoutId | UUID v7 | Yes | @IsUUID("7") | |
method | enum | Yes | cod | esewa | Unknown or unconfigured → 400 PAYMENT_METHOD_UNAVAILABLE |
6.2 Params DTO
PaymentParamsDto { id } — uuid7.
6.3 ResolvePaymentDto
{ note: string } — 10–2000 characters. Records what a human decided; changes no status, no amount, no outcome.
6.4 Admin query DTO
status, method, customerId, checkoutId, minAmount, maxAmount, awaitingReconciliation (boolean — the queue that matters), sortBy, order, page, size.
6.5 Response DTO — start/poll
{
"id": "0195c4f2-…", "checkoutId": "0195c4f2-…", "method": "esewa",
"status": "awaiting_gateway",
"amount": 245000, "currency": "NPR",
"expiresAt": "…",
"redirect": { "type": "form_post", "url": "https://rc-epay.esewa.com.np/…",
"fields": { "amount": "2450.00", "tax_amount": "0", "total_amount": "2450.00",
"transaction_uuid": "…", "product_code": "EPAYTEST",
"signed_field_names": "total_amount,transaction_uuid,product_code",
"signature": "…" } },
"retryAfterSeconds": null,
"shortVariants": [],
"message": null
}amount is minor units (245000 = NPR 2,450.00). COD returns status: "paid" and redirect: null. GET /:id has the same body with no redirect — it reads recorded state and never re-queries the gateway.
A short order still reports status: "paid", never a failure. The purchase completed and the
customer was charged the agreed amount; shortVariants (renamed from shortProducts — a
breaking rename with no compatibility window, this API carries no version segment) is the list
of VARIANT public ids that will not ship because stock ran out between the checkout freeze and
settlement. message is populated in that case ("Your payment went through, but some items sold
out before it landed and will not be included."). A client still reading the old shortProducts
key gets undefined and must render status: "paid" as a plain, complete success — the customer
is never told an item they paid for is not coming. Check shortVariants.length > 0 on every paid
response, not only on first load.
(apps/api/src/modules/payment/customer/payment-response.builder.ts:26-28,60-64,80-83)
redirect.fieldsis SIGNED. Build a hidden form with one<input>per key and POST it toredirect.urlexactly as given — do not re-encode, round, strip zeros, or drop "unused" fields. eSewa's HMAC covers those exact strings; any edit fails verification, which the customer experiences as their money leaving and no order appearing.
7. Enum Reference
| Enum | Value | Meaning | Runtime Effect | Source |
|---|---|---|---|---|
payment_method | cod / esewa | The method | Registry decides availability | enums.ts |
payment_attempt_status | initiated / pending_verification / succeeded / failed / cancelled / expired | The attempt | Six values — there is no "needs a human" status; that is the flag | |
payment_reconciliation_reason | checkout_refused / attempt_superseded / amount_mismatch / late_confirmation / … | Why a human must look | The flag's reason |
Business statuses (response-level, seven): awaiting_gateway, processing, paid, failed_retryable, failed_final, cancelled, already_settled.
8. Endpoint Reference
8.1 GET /api/mobile/payments/methods
Enabled methods for this deployment — call it rather than hard-coding: a deployment without eSewa credentials returns only COD. [{ method, label, description, requiresRedirect }].
8.2 POST /api/mobile/payments
Purpose
Start a payment for a checkout. 201 for a new attempt, 200 when one was already in flight — both carry the same body; neither is an error.
Request
{ "checkoutId": "0195c4f2-…", "method": "esewa" }Response
Attempt shape per §6.5 — awaiting_gateway + the signed redirect.fields for eSewa; paid + redirect: null for COD.
Error Cases
| HTTP | Code | Condition |
|---|---|---|
| 400 | PAYMENT_METHOD_UNAVAILABLE | Unknown method, or not configured here |
| 404 | CHECKOUT_SESSION_NOT_FOUND | Unknown checkout, or another customer's |
| 409 | CHECKOUT_SESSION_EXPIRED | The checkout lapsed before payment started |
| 409 | PAYMENT_ATTEMPT_ALREADY_LIVE | A payment is in flight with a different method — offer cancel-then-retry |
| 409 | PAYMENT_VERIFICATION_PENDING | Gateway unanswered about the previous attempt — do not offer a retry; poll |
| 409 | PAYMENT_ALREADY_SETTLED | The checkout is already paid and being finalised — show success, not an error; poll |
8.3 GET /api/mobile/payments/:id
Poll after a redirect. Reads recorded state, never re-queries the gateway — repeated calls are free. 404 PAYMENT_ATTEMPT_NOT_FOUND (unknown or another customer's).
8.4 POST /api/mobile/payments/:id/cancel
Cancel the attempt. 404 as above; 409 PAYMENT_ATTEMPT_NOT_CANCELLABLE (already paid — show success); 409 CHECKOUT_PAYMENT_IN_PROGRESS (raised by checkout — poll).
8.5 Gateway return — GET/POST /api/payments/esewa/return/:attemptId/:token/success-or-failure
eSewa's routes, not the storefront's. Public, defended by the four layers (token hash, HMAC over the declared field set, server-to-server status check, database). The token is in the URL path — redactUrlSecrets keeps it out of the logs. On completion the browser is redirected to {FRONTEND_BASE_URL}{PAYMENT_RESULT_PATH}?status=…&payment={attemptId}&checkout={checkoutId}.
Treat the query string as a hint, not truth — it is unauthenticated and editable. Render from GET /api/mobile/payments/:id.
8.6 GET /api/payments (admin)
Paginated list with the filters of §6.4. awaitingReconciliation=true is the queue that matters — payments where money moved and no sale was recorded. Payments_READ.
8.7 GET /api/payments/:id (admin)
The attempt plus timeline — every transition and every gateway message, oldest first, append-only, payloads already redacted. The row says where a payment ended; the timeline says how it got there. Payments_READ; 404 PAYMENT_ATTEMPT_NOT_FOUND.
8.8 POST /api/payments/:id/resolve (admin)
{ "note": "Refunded NPR 2450 via the eSewa merchant portal, ref 991122." }10–2000 chars. Records what a human decided; changes no status, no amount, no outcome; takes the case out of the queue. 400 PAYMENT_RESOLUTION_NOT_APPLICABLE when not flagged or already resolved by someone else.
There is deliberately no admin route to mark a payment paid, edit an amount, retry a charge or refund one — marking paid is refused by a database constraint, not merely absent.
9. Flow Diagrams
9.1 Route Ownership
9.2 Request Sequence (start eSewa)
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/payments | offset page/size | 20 | 100 | sortBy (admin-defined) | status, method, customerId, checkoutId, minAmount, maxAmount, awaitingReconciliation | — |
Customer endpoints are single-attempt (never paginated).
11. Caching, Jobs, and External Integrations
| Integration | Used? | Details |
|---|---|---|
| Redis cache | No — payment caches nothing | |
| BullMQ | Yes | payment queue — verification re-asks (bounded backoff), completion (COMPLETE_SETTLED_CHECKOUT via outbox), expiry sweep |
| External API | Yes | eSewa (form post + status API) via the gateway port; COD has no network call |
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? |
|---|---|---|---|---|---|---|---|---|---|---|
GET /api/mobile/payments/methods | methods | — | registry | JWT+IpThrottle | — | — | — | — | — | Yes |
POST /api/mobile/payments | start | StartPaymentDto | PaymentAttemptService.start | JWT+IpThrottle | — | — | — | checkout, attempts | 400/404/409 | Yes |
GET /api/mobile/payments/:id | get | params DTO | …get | JWT+IpThrottle | — | — | — | attempts | 404 | Yes |
POST /api/mobile/payments/:id/cancel | cancel | params DTO | …cancel | JWT+IpThrottle | — | — | — | attempts | 404/409 | Yes |
| `GET | POST /api/payments/esewa/return/:id/:token/success-or-failure` | 4 methods | — | PaymentSettlementService.applyOutcome | public | — | — | outbox → complete | attempts, events | — |
GET /api/payments | findAll | query DTO | PaymentAdminService.list | JWT+Role+IpThrottle | Payments_READ | — | — | attempts | — | Yes |
GET /api/payments/:id | findById | params DTO | …findOne | JWT+Role+IpThrottle | Payments_READ | — | — | attempts, events | 404 | Yes |
POST /api/payments/:id/resolve | resolve | ResolvePaymentDto | PaymentReconciliationService.resolve | JWT+Role+IpThrottle | Payments_UPDATE | — | — | attempts | 400/404 | Yes |
13.2 Request/Response Exhaustiveness
Covered in §8: minimal start request (§6.1/8.2), the signed-redirect response (§6.5), the 201-vs-200 double-submit (§8.2), the two refusal-to-double-charge 409s and how the UI must treat them as non-failures (§8.2, feature §5.1), COD's instant-paid shape (§6.5), the result-page hint-not-truth rule (§8.5), domain errors per endpoint (§8 error tables).
13.3 API Diagram Pack
Route ownership (§9.1), request sequence (§9.2, backend §7.1), error decision tree (§9.3), verify-and-complete flow (backend §7.1).
13.4 Consumer Integration Notes
| Consumer | Required Knowledge | Failure Handling | Contract Stability |
|---|---|---|---|
| Web frontend | Seven business statuses; signed form POSTed exactly; query string = hint | PAYMENT_ALREADY_SETTLED = success; PAYMENT_VERIFICATION_PENDING = poll, never retry | Stable |
| Mobile app | retryAfterSeconds polling; methods list not hard-coded | 409s per §8.2 | Stable |
| Admin panel | awaitingReconciliation queue; resolve-note semantics | PAYMENT_RESOLUTION_NOT_APPLICABLE → refresh | Stable |
| QA | Reconciliation flag vs status; six enum values; replay-proof amount CHECK | Reproduce via exact codes | Stable |
| Order (future) | A paid payment → checkout completed → order | shortVariants for shortfalls (VARIANT public ids, renamed from shortProducts) | Stable |
13.5 API Tradeoffs and Rationale
| Decision | Chosen Behavior | Alternatives Considered | Why This Tradeoff | Risk | Mitigation |
|---|---|---|---|---|---|
No payment table | One amount | Second copy | Two copies disagree = wrong charge | — | CHECKs |
| Amount-carrying outcome | Replay unwritable | Boolean success | Structural proof | — | Port + CHECK |
| Flag not status | Outcome survives | Seven-status enum | succeeded_at + index integrity | Two columns | Documented |
| Public return routes | eSewa's redirect | Bearer auth | Gateway has no token | Forgery | Four defences |
| No admin mutations | Constraint-refused | Admin override | No repriced charges | Support burden | Resolve note |
| Short transactions | No locks over HTTP | One tx | Network calls | Partial states | Attempt-scoped idempotent writes |
13.6 API Change Impact
| Change | Affected Consumers | Backend Impact | Data Impact | Migration Needed? | Compatibility Plan |
|---|---|---|---|---|---|
| New gateway | None | Port implementation | None | No | Registry-gated |
| Order module lands | Payment clients | Consumes paid | None | No | Contract frozen |
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.5, §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/payment/backend
- Features and flows doc: /docs/developer/payment/feature
- TDD: not yet published