← Back to list

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

Mukesh Adhykari · 2025-10-25 14:12 · 0 claps · 5.5 min read
#payment-integration #esewa #nextjs #esewa-nextjs-strapicms
Open on Medium ↗
Wiki topics: FIN · Fintech & Banking 🌐 · Web Development

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

  1. Enter amount and product name
  2. Click “Pay with eSewa”
  3. Log in with test credentials
  4. Complete the payment
  5. 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

  1. Never expose secret keys — Keep them server-side only
  2. Validate all inputs — Sanitize user data before processing
  3. Verify signatures — Always validate eSewa response signatures
  4. Use HTTPS — Ensure all production URLs use HTTPS
  5. 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