Payment Module Architecture
Gateway registry, Stripe webhook-first finalization, and order queue integration for checkout and buy-now.
Payment Module Architecture
Audience: Backend developers, frontend integrators Scope: Payment runtime boundaries, gateway contracts, Stripe + legacy gateways
Overview
The payment module (apps/api/src/modules/payment) owns payment initiation and payment status truth.
Registered gateways:
stripe(preferred)esewafonepayconnect_ipscod(Cash on Delivery — no redirect, pending collection)
Current consumer:
- Order checkout and buy-now (
/api/orders/*,/api/mobile/orders/*)
Runtime Flow
Finalization Model
- Source of payment truth for Stripe is webhook-first.
- Redirect endpoints are retained for browser UX and idempotent reconciliation.
- Existing order queue contracts remain unchanged:
payment.completed->order.payment_successpayment.failed->order.payment_failed
Frontend Contract
Frontend keeps a single initiation pattern:
- Call checkout/buy-now once.
- Use
initiationType+redirectUrl+gatewayPayloadto send user to gateway. - Do not call manual verify endpoints.
COD exception: For
initiationType: "cod", no gateway redirect occurs. Frontend navigates toreturnUrldirectly after receiving the response. The order remainsorderStatus = "payment_pending"withpaymentStatus = "cod_pending"until admin cash collection setspaymentStatus = "cod_collected".
File Map
apps/api/src/modules/payment/payment.module.tsapps/api/src/modules/payment/payment.service.tsapps/api/src/modules/payment/controllers/payment-redirect.controller.tsapps/api/src/modules/payment/controllers/stripe-webhook.controller.tsapps/api/src/modules/payment/gateways/stripe.gateway.tspackages/db/src/schema/payment.ts
POS Direct Payment Consumer
The POS module (apps/api/src/modules/pos/) is a direct consumer of PaymentService that bypasses the gateway redirect flow entirely.
POS payment flow (in-store, synchronous):
PosPaymentServicecallsPaymentService.createPaymentRecord(orderId, "order", customerId, amountNpr, gateway)to create a payment record.PosPaymentServicecallsPaymentService.markPaymentCompleted(paymentId, transactionRef, {})to mark it completed.PosPaymentServiceemitsPAYMENT_EVENTS.COMPLETEDviaEventEmitter2.- The existing
OrderPaymentListenerhandles the event and transitions the order topaid.
POS gateways: pos_cash, pos_fonepay, pos_esewa, pos_card. These are stored as the paymentMethod on the order but do not go through the gateway registry (no gateway.initiate() call).
The redirect handlers (/api/payments/redirect/:paymentId/success|failure) are NOT used for POS payments.