npm.io
1.2.0 • Published 23h ago

pay-panda-js

Licence
MIT
Version
1.2.0
Deps
0
Size
51 kB
Vulns
0
Weekly
0

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 }) or verifyPayment({ paymentId, amount }) is the strict fulfillment check. Pull amount from 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 calls verifyPayment({ orderId, amount }).
  • PaymentSummary.verified is derived from status === 'SUCCESS' for display/summary convenience. Do not treat it as a replacement for the server verification result from verifyPayment().
  • parseRedirectParams() accepts full URLs, relative URLs like /callback?..., raw query strings, URLSearchParams, Express req, 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
  • URL
  • URLSearchParams
  • 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:

  1. Confirm package name is available:

    npm view pay-panda-js
  2. Login:

    npm login
  3. Dry run:

    npm run pack:check
  4. Publish:

    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() or verifyRedirect() before fulfilling an order.
  • Store paymentId, orderId, status, bankRrn, and paidAt in your own database.

Keywords