Shop It Docs
Developer ResourcesPayment

Gateway Interface

The PaymentGateway contract — method signatures, parameter types, return types, and how each method fits into the payment lifecycle.

Gateway Interface

Audience: Backend developers implementing or consuming payment gateways Scope: Interface contract, parameter types, return types, method responsibilities


The PaymentGateway Interface

Every payment gateway must implement this interface:

// apps/api/src/modules/payment/payment.interface.ts

export type InitiationType = "form_post" | "redirect" | "sdk";

export interface PaymentGateway {
  readonly name: string;
  initiate(params: InitiatePaymentParams): Promise<InitiatePaymentResult>;
  verify(params: VerifyPaymentParams): Promise<VerifyPaymentResult>;
}

initiate() — Start a Payment

Called when a user clicks "Pay". The gateway generates a signed payload that the frontend uses to send the user to the payment portal.

Parameters

export interface InitiatePaymentParams {
  amountNpr: number;       // Payment amount in Nepali Rupees
  referenceId: number;     // ID of the entity being paid for (e.g., subscription ID)
  referenceType: string;   // Entity type (e.g., "subscription", "order")
  userId: string;          // The paying user's ID
  returnUrl: string;       // Where the gateway should redirect after payment
}

Return Type

export interface InitiatePaymentResult {
  gatewayTransactionId: string;           // Unique ID for tracking this transaction
  initiationType: InitiationType;         // How the frontend should start the payment
  redirectUrl: string;                    // Gateway URL (form action or redirect target)
  gatewayPayload: Record<string, string>; // Signed form fields / query params
}

initiationType Values

ValueFrontend BehaviorExample Gateways
form_postBuild a hidden <form>, set action = redirectUrl, add gatewayPayload as hidden inputs, submit via POSTeSewa, ConnectIPS
redirectAppend gatewayPayload as query params to redirectUrl, navigate via window.location.hrefFonepay
sdkPass gatewayPayload values to the gateway's client-side SDKKhalti (future)

gatewayPayload — What Goes In Here

The payload contains the exact field names the gateway expects. For form-based gateways, these become the hidden form input names. Examples:

eSewa (form_post):

{
  "amount": "500",
  "tax_amount": "0",
  "total_amount": "500",
  "transaction_uuid": "42-1708000000000",
  "product_code": "EPAYTEST",
  "product_service_charge": "0",
  "product_delivery_charge": "0",
  "success_url": "https://myapp.com/payment/success",
  "failure_url": "https://myapp.com/payment/failure",
  "signed_field_names": "total_amount,transaction_uuid,product_code",
  "signature": "kI4GnQx9..."
}

Fonepay (redirect):

{
  "PID": "MERCHANT123",
  "MD": "P",
  "PRN": "ref-42",
  "AMT": "500",
  "CRN": "NPR",
  "DT": "02/22/2026",
  "R1": "Subscription purchase",
  "R2": "",
  "RU": "https://myapp.com/payment/callback",
  "DV": "a1b2c3d4e5..."
}

ConnectIPS (form_post):

{
  "MERCHANTID": "123",
  "APPID": "app-001",
  "APPNAME": "BullHouse",
  "TXNID": "txn-42-1708000000000",
  "TXNDATE": "22-02-2026",
  "TXNCRNCY": "NPR",
  "TXNAMT": "50000",
  "REFERENCEID": "sub-42",
  "REMARKS": "Subscription purchase",
  "PARTICULARS": "",
  "TOKEN": "sha256rsabase64..."
}

The frontend doesn't need to know what these fields mean — it just iterates and builds the form/URL.


verify() — Confirm a Payment

Called when the user returns from the gateway. The gateway verifies the callback data is authentic and the payment was successful.

Parameters

export interface VerifyPaymentParams {
  gatewayTransactionId: string;                // The ID returned by initiate()
  gatewayResponse: Record<string, unknown>;    // Decoded callback data from the gateway
}

The gatewayResponse is the raw data the frontend received from the gateway's redirect callback. For eSewa, this is the decoded base64 JSON from the success URL. For Fonepay, these are the callback query parameters. The frontend decodes and passes them through.

Return Type

export interface VerifyPaymentResult {
  success: boolean;   // Whether the payment was verified successfully
  amountNpr: number;  // The verified amount (used for cross-checking)
}

What verify() Should Do

  1. Validate the response signature — recompute the HMAC/signature from the callback data using the secret key, compare with the received signature
  2. Call the gateway's server-side status API — never trust client-side callback data alone; always double-check with a server-to-server call
  3. Return the verified amount — the consumer (e.g., SubscriptionMobileService) can cross-check this against the expected amount

Security Requirements

RequirementWhy
Always verify the callback signatureCallback data comes through the client — it could be tampered with
Always call the server-side status APIProvides independent confirmation that the payment actually happened
Never expose the secret key to the frontendThe backend signs payloads and verifies responses; the secret never leaves the server
Return the verified amountAllows the consumer to detect amount mismatches (e.g., user paid less than expected)

Full Lifecycle


Example: How SubscriptionMobileService Uses It

async initiatePurchase(userId, planId, gateway, returnUrl) {
  // 1. Create the subscription in pending state
  const result = await this.subscriptionService.createPendingPurchase(userId, planId);

  // 2. Create a pending payment record
  const paymentRecord = await this.paymentService.createPaymentRecord(
    result.subscription.id,
    "subscription",
    userId,
    result.plan.priceNpr,
    gateway,
  );

  // 3. Ask the gateway to generate signed payload
  const gatewayInstance = this.paymentService.getGateway(gateway);
  const gatewayResult = await gatewayInstance.initiate({
    amountNpr: result.plan.priceNpr,
    referenceId: result.subscription.id,
    referenceType: "subscription",
    userId,
    returnUrl,
  });

  // 4. Return everything the frontend needs
  return {
    subscription: result.subscription,
    payment: paymentRecord,
    gatewayTransactionId: gatewayResult.gatewayTransactionId,
    initiationType: gatewayResult.initiationType,
    redirectUrl: gatewayResult.redirectUrl,
    gatewayPayload: gatewayResult.gatewayPayload,
  };
}