esewa-api-wrapper
v1.0.0
Published
A secure and developer-friendly Node.js wrapper for seamless integration with the eSewa Payment Gateway.
Maintainers
Readme
💚 esewa-api-wrapper
A secure and developer-friendly Node.js wrapper for seamless integration with the eSewa Payment Gateway.
Installation · Quick Start · API Reference · Examples · Security
✨ Features
- 🔐 HMAC-SHA256 Signed Requests — Automatic signature generation & verification
- 🛡️ Tamper-Proof Responses — Timing-safe signature verification prevents fraud
- 🏗️ TypeScript First — Full type definitions with comprehensive JSDoc
- 🌐 Dual Environment — Seamless switching between sandbox and production
- 📦 ESM + CommonJS — Works everywhere — import or require
- 🔄 Auto Retry — Exponential backoff for transient network failures
- 🪵 Debug Logging — Optional logs with automatic sensitive data masking
- ⚡ Zero Dependencies — Uses only Node.js built-in modules
- 🌳 Tree-Shakable — Import only what you need
📦 Installation
npm install esewa-api-wrapperyarn add esewa-api-wrapperpnpm add esewa-api-wrapper🔧 Environment Setup
Create a .env file in your project root:
cp node_modules/esewa-api-wrapper/.env.example .env# .env
ESEWA_MERCHANT_ID=EPAYTEST
ESEWA_SECRET_KEY=8gBm/:&EnhH.1/q
ESEWA_ENVIRONMENT=sandbox
ESEWA_SUCCESS_URL=http://localhost:3000/payment/success
ESEWA_FAILURE_URL=http://localhost:3000/payment/failure⚠️ Never commit
.envto version control. Add it to.gitignore.
Sandbox Test Credentials
| Field | Value |
|-------|-------|
| Merchant ID | EPAYTEST |
| Secret Key | 8gBm/:&EnhH.1/q |
| eSewa ID | 9806800001 to 9806800005 |
| Password | Nepal@123 |
| MPIN | 1122 |
| OTP Token | 123456 |
🚀 Quick Start
import { EsewaClient } from 'esewa-api-wrapper';
// Initialize the client
const esewa = new EsewaClient({
merchantId: 'EPAYTEST',
secretKey: process.env.ESEWA_SECRET_KEY!,
environment: 'sandbox',
successUrl: 'https://yourdomain.com/payment/success',
failureUrl: 'https://yourdomain.com/payment/failure',
});
// 1. Create a payment
const payment = esewa.createPayment({
amount: 1000,
taxAmount: 0,
serviceCharge: 0,
deliveryCharge: 0,
transactionUuid: esewa.generateTransactionUuid(),
productCode: 'EPAYTEST',
});
console.log(payment.url); // eSewa payment form URL
console.log(payment.formData); // Signed form data to POST
// 2. Verify a payment (after success callback)
const status = await esewa.verifyPayment({
transactionUuid: 'your-transaction-uuid',
totalAmount: 1000,
productCode: 'EPAYTEST',
});
if (status.status === 'COMPLETE') {
console.log('Payment confirmed!', status.ref_id);
}📖 API Reference
new EsewaClient(config)
Creates a new eSewa client instance.
interface EsewaConfig {
merchantId: string; // Your eSewa merchant ID
secretKey: string; // HMAC secret key (from eSewa)
environment: 'sandbox' | 'production';
successUrl: string; // Redirect URL on success
failureUrl: string; // Redirect URL on failure
enableLogging?: boolean; // Debug logging (default: false)
maxRetries?: number; // Network retry attempts (default: 3)
retryDelay?: number; // Base retry delay ms (default: 1000)
timeout?: number; // Request timeout ms (default: 30000)
}esewa.createPayment(options): PaymentInitiationResult
Generates a signed payment URL and form data for redirecting users to eSewa.
const { url, formData } = esewa.createPayment({
amount: 1000, // Base price
taxAmount: 130, // Tax (optional, default: 0)
serviceCharge: 0, // Service charge (optional, default: 0)
deliveryCharge: 0, // Delivery charge (optional, default: 0)
transactionUuid: esewa.generateTransactionUuid(),
productCode: 'EPAYTEST',
});
// Redirect user via POST to `url` with `formData` as bodyReturns:
interface PaymentInitiationResult {
url: string; // eSewa payment endpoint URL
formData: PaymentFormData; // All signed form fields
}esewa.generatePaymentForm(options): string
Returns a complete auto-submitting HTML page. Ideal for server-rendered apps.
// Express.js
app.post('/pay', (req, res) => {
const html = esewa.generatePaymentForm({
amount: 1000,
transactionUuid: esewa.generateTransactionUuid(),
productCode: 'EPAYTEST',
});
res.setHeader('Content-Type', 'text/html');
res.send(html);
});esewa.verifyPayment(options): Promise<PaymentVerificationResponse>
Server-to-server verification via eSewa's status check API.
const result = await esewa.verifyPayment({
transactionUuid: 'your-transaction-uuid',
totalAmount: 1000,
productCode: 'EPAYTEST',
});
// result.status: 'COMPLETE' | 'PENDING' | 'FULL_REFUND' | 'PARTIAL_REFUND' |
// 'AMBIGUOUS' | 'NOT_FOUND' | 'CANCELED'
// result.ref_id: eSewa reference ID (e.g., '0001TS9')esewa.decodeResponse(encodedData): EsewaPaymentResponse
Decodes and signature-verifies the Base64-encoded response from eSewa's success redirect.
// Express.js success callback
app.get('/payment/success', (req, res) => {
try {
const response = esewa.decodeResponse(req.query.data as string);
// response.status === 'COMPLETE'
// response.transaction_code === '000AWEO'
// response.total_amount === 1000
} catch (error) {
if (error instanceof SignatureError) {
// ⚠️ Possible tampering — DO NOT process this payment
}
}
});esewa.verifyWebhookSignature(payload, signature?): WebhookVerificationResult
Verifies incoming webhook notification signatures.
app.post('/webhook/esewa', (req, res) => {
const result = esewa.verifyWebhookSignature(
req.body,
req.headers['x-esewa-signature'] as string
);
if (result.isValid) {
// Process result.payload
res.json({ received: true });
} else {
res.status(403).json({ error: result.error });
}
});esewa.initiateRefund(options): Promise<RefundResponse>
Checks refund eligibility and current transaction status.
const refund = await esewa.initiateRefund({
transactionUuid: 'your-transaction-uuid',
amount: 500,
productCode: 'EPAYTEST',
reason: 'Customer requested refund',
});Utility Methods
| Method | Description |
|--------|-------------|
| esewa.generateTransactionUuid() | Generates a unique YYMMDD-xxxxxxxxxxxx transaction ID |
| esewa.calculateTotalAmount(request) | Sums amount + tax + service + delivery charges |
| esewa.signPayload(payload) | Signs any { key: value } object with HMAC-SHA256 |
| esewa.verifySignature(payload, sig, fields) | Timing-safe signature verification |
| esewa.getEnvironmentBaseUrl() | Returns { payment, status } URLs for current env |
| esewa.getEnvironment() | Returns 'sandbox' or 'production' |
| esewa.getMerchantId() | Returns the configured merchant ID |
Standalone Exports (Tree-Shakable)
For serverless or size-sensitive environments, import only what you need:
import {
generatePaymentSignature,
verifySignature,
generateTransactionUuid,
calculateTotalAmount,
getPaymentUrl,
} from 'esewa-api-wrapper';💡 Examples
Express.js Integration
See the complete example: examples/express-example.ts
import express from 'express';
import { EsewaClient, SignatureError } from 'esewa-api-wrapper';
const app = express();
const esewa = new EsewaClient({ /* config */ });
// Initiate payment
app.post('/pay', (req, res) => {
const html = esewa.generatePaymentForm({
amount: parseFloat(req.body.amount),
transactionUuid: esewa.generateTransactionUuid(),
productCode: 'EPAYTEST',
});
res.send(html);
});
// Verify payment
app.get('/payment/success', async (req, res) => {
const response = esewa.decodeResponse(req.query.data as string);
const status = await esewa.verifyPayment({
transactionUuid: response.transaction_uuid,
totalAmount: response.total_amount,
productCode: response.product_code,
});
if (status.status === 'COMPLETE') {
res.send('Payment successful!');
}
});Next.js Integration
See the complete example: examples/nextjs-example.ts
// app/api/payment/initiate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { esewa } from '@/lib/esewa';
export async function POST(request: NextRequest) {
const { amount } = await request.json();
const payment = esewa.createPayment({
amount,
transactionUuid: esewa.generateTransactionUuid(),
productCode: process.env.ESEWA_PRODUCT_CODE!,
});
return NextResponse.json({
paymentUrl: payment.url,
formData: payment.formData,
});
}🔐 Security
Best Practices
Never expose
secretKeyclient-side — Keep it in environment variables on the server only.Always verify payments server-to-server — After receiving a success callback, call
verifyPayment()to confirm with eSewa directly.Validate response signatures — Use
decodeResponse()which automatically verifies HMAC-SHA256 signatures before returning data.Use HTTPS in production — The client enforces HTTPS for success/failure URLs in production mode.
Generate unique transaction UUIDs — Use
generateTransactionUuid()to prevent replay attacks.Verify webhook signatures — Always call
verifyWebhookSignature()before processing webhook data.
How Signatures Work
┌─────────────────────────────────────────────────────┐
│ Payment Request │
│ │
│ Message: "total_amount=110,transaction_uuid=241028, │
│ product_code=EPAYTEST" │
│ │
│ Secret Key: "8gBm/:&EnhH.1/q" │
│ │
│ HMAC-SHA256 → Base64 │
│ │
│ Signature: "i94zsd3oXF6ZsSr/kGqT4sSzYQzjj1W/..." │
└─────────────────────────────────────────────────────┘Security Features
| Feature | Implementation | |---------|---------------| | Request Signing | HMAC-SHA256 with Base64 encoding | | Response Verification | Timing-safe signature comparison | | Anti-Tampering | Signed field verification | | Replay Prevention | Unique transaction UUIDs | | Data Protection | Sensitive data masking in logs | | HTTPS Enforcement | Required in production mode | | Input Sanitization | All inputs validated & sanitized |
🏗️ Transaction Flow
┌──────────┐ 1. createPayment() ┌──────────┐
│ │ ──────────────────────────▶│ │
│ Your │ 2. Redirect (POST) │ eSewa │
│ Server │ ──────────────────────────▶│ ePay │
│ │ │ │
│ │ 3. Success redirect │ │
│ │ ◀──────────────────────────│ │
│ │ 4. decodeResponse() │ │
│ │ │ │
│ │ 5. verifyPayment() │ │
│ │ ──────────────────────────▶│ │
│ │ 6. Status response │ │
│ │ ◀──────────────────────────│ │
└──────────┘ └──────────┘🧪 Error Handling
The package provides specialized error classes for precise error handling:
import {
EsewaError, // Base error class
ValidationError, // Invalid input/config
SignatureError, // Signature mismatch (possible tampering)
NetworkError, // API request failures
ConfigurationError, // Client misconfiguration
} from 'esewa-api-wrapper';
try {
await esewa.verifyPayment({ /* ... */ });
} catch (error) {
if (error instanceof SignatureError) {
// ⚠️ Possible tampering detected
} else if (error instanceof NetworkError) {
// 🌐 eSewa API unreachable — retry later
} else if (error instanceof ValidationError) {
// ❌ Invalid parameters
}
}All errors include:
code— Machine-readable error code (e.g.,SIGNATURE_ERROR)message— Human-readable descriptiondetails— Additional context (optional)cause— Original error for chain tracing (optional)
📁 Project Structure
esewa-api-wrapper/
├── src/
│ ├── client.ts # Main EsewaClient class
│ ├── config.ts # Configuration management
│ ├── errors.ts # Custom error classes
│ ├── payment.ts # Payment initiation & form generation
│ ├── verification.ts # Response decoding & status verification
│ ├── webhook.ts # Webhook signature verification
│ ├── types/
│ │ └── index.ts # All TypeScript interfaces & types
│ ├── utils/
│ │ ├── crypto.ts # HMAC-SHA256 signature utilities
│ │ ├── validator.ts # Input validation & sanitization
│ │ └── helpers.ts # URL builders, Base64, HTTP, logging
│ └── index.ts # Package entry point (barrel exports)
├── tests/ # Vitest test suites
├── examples/ # Express.js & Next.js examples
├── .github/workflows/ # CI/CD pipeline
├── package.json
├── tsconfig.json
└── README.md🤝 Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build
npm run build
# Lint
npm run lint
# Format
npm run format