Payments SDK
Providers

FonePay

Signed hosted checkout, Dynamic QR generation, and PRN status verification with FonePay.

@payments-sdk/fonepay supports FonePay's signed hosted-checkout URL and its server-side third-party Dynamic QR API.

Install and configure

npm install @payments-sdk/payments @payments-sdk/fonepay
import { fonepay } from '@payments-sdk/fonepay';

const provider = fonepay({
  merchantCode: process.env.FONEPAY_MERCHANT_CODE!,
  secretKey: process.env.FONEPAY_SECRET_KEY!,
  username: process.env.FONEPAY_USERNAME!,
  password: process.env.FONEPAY_PASSWORD!,
  fonepayBaseUrl: process.env.FONEPAY_BASE_URL!,
});

username and password are required for Dynamic QR. fonepayBaseUrl is only required for hosted redirect checkout. Dynamic QR endpoints default to:

https://merchantapi.fonepay.com/api/merchant/merchantDetailsForThirdParty/thirdPartyDynamicQrDownload
https://merchantapi.fonepay.com/api/merchant/merchantDetailsForThirdParty/txnVerification

Override them for a merchant-specific environment with endpoints: { generateQr, status }. The provider does not load credentials from the environment automatically.

Hosted checkout

const result = await payments.checkout({
  provider: 'fonepay',
  amount: 1500_00,
  currency: 'NPR',
  reference: 'order_123',
  providerOptions: {
    returnUrl: 'https://merchant.example/payments/fonepay/return',
    remarks1: 'Payment for order 123',
    remarks2: 'Online checkout',
  },
});

if (result.action.type === 'redirect') {
  // Redirect the customer to result.action.url.
}

The application amount is integer paisa and is converted to a decimal FonePay amount. The provider constructs the URL locally and does not call FonePay from the server during checkout. It signs the plain values in this order:

PID,MD,PRN,AMT,CRN,DT,R1,R2,RU

DT uses FonePay's MM/DD/YYYY format in Nepal time. PRN must be 3–25 characters; remarks1 is required; remarks2 defaults to N/A.

Dynamic QR checkout

Pass mode: 'dynamic_qr' to call FonePay's Dynamic QR generation endpoint:

const result = await payments.checkout({
  provider: 'fonepay',
  amount: 350_00,
  currency: 'NPR',
  reference: 'order_123',
  providerOptions: {
    mode: 'dynamic_qr',
    remarks1: 'Payment for order 123',
    remarks2: 'Table 4',
  },
});

if (
  result.action.type === 'qr' &&
  result.action.format === 'payload'
) {
  // Render result.action.data with the application's QR renderer.
}

The Dynamic QR request sends the integer SDK amount unchanged and signs this canonical string with HMAC-SHA512, encoded as lowercase hex:

amount,prn,merchantCode,remarks1,remarks2

For the standard NPR minor-unit convention, 350_00 is sent as 35000 (paisa). FonePay merchant configurations can define the amount unit, so confirm the account contract before going live. providerData includes the QR payload, PRN, raw gateway response, and the returned WebSocket URL when available. If FonePay returns localhost or 127.0.0.1 in that URL, the provider replaces only the host with wss://ws.fonepay.com.

The SDK does not own WebSocket connections, polling timers, or QR rendering. Use the WebSocket only as a UI acceleration and poll the provider's status endpoint as the fallback and settlement check.

Dynamic QR verification

Verify the same PRN through the typed verify namespace:

const verified = await payments.verify({
  provider: 'fonepay',
  reference: 'order_123',
  providerOptions: { amount: 350_00 },
});

The transaction verification request includes prn, merchantCode, and the requested amount as a decimal FonePay amount. It sends Basic authorization with the merchant username/password and an auth HMAC-SHA512 header over:

username,password,POST,application/json,/merchant/merchantDetailsForThirdParty/txnVerification,JSON_REQUEST_BODY

Pass the amount in SDK minor units. For example, 350_00 is sent as 350.00. FonePay's paymentStatus is normalized conservatively: successful values become succeeded, pending remains pending, and failed, cancelled, expired, or unknown values are not treated as success. The raw trace ID, requested amount, total transaction amount, and transaction date are retained in providerData.

Signed browser returns

Pass the raw application request to the provider-owned returns namespace:

export async function fonepayReturn(request: Request) {
  return payments.returns.fonepay(request);
}

FonePay returns PRN, PID, PS, RC, UID, BC, INI, P_AMT, R_AMT, and DV. The provider verifies the merchant code and the HMAC-SHA512 signature over:

PRN,PID,PS,RC,UID,BC,INI,P_AMT,R_AMT

Hosted browser returns are authenticated input, not fulfillment. Validate the returned reference against the application's stored payment intent and reconcile idempotently before marking an order paid.

Capabilities and security

FonePay implements checkout, Dynamic QR verify, and signed returns. It does not implement webhooks, refunds, or cancellations.

  • Keep secretKey, username, and password on the server and out of logs.
  • Use an HTTPS backend route for returnUrl.
  • Do not treat a QR display, WebSocket message, or browser redirect as final settlement without provider verification.

On this page