Shop It Docs
Developer ResourcesPayment

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)
  • esewa
  • fonepay
  • connect_ips
  • cod (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_success
    • payment.failed -> order.payment_failed

Frontend Contract

Frontend keeps a single initiation pattern:

  1. Call checkout/buy-now once.
  2. Use initiationType + redirectUrl + gatewayPayload to send user to gateway.
  3. Do not call manual verify endpoints.

COD exception: For initiationType: "cod", no gateway redirect occurs. Frontend navigates to returnUrl directly after receiving the response. The order remains orderStatus = "payment_pending" with paymentStatus = "cod_pending" until admin cash collection sets paymentStatus = "cod_collected".

File Map

  • apps/api/src/modules/payment/payment.module.ts
  • apps/api/src/modules/payment/payment.service.ts
  • apps/api/src/modules/payment/controllers/payment-redirect.controller.ts
  • apps/api/src/modules/payment/controllers/stripe-webhook.controller.ts
  • apps/api/src/modules/payment/gateways/stripe.gateway.ts
  • packages/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):

  1. PosPaymentService calls PaymentService.createPaymentRecord(orderId, "order", customerId, amountNpr, gateway) to create a payment record.
  2. PosPaymentService calls PaymentService.markPaymentCompleted(paymentId, transactionRef, {}) to mark it completed.
  3. PosPaymentService emits PAYMENT_EVENTS.COMPLETED via EventEmitter2.
  4. The existing OrderPaymentListener handles the event and transitions the order to paid.

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.