Happy House - Ecommerce Docs
Developer ResourcesPayment

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

AreaFiles InspectedWhat Was Verified
Controllersapps/api/src/modules/payment/customer/payment-customer.controller.ts, admin/payment-admin.controller.ts, gateway-return/gateway-return.controller.tsRoutes, methods, guards, permissions
DTOsdto/*.tsValidation
Servicesshared/*.tsBehavior, transitions, statuses
Schemapackages/db/src/schema/payment/*.tsSix-status enum, flag columns, CHECKs
Error registryapps/api/src/common/types/error-codes.ts (// PAYMENT)PAYMENT_* codes

2. Module Summary

FieldValue
Module namepayment
Module slugpayment
Primary actorscustomer, admin, gateway (eSewa)
API surfacesmobile (customer), admin, public (gateway return only)
Base route prefixes/api/mobile/payments, /api/payments, /api/payments/esewa/return
Auth modelJwtAuthGuard (customer/admin); public + four defences (gateway return)
PersistencePostgreSQL (payment_attempt, payment_event); no cache
Runtime source of truthpayment_attempt rows + the checkout's frozen grand_total
Sibling docsBackend, Features and flows

3. Concepts and Terminology

TermMeaningSource FileUsed By
attemptOne row per interaction with a methodschemaAll routes
statusOne of six attempt statusesschemaAll routes
Business statusOne of seven client-facing statuses (awaiting_gatewayalready_settled)attempt serviceResponses
reconciliation_flagged_atThe flag — "a human must look", orthogonal to statusschemaAdmin queue
redirect.fieldsThe signed form to POST exactly as givengatewayStart
retryAfterSecondsWhen to poll a processing attemptattempt servicePoll
shortVariantsVARIANT public ids that will not be included (stock ran out between checkout freeze and settlement)completionPaid

4. API Surface Map

SurfaceMethodPathActorAuth/GuardPermissionControllerPurpose
MobileGET/api/mobile/payments/methodsCustomerJWT + IpThrottlePaymentCustomerControllerEnabled methods
MobilePOST/api/mobile/paymentsCustomerJWT + IpThrottlesameStart a payment (201/200)
MobileGET/api/mobile/payments/:idCustomerJWT + IpThrottlesamePoll an attempt
MobilePOST/api/mobile/payments/:id/cancelCustomerJWT + IpThrottlesameCancel
GatewayGET/POST/api/payments/esewa/return/:attemptId/:token/successGatewaypublicGatewayReturnControllereSewa success redirect
GatewayGET/POST/api/payments/esewa/return/:attemptId/:token/failureGatewaypublicsameeSewa failure redirect
AdminGET/api/paymentsAdminJWT+RolePayments_READPaymentAdminControllerList
AdminGET/api/payments/:idAdminJWT+RolePayments_READsameTimeline
AdminPOST/api/payments/:id/resolveAdminJWT+RolePayments_UPDATEsameResolve 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

SurfaceGuard/DecoratorIdentity ShapePermissionGuest AllowedNotes
CustomerJwtAuthGuard, IpThrottlerGuardreq.user.idNoOwnership-scoped: another customer's attempt is 404, never 403
AdminJwtAuthGuard, RoleGuard, IpThrottlerGuardreq.userPayments_READ / Payments_UPDATENo
Gateway returnpublicn/aFour defences: per-attempt token (SHA-256 only), HMAC, server-to-server status check, database

6. DTO and Model Reference

6.1 StartPaymentDto

FieldTypeRequiredValidationNotes
checkoutIdUUID v7Yes@IsUUID("7")
methodenumYescod | esewaUnknown 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.fields is SIGNED. Build a hidden form with one <input> per key and POST it to redirect.url exactly 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

EnumValueMeaningRuntime EffectSource
payment_methodcod / esewaThe methodRegistry decides availabilityenums.ts
payment_attempt_statusinitiated / pending_verification / succeeded / failed / cancelled / expiredThe attemptSix values — there is no "needs a human" status; that is the flag
payment_reconciliation_reasoncheckout_refused / attempt_superseded / amount_mismatch / late_confirmation / …Why a human must lookThe 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

HTTPCodeCondition
400PAYMENT_METHOD_UNAVAILABLEUnknown method, or not configured here
404CHECKOUT_SESSION_NOT_FOUNDUnknown checkout, or another customer's
409CHECKOUT_SESSION_EXPIREDThe checkout lapsed before payment started
409PAYMENT_ATTEMPT_ALREADY_LIVEA payment is in flight with a different method — offer cancel-then-retry
409PAYMENT_VERIFICATION_PENDINGGateway unanswered about the previous attempt — do not offer a retry; poll
409PAYMENT_ALREADY_SETTLEDThe 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)

EndpointPagination TypeDefault SizeMax SizeSort FieldsFiltersResult Cap
GET /api/paymentsoffset page/size20100sortBy (admin-defined)status, method, customerId, checkoutId, minAmount, maxAmount, awaitingReconciliation

Customer endpoints are single-attempt (never paginated).

11. Caching, Jobs, and External Integrations

IntegrationUsed?Details
Redis cacheNo — payment caches nothing
BullMQYespayment queue — verification re-asks (bounded backoff), completion (COMPLETE_SETTLED_CHECKOUT via outbox), expiry sweep
External APIYeseSewa (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

RouteController MethodDTOsService MethodGuardsPermissionsCacheJobsDB TouchesErrorsDocumented?
GET /api/mobile/payments/methodsmethodsregistryJWT+IpThrottleYes
POST /api/mobile/paymentsstartStartPaymentDtoPaymentAttemptService.startJWT+IpThrottlecheckout, attempts400/404/409Yes
GET /api/mobile/payments/:idgetparams DTO…getJWT+IpThrottleattempts404Yes
POST /api/mobile/payments/:id/cancelcancelparams DTO…cancelJWT+IpThrottleattempts404/409Yes
`GETPOST /api/payments/esewa/return/:id/:token/success-or-failure`4 methodsPaymentSettlementService.applyOutcomepublicoutbox → completeattempts, events
GET /api/paymentsfindAllquery DTOPaymentAdminService.listJWT+Role+IpThrottlePayments_READattemptsYes
GET /api/payments/:idfindByIdparams DTO…findOneJWT+Role+IpThrottlePayments_READattempts, events404Yes
POST /api/payments/:id/resolveresolveResolvePaymentDtoPaymentReconciliationService.resolveJWT+Role+IpThrottlePayments_UPDATEattempts400/404Yes

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

ConsumerRequired KnowledgeFailure HandlingContract Stability
Web frontendSeven business statuses; signed form POSTed exactly; query string = hintPAYMENT_ALREADY_SETTLED = success; PAYMENT_VERIFICATION_PENDING = poll, never retryStable
Mobile appretryAfterSeconds polling; methods list not hard-coded409s per §8.2Stable
Admin panelawaitingReconciliation queue; resolve-note semanticsPAYMENT_RESOLUTION_NOT_APPLICABLE → refreshStable
QAReconciliation flag vs status; six enum values; replay-proof amount CHECKReproduce via exact codesStable
Order (future)A paid payment → checkout completed → ordershortVariants for shortfalls (VARIANT public ids, renamed from shortProducts)Stable

13.5 API Tradeoffs and Rationale

DecisionChosen BehaviorAlternatives ConsideredWhy This TradeoffRiskMitigation
No payment tableOne amountSecond copyTwo copies disagree = wrong chargeCHECKs
Amount-carrying outcomeReplay unwritableBoolean successStructural proofPort + CHECK
Flag not statusOutcome survivesSeven-status enumsucceeded_at + index integrityTwo columnsDocumented
Public return routeseSewa's redirectBearer authGateway has no tokenForgeryFour defences
No admin mutationsConstraint-refusedAdmin overrideNo repriced chargesSupport burdenResolve note
Short transactionsNo locks over HTTPOne txNetwork callsPartial statesAttempt-scoped idempotent writes

13.6 API Change Impact

ChangeAffected ConsumersBackend ImpactData ImpactMigration Needed?Compatibility Plan
New gatewayNonePort implementationNoneNoRegistry-gated
Order module landsPayment clientsConsumes paidNoneNoContract 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