pay-panda-js
Official Node.js client for Pay-Panda server-to-server payment integration.
Use this package in your backend to:
- authenticate with Pay-Panda OAuth app credentials;
- validate app credentials;
- create hosted checkout/payment QR URLs;
- check payment status;
- verify redirect callbacks before fulfilling orders;
- normalize payment responses;
- generate payment summaries for your own app.
Do not use this package in React/browser/mobile frontend code. Your Pay-Panda App Secret must stay on your backend server.
Module format
This package is ESM-only and supports modern Node.js 18+.
ESM usage:
import { PayPanda } from 'pay-panda-js';
CommonJS users can load it with dynamic import:
const { PayPanda } = await import('pay-panda-js');
Install
npm install pay-panda-js
For local testing from this folder:
cd D:\2026-SHOP\Pay-Panda\Pay-Panda-Client
npm install
Configure
Get these from the Pay-Panda dashboard:
- App ID
- App Secret
Example .env:
PAY_PANDA_API_BASE=https://metatronhost.in/pay-panda/api
PAY_PANDA_APP_ID=app_xxxxxxxxx
PAY_PANDA_APP_SECRET=secret_xxxxxxxxx
PAY_PANDA_REDIRECT_URL=https://yourapp.com/pay-panda/callback
Quick start
import { PayPanda } from 'pay-panda-js';
const payPanda = new PayPanda({
appId: process.env.PAY_PANDA_APP_ID,
appSecret: process.env.PAY_PANDA_APP_SECRET,
});
const payment = await payPanda.createPayment({
orderId: 'ORDER-1001',
amount: 499,
customerName: 'Vishnu B',
customerMobile: '9788116632',
customerEmail: 'vishnu@example.com', // optional — Pay-Panda emails a receipt automatically on success
reason: 'Order payment',
remark1: 'Invoice #1001',
redirectUrl: 'https://yourapp.com/pay-panda/callback',
});
console.log(payment.checkoutUrl);
Send payment.checkoutUrl to your customer.
Verify before fulfilling
const result = await payPanda.verifyPayment({
paymentId: 'pay_xxxxx',
orderId: 'ORDER-1001',
// Pass the amount YOU originally requested for this order — look it up from your own
// database, never from a redirect URL or anything the customer's browser sent. Some UPI
// apps let the payer edit the amount before paying; this rejects verification
// (verified: false, code: 'AMOUNT_MISMATCH') if it doesn't match to the paisa, even if the
// payment otherwise shows as SUCCESS.
amount: 499.00,
customerMobile: '9788116632', // optional — checked, but informational only (see below)
});
if (result.verified && result.payment.status === 'SUCCESS') {
// Mark your order as paid.
// Fulfill product/service/subscription here.
if (result.mobileMatched === false) {
// Still safe to fulfill — the checkout contact number isn't necessarily the UPI handle's
// registered number. Use this only if you want extra scrutiny on your own end.
}
} else if (result.code === 'AMOUNT_MISMATCH') {
// Do not fulfill — the paid amount does not match what you requested.
}
Never fulfill only from redirect query params. Always call verifyPayment() from your backend, and always pass the amount you requested so a tampered-amount payment can never be reported as verified.
Verification notes
verifyPayment({ orderId, amount })orverifyPayment({ paymentId, amount })is the strict fulfillment check. Pullamountfrom your own order database, not from the redirect URL.verifyRedirect(req)only extracts trusted identifiers and calls Pay-Panda verification. It cannot know your original order amount unless your app looks that order up and callsverifyPayment({ orderId, amount }).PaymentSummary.verifiedis derived fromstatus === 'SUCCESS'for display/summary convenience. Do not treat it as a replacement for the server verification result fromverifyPayment().parseRedirectParams()accepts full URLs, relative URLs like/callback?..., raw query strings,URLSearchParams, Expressreq, and plain query objects.
Express redirect callback
import express from 'express';
import { PayPanda } from 'pay-panda-js';
const app = express();
const payPanda = new PayPanda({
appId: process.env.PAY_PANDA_APP_ID,
appSecret: process.env.PAY_PANDA_APP_SECRET,
});
app.get('/pay-panda/callback', async (req, res) => {
try {
const result = await payPanda.verifyRedirect(req);
if (result.verified && result.payment.status === 'SUCCESS') {
// await markOrderPaid(result.payment.orderId, result.payment.paymentId);
return res.json({ success: true, payment: result.summary });
}
return res.status(400).json({
success: false,
message: 'Payment not completed',
payment: result.summary,
});
} catch (error) {
return res.status(error.status || 500).json({
success: false,
message: error.message,
requestId: error.requestId,
});
}
});
Pay-Panda redirects to your redirectUrl like:
https://yourapp.com/pay-panda/callback?pay_panda_payment_id=pay_xxxxx&order_id=ORDER-1001&status=SUCCESS&bank_rrn=618587140937
Full flow
Your backend creates order
-> payPanda.createPayment()
-> send checkoutUrl to customer
-> customer pays on Pay-Panda checkout
-> Pay-Panda redirects to your redirectUrl
-> payPanda.verifyRedirect(req)
-> if SUCCESS, fulfill order
Exports
import {
PayPanda,
PayPandaError,
createPayPandaClient,
PAYMENT_STATUS,
parseRedirectParams,
normalizePayment,
summarizePayment,
isSuccessfulPayment,
isPendingPayment,
isExpiredPayment,
} from 'pay-panda-js';
Client methods
new PayPanda(options)
const payPanda = new PayPanda({
appId: process.env.PAY_PANDA_APP_ID,
appSecret: process.env.PAY_PANDA_APP_SECRET,
apiBase: 'https://metatronhost.in/pay-panda/api', // optional
});
authenticate(options?)
Gets and caches an OAuth access token.
const token = await payPanda.authenticate();
getAccessToken(options?)
Returns only the access token string.
const accessToken = await payPanda.getAccessToken();
validateCredentials()
Checks whether the App ID and App Secret are valid.
const result = await payPanda.validateCredentials();
if (result.valid) {
console.log('Pay-Panda credentials are valid');
}
createPayment(input)
Creates a Pay-Panda hosted checkout.
const payment = await payPanda.createPayment({
orderId: 'ORDER-1001',
amount: 499,
customerName: 'Vishnu B',
customerMobile: '9788116632',
customerEmail: 'vishnu@example.com', // optional — triggers Pay-Panda's automatic receipt email on success
reason: 'Order payment',
remark1: 'Invoice #1001',
redirectUrl: 'https://yourapp.com/pay-panda/callback',
businessUnitCode: 'branch-a',
expiresInMinutes: 10,
});
Also accepts snake_case:
await payPanda.createPayment({
order_id: 'ORDER-1001',
amount: 499,
customer_name: 'Vishnu B',
customer_email: 'vishnu@example.com',
redirect_url: 'https://yourapp.com/pay-panda/callback',
});
customerEmail / customer_email is optional. If you supply it, Pay-Panda automatically sends
that address a payment receipt email the moment the payment is confirmed — no extra API call
needed. Omit it and no receipt is sent.
createPaymentUrl(input)
Alias for createPayment(input).
const payment = await payPanda.createPaymentUrl({ orderId: 'ORDER-1001', amount: 499 });
console.log(payment.checkoutUrl);
getPaymentStatus(identifier)
Check status by order ID or payment ID.
const byOrder = await payPanda.getPaymentStatus({ orderId: 'ORDER-1001' });
const byPayment = await payPanda.getPaymentStatus({ paymentId: 'pay_xxxxx' });
You can also pass order ID directly:
const payment = await payPanda.getPaymentStatus('ORDER-1001');
getPaymentByOrderId(orderId)
const payment = await payPanda.getPaymentByOrderId('ORDER-1001');
getPaymentByPaymentId(paymentId)
const payment = await payPanda.getPaymentByPaymentId('pay_xxxxx');
verifyPayment(input)
Server-side verification before order fulfillment.
const result = await payPanda.verifyPayment({
paymentId: 'pay_xxxxx',
orderId: 'ORDER-1001',
amount: 499.00, // strongly recommended — pass the amount YOU requested, from your own records
customerMobile: '9788116632', // optional
});
amount, if supplied, is checked exactly (to the paisa) against the stored payment — a
mismatch always forces { verified: false, code: 'AMOUNT_MISMATCH' } regardless of the
payment's status. This is what catches a UPI app that let the payer change the amount before
paying.
customerMobile, if supplied, is checked too but only reported back as mobileMatched: true | false — it never affects verified. The number collected at checkout is a contact field, not
necessarily the UPI handle's registered number, so a mismatch alone shouldn't block a
legitimately paid order.
Also accepts redirect params:
const result = await payPanda.verifyPayment({
redirectParams: req.query,
});
verifyRedirect(reqOrUrlOrQuery)
Convenience helper for redirect callbacks.
const result = await payPanda.verifyRedirect(req);
Accepted values:
- Express request object with
req.query - URL string
URLURLSearchParams- plain query object
getPaymentSummary(identifier)
Returns a clean summary object for your app/database/logs.
const summary = await payPanda.getPaymentSummary({ orderId: 'ORDER-1001' });
Example summary:
{
"paymentId": "pay_xxxxx",
"orderId": "ORDER-1001",
"amount": 499,
"currency": "INR",
"status": "SUCCESS",
"verified": true,
"checkoutUrl": "https://www.pay-panda.app/pay/pay_xxxxx",
"bankRrn": "618587140937",
"paidAt": "2026-07-17T10:10:00.000Z"
}
request(path, options?)
Low-level API request helper for future Pay-Panda API endpoints.
const data = await payPanda.request('/v1/payments/ORDER-1001');
Helper functions
parseRedirectParams(value)
const params = parseRedirectParams(req);
Returns:
{
pay_panda_payment_id: 'pay_xxxxx',
payment_id: 'pay_xxxxx',
order_id: 'ORDER-1001',
status: 'SUCCESS',
bank_rrn: '618587140937'
}
isSuccessfulPayment(payment)
if (isSuccessfulPayment(payment)) {
// fulfill
}
summarizePayment(payment)
const summary = summarizePayment(payment);
normalizePayment(payment)
Normalizes Pay-Panda camelCase and snake_case response fields.
const normalized = normalizePayment(payment);
console.log(normalized.paymentId, normalized.orderId, normalized.checkoutUrl);
Response statuses
PAYMENT_STATUS.PENDING
PAYMENT_STATUS.SUCCESS
PAYMENT_STATUS.FAILED
PAYMENT_STATUS.EXPIRED
Error handling
The package throws PayPandaError.
try {
await payPanda.createPayment({ orderId: 'ORDER-1001', amount: 499 });
} catch (error) {
console.error(error.message);
console.error(error.status);
console.error(error.code);
console.error(error.requestId);
}
Local examples
This repository also includes runnable examples:
npm run example:basic
npm run example:summary
npm run example:callback
And direct API snippets:
npm run oauth
npm run create-payment
npm run status
npm run verify
npm run callback-demo
Testing confidence
This package includes syntax checks and offline contract-helper tests. It does not run a live Pay-Panda API integration test during npm test, because live tests require real dashboard app credentials and an active merchant setup. For release confidence, run a separate live smoke test with your own sandbox/live Pay-Panda credentials before publishing.
Package validation
npm test
npm run pack:check
NPM publish checklist
Before publishing:
Confirm package name is available:
npm view pay-panda-jsLogin:
npm loginDry run:
npm run pack:checkPublish:
npm publish --access public
Security rules
- App Secret must stay on backend only.
- Do not put App Secret in React, browser JavaScript, Android, iOS, or public repos.
- Always call
verifyPayment()orverifyRedirect()before fulfilling an order. - Store
paymentId,orderId,status,bankRrn, andpaidAtin your own database.