Integrating eSewa Payment Gateway in Next.js: A Complete Guide By Mukesh Adhikari
Integrating eSewa Payment Gateway in Next.js with Strapi CMS: A Complete Guide

Integrating eSewa Payment Gateway in Next.js: A Complete Guide By Mukesh Adhikari
Integrating eSewa Payment Gateway in Next.js with Strapi CMS: A Complete Guide
If you’re building an e-commerce platform or any payment-enabled application in Nepal, integrating eSewa is essential. In this guide, I’ll walk you through integrating eSewa v2 payment gateway in a Next.js application with Strapi as your CMS backend.
What We’ll Build
- Payment initiation from Next.js frontend
- Secure API routes to handle eSewa signatures
- Strapi integration for order management
- Success/failure callback handling
- Payment verification
Prerequisites
Before starting, ensure you have:
- Node.js 18+ installed
- A Next.js 14+ project
- Strapi CMS setup (optional)
- Basic understanding of React and API routes on Next.js
Step 1: Setup Environment Variables
Create a .env.local file in your Next.js project root:
# Base URL
NEXT_PUBLIC_BASE_URL=http://localhost:3000
# eSewa Configuration (Test Mode)
NEXT_PUBLIC_ESEWA_MERCHANT_CODE=EPAYTEST
NEXT_PUBLIC_ESEWA_SECRET_KEY=8gBm/:&EnhH.1/q
NEXT_PUBLIC_ESEWA_PAYMENT_URL=https://rc-epay.esewa.com.np/api/epay/main/v2/form
# Strapi Configuration
NEXT_PUBLIC_STRAPI_URL=http://localhost:1337
STRAPI_API_TOKEN=your_strapi_api_token
Important: For production, replace test credentials with your actual merchant code and secret key from eSewa.
Step 2: Install Required Dependencies
npm install crypto-js uuid
Step 3: Create eSewa Signature Generator
Create lib/generateEsewaSignature.ts:
import CryptoJS from "crypto-js";
export function generateEsewaSignature(
secretKey: string,
message: string
): string {
const hash = CryptoJS.HmacSHA256(message, secretKey);
return CryptoJS.enc.Base64.stringify(hash);
}
This function generates the required HMAC-SHA256 signature for eSewa payment verification.
Step 4: Create Payment Types
Create lib/types.ts:
export type PaymentMethod = "esewa";
export interface PaymentRequestData {
amount: string;
productName: string;
transactionId: string;
method: PaymentMethod;
orderId?: string;
}
export interface EsewaConfig {
amount: string;
tax_amount: string;
total_amount: string;
transaction_uuid: string;
product_code: string;
product_service_charge: string;
product_delivery_charge: string;
success_url: string;
failure_url: string;
signed_field_names: string;
signature: string;
}
Step 5: Create Payment Initiation API Route
Create app/api/initiate-payment/route.ts:
import { NextResponse } from "next/server";
import { v4 as uuidv4 } from "uuid";
import { generateEsewaSignature } from "@/lib/generateEsewaSignature";
import { PaymentRequestData } from "@/lib/types";
export async function POST(req: Request) {
try {
const paymentData: PaymentRequestData = await req.json();
const { amount, productName, transactionId, method, orderId } = paymentData;
// Validation
if (!amount || !productName || !transactionId || !method) {
return NextResponse.json(
{ error: "Missing required fields" },
{ status: 400 }
);
}
if (method === "esewa") {
// Generate unique transaction UUID
const transactionUuid = `${Date.now()}-${uuidv4()}`;
// eSewa configuration
const esewaConfig = {
amount: amount,
tax_amount: "0",
total_amount: amount,
transaction_uuid: transactionUuid,
product_code: process.env.NEXT_PUBLIC_ESEWA_MERCHANT_CODE!,
product_service_charge: "0",
product_delivery_charge: "0",
success_url: `${process.env.NEXT_PUBLIC_BASE_URL}/payment/success?orderId=${orderId}`,
failure_url: `${process.env.NEXT_PUBLIC_BASE_URL}/payment/failure`,
signed_field_names: "total_amount,transaction_uuid,product_code",
};
// Generate signature
const signatureString = `total_amount=${esewaConfig.total_amount},transaction_uuid=${esewaConfig.transaction_uuid},product_code=${esewaConfig.product_code}`;
const signature = generateEsewaSignature(
process.env.NEXT_PUBLIC_ESEWA_SECRET_KEY!,
signatureString
);
return NextResponse.json({
esewaConfig: {
...esewaConfig,
signature,
},
});
}
return NextResponse.json(
{ error: "Invalid payment method" },
{ status: 400 }
);
} catch (error) {
console.error("Payment API Error:", error);
return NextResponse.json(
{ error: "Error creating payment session" },
{ status: 500 }
);
}
}
Step 6: Create Payment Component
Create app/payment/page.tsx:
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
interface EsewaConfig {
amount: string;
tax_amount: string;
total_amount: string;
transaction_uuid: string;
product_code: string;
product_service_charge: string;
product_delivery_charge: string;
success_url: string;
failure_url: string;
signed_field_names: string;
signature: string;
}
export default function PaymentPage() {
const [amount, setAmount] = useState("");
const [productName, setProductName] = useState("");
const [isLoading, setIsLoading] = useState(false);
const handlePayment = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
try {
// Generate transaction ID
const transactionId = `TXN-${Date.now()}`;
// Call payment API
const response = await fetch("/api/initiate-payment", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
method: "esewa",
amount,
productName,
transactionId,
}),
});
if (!response.ok) {
throw new Error("Payment initiation failed");
}
const { esewaConfig }: { esewaConfig: EsewaConfig } = await response.json();
// Create form and submit to eSewa
const form = document.createElement("form");
form.method = "POST";
form.action = process.env.NEXT_PUBLIC_ESEWA_PAYMENT_URL!;
// Add form fields
Object.entries(esewaConfig).forEach(([key, value]) => {
const input = document.createElement("input");
input.type = "hidden";
input.name = key;
input.value = String(value);
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
} catch (error) {
console.error("Payment error:", error);
alert("Payment failed. Please try again.");
} finally {
setIsLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-4">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>eSewa Payment</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handlePayment} className="space-y-4">
<div>
<Label htmlFor="amount">Amount (NPR)</Label>
<Input
id="amount"
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Enter amount"
required
min="1"
/>
</div>
<div>
<Label htmlFor="product">Product Name</Label>
<Input
id="product"
value={productName}
onChange={(e) => setProductName(e.target.value)}
placeholder="Enter product name"
required
/>
</div>
<Button
type="submit"
className="w-full"
disabled={isLoading || !amount || !productName}
>
{isLoading ? "Processing..." : "Pay with eSewa"}
</Button>
</form>
</CardContent>
</Card>
</div>
);
}
Step 7: Handle Payment Success
Create app/payment/success/page.tsx:
"use client";
import { useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export default function PaymentSuccess() {
const searchParams = useSearchParams();
const [isVerifying, setIsVerifying] = useState(true);
useEffect(() => {
verifyPayment();
}, []);
const verifyPayment = async () => {
try {
const orderId = searchParams.get("orderId");
const data = searchParams.get("data");
// Verify payment with eSewa (you can call verification API)
// Update order status in Strapi
if (orderId) {
await fetch(`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/orders/${orderId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.STRAPI_API_TOKEN}`,
},
body: JSON.stringify({
data: { status: "paid", paymentData: data },
}),
});
}
setIsVerifying(false);
} catch (error) {
console.error("Verification error:", error);
setIsVerifying(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-4">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Payment Successful!</CardTitle>
</CardHeader>
<CardContent>
{isVerifying ? (
<p>Verifying payment...</p>
) : (
<div>
<p className="text-green-600 font-semibold mb-4">
Your payment has been processed successfully.
</p>
<p className="text-sm text-gray-600">
Order ID: {searchParams.get("orderId")}
</p>
</div>
)}
</CardContent>
</Card>
</div>
);
}
Step 8: Handle Payment Failure
Create app/payment/failure/page.tsx:
"use client";
import Link from "next/link";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
export default function PaymentFailure() {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-4">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Payment Failed</CardTitle>
</CardHeader>
<CardContent>
<p className="text-red-600 mb-4">
Your payment could not be processed. Please try again.
</p>
<Link href="/payment">
<Button className="w-full">Try Again</Button>
</Link>
</CardContent>
</Card>
</div>
);
}
Step 9: Strapi Integration (Optional)
If you’re using Strapi, create an Order content type with these fields:
amount(Number)productName(Text)transactionId(Text)status(Enum: pending, paid, failed)paymentData(JSON)
Then update your payment initiation to create an order in Strapi:
// Before initiating payment
const orderResponse = await fetch(`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/orders`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.STRAPI_API_TOKEN}`,
},
body: JSON.stringify({
data: {
amount,
productName,
transactionId,
status: "pending",
},
}),
});
const order = await orderResponse.json();
const orderId = order.data.id;
Testing Your Integration
Test Credentials
- eSewa ID: 9806800001, 9806800002, 9806800003
- Password: Nepal@123
- MPIN: 1122
- Token: 123456
Test Flow
- Enter amount and product name
- Click “Pay with eSewa”
- Log in with test credentials
- Complete the payment
- Verify success/failure redirect
Production Checklist
Before going live:
- [ ] Replace test merchant code with production code
- [ ] Update secret key with production key
- [ ] Change eSewa payment URL to production endpoint
- [ ] Set up proper error logging
- [ ] Implement payment verification API
- [ ] Add transaction history
- [ ] Test thoroughly with real credentials
Key Security Points
- Never expose secret keys — Keep them server-side only
- Validate all inputs — Sanitize user data before processing
- Verify signatures — Always validate eSewa response signatures
- Use HTTPS — Ensure all production URLs use HTTPS
- Log transactions — Keep detailed logs for debugging and auditing
Common Issues & Solutions
Issue: Signature mismatch error Solution: Verify that the signature string format matches exactly: total_amount,transaction_uuid,product_code
Issue: Payment URL not working Solution: Check if you’re using the correct URL for test/production environment
Issue: Callback not working Solution: Ensure your success/failure URLs are publicly accessible and correctly formatted
Conclusion
You’ve now successfully integrated eSewa payment gateway in your Next.js application with Strapi! This setup provides a solid foundation for handling payments securely and efficiently.
For production use, remember to:
- Get your merchant account approved by eSewa
- Switch to production credentials
- Implement proper error handling and logging
- Add payment verification for extra security
Have questions or facing issues? Drop a comment below, and I’ll be happy to help!
NextJS #eSewa #PaymentGateway #Nepal #WebDevelopment #Strapi #JavaScript #TypeScript
메타데이터
- post_id
- da2efbe3c7ef
- slug
- integrating-esewa-payment-gateway-in-next-js-a-complete-guide-by-mukesh-adhikari-da2efbe3c7ef
- url
- https://medium.com/@mukesh.adhykari/integrating-esewa-payment-gateway-in-next-js-a-complete-guide-by-mukesh-adhikari-da2efbe3c7ef
- canonical_url
- https://medium.com/@mukesh.adhykari/integrating-esewa-payment-gateway-in-next-js-a-complete-guide-by-mukesh-adhikari-da2efbe3c7ef
- author_url
- https://medium.com/@mukesh.adhykari
- status
- ok
- fetched_at
- 2026-07-16 04:52:32