paynotify-node
v1.0.8
Published
Official Node.js SDK for PayNotify Webhook Engine
Readme
[!NOTE] Designed for enterprise reliability, this Node.js SDK seamlessly integrates your backend with the PayNotify Engine. It implements Dynamic Cent Masking (Penny Drop) for lock-free concurrency management, and strictly enforces HMAC-SHA256 Webhook Verification to prevent replay and mutation attacks.
⚡ Core Architecture Features
- Atomic Penny-Drop Concurrency: Instantly resolves payment overlap (e.g., 50 users buying a ₹49 product simultaneously) by dynamically masking amounts natively via Supabase RPCs.
- Zero-Drift Signature Validation: Uses stable string concatenation (
orderId:amount:status:timestamp) instead of JSON signing to absolutely prevent parsing mutation attacks. - Replay Attack Boundary: Enforces a rigid 5-minute expiry window on all incoming webhooks.
- Client-Side Idempotency: Requires
idempotencyKeyon all order creations, shielding against double-billing during network retries.
📦 Installation
# npm
npm install paynotify-node
# yarn
yarn add paynotify-node
# pnpm
pnpm add paynotify-node🚀 Initialization
import { PayNotify } from 'paynotify-node';
const paynotify = new PayNotify({
apiKey: process.env.PAYNOTIFY_API_KEY as string,
// Optional: Defaults to https://paypager.vercel.app
gatewayUrl: process.env.PAYNOTIFY_GATEWAY_URL
});[!CAUTION] Secret Management Your
apiKeyis a highly privileged credential. Never commit it to source control or expose it to client-side environments (Browser/Mobile Apps).
🛒 Creating a Payment Order
When a user initiates checkout, call createOrder to atomically lock a unique decimal amount.
import express from 'express';
const app = express();
app.post('/api/checkout', async (req, res) => {
try {
const { baseAmount, customerName, sessionId } = req.body;
const orderData = await paynotify.createOrder({
baseAmount: baseAmount,
customerName: customerName || 'Guest',
// REQUIRED: Use session ID, Cart UUID, or Database ID to ensure idempotency
idempotencyKey: `checkout_${sessionId}`
});
// orderData returns: { success: true, orderId: "...", amount: 49.04, status: "PENDING" }
// 1. Store orderData.orderId and orderData.amount in your Database
// 2. Return data to the frontend for the React SDK
res.json(orderData);
} catch (error: any) {
res.status(503).json({ error: 'Gateway unavailable, please retry.' });
}
});🔐 Securing Webhooks (Express Middleware)
PayNotify achieves Bi-Directional JIT (Just-In-Time) verification. You will receive webhooks instantly.
The SDK includes Express middleware that automatically handles timing attacks, signature validation, and replay prevention.
app.post(
'/api/webhook',
express.json(),
paynotify.verifyWebhook(), // Drops unauthenticated/replay requests automatically
async (req, res) => {
// req.paynotify is populated only if the signature is 100% valid
const { orderId, amount, status, sender_name, sender_upi } = (req as any).paynotify;
if (status === 'VERIFIED') {
// ✅ Trust this data. Update your database, unlock features, dispatch emails.
await db.orders.updateOne(
{ id: orderId, expectedAmount: amount, status: 'PENDING' },
{ $set: { status: 'PAID' } }
);
console.log(`Payment confirmed from ${sender_name} via ${sender_upi}`);
return res.status(200).json({ success: true });
}
res.status(200).json({ message: 'Ignored' });
}
);📖 API Reference
PayNotify(config)
| Option | Type | Required | Description |
|---|---|---|---|
| apiKey | string | Yes | Your secret PayNotify environment API key. |
| gatewayUrl | string | No | Target API environment (default: https://paypager.vercel.app). |
createOrder(params)
| Parameter | Type | Required | Description |
|---|---|---|---|
| baseAmount | number | Yes | Base price of the product (e.g., 49.00). |
| idempotencyKey | string | Yes | Unique tracking key preventing double execution on network retry. |
| customerName | string | No | Used for dashboard analytics and logging. |
verifyWebhook()
Returns standard Express Middleware (req, res, next). Validates X-PayNotify-Signature using crypto.timingSafeEqual.
