Payments SDK
Guides

NestJS integration

Keep NestJS infrastructure and settlement logic outside the SDK.

NestJS owns the HTTP and application boundaries. @payments-sdk/payments remains a plain configured object that performs provider operations. Exporting that object from a small module keeps the provider configuration outside Nest's dependency-injection lifecycle while services and controllers can import it.

Configure once

// payments.ts
import { esewa } from '@payments-sdk/esewa';
import { fonepay } from '@payments-sdk/fonepay';
import { createPayments } from '@payments-sdk/payments';

export const payments = createPayments({
  providers: [
    fonepay({ merchantCode, secretKey, username, password }),
    esewa({
      productCode,
      secretKey: esewaSecretKey,
      environment: 'sandbox',
    }),
  ],
});

The values above should come from Nest's configuration system or a secret manager in a real application. Keep this module server-only.

Application service

The service owns persistence, idempotency, and settlement. The SDK does not provide createPayment() or settlePayment(); those are application business operations around the SDK:

// payment.service.ts
import { Injectable } from '@nestjs/common';
import { payments } from './payments.js';

@Injectable()
export class PaymentService {
  async createPayment(orderId: string) {
    const order = await this.orders.findForCheckout(orderId);

    const result = await payments.checkout({
      provider: order.provider,
      amount: order.amountMinor,
      currency: order.currency,
      reference: order.paymentReference,
      providerOptions: order.providerOptions,
    });

    await this.payments.saveAction(order.paymentReference, result);
    return result;
  }

  async settlePayment(reference: string) {
    const payment = await this.payments.findByReference(reference);
    if (!payment || payment.settledAt) return payment;

    const verified = await payments.verify({
      provider: payment.provider,
      reference,
      providerOptions: payment.verifyOptions,
    });

    if (verified.status !== 'succeeded') return verified;

    // Use a transaction or an equivalent idempotency boundary.
    return this.payments.markSettledAndFulfill(payment.id, verified);
  }
}

markSettledAndFulfill() must be safe when called more than once. A provider return, a retry, and a reconciliation job may all attempt settlement for the same payment.

Controller routes

Controllers translate HTTP requests into calls to the service. The SDK does not register these routes:

// payment.controller.ts
import { Controller, Get, Param, Post, Req } from '@nestjs/common';
import type { Request } from 'express';

@Controller('payments')
export class PaymentController {
  constructor(private readonly service: PaymentService) {}

  @Post(':orderId/checkout')
  createCheckout(@Param('orderId') orderId: string) {
    return this.service.createPayment(orderId);
  }

  @Get('returns/esewa')
  async esewaReturn(@Req() request: Request) {
    const returned = await payments.returns.esewa(request);
    return this.service.settlePayment(returned.reference!);
  }
}

When using Express, pass the request shape that preserves the raw URL and body needed by the provider. A Web Request or the normalized raw request shape is also supported by the SDK.

Webhook route

Webhook support is capability-driven. Only add a typed payments.webhooks.<provider> call for a configured provider that implements a webhook parser:

@Post('webhooks/provider-name')
async providerWebhook(@Req() request: Request) {
  const event = await payments.webhooks.providerName(request);
  return this.service.settlePayment(event.reference!);
}

The initial eSewa, FonePay, and Khalti packages expose returns where applicable, but do not expose webhooks. A future webhook-capable provider adds that namespace through its own package without changing @payments-sdk/payments.

Do not treat a browser return and a webhook as the same event. Returns are browser-facing inbound requests; webhooks are server-to-server events. Both should be verified and settled idempotently according to the provider contract.

On this page