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/fonepayimport { 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/txnVerificationOverride 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,RUDT 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,remarks2For 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_BODYPass 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_AMTHosted 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, andpasswordon 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.