eSewa is one of the most widely used digital payment platforms in Nepal, making it a must-know tool for developers building e-commerce or payment-based applications. This guide will walk you through integrating eSewa into your web application using the Hono framework and TypeScript.
Prerequisites
To follow along, you'll need:
A basic understanding of TypeScript.
A web framework (we'll use Hono).
Node.js installed on your system.
eSewa test credentials.
If you’re new to Hono or need more background on eSewa’s API, consider checking their official developer documentation .
Setting Up the Payment Initiation Route
We'll begin by defining a route to initiate the payment process. Here’s how it looks in TypeScript:
app . post ( '/initiate-payment' , async ( c ) => {
try {
const { product_name , amount } = await c . req . json ();
// Adding fixed charges for the payment
const tax_amount = 10 ;
const product_delivery_charge = 0 ;
const product_service_charge = 0 ;
const total_amount = amount + tax_amount + product_delivery_charge + product_service_charge ;
// Generating transaction details
const transaction_uuid = Date . now (). toString ();
const product_code = generateProductCode ( product_name );
// Creating a signature to validate the payment
const signed_field_names = "total_amount,transaction_uuid,product_code" ;
const signature = createSignature (
`total_amount= ${ total_amount } ,transaction_uuid= ${ transaction_uuid } ,product_code= ${ product_code } `
);
return c . json ({
amount ,
total_amount ,
tax_amount ,
product_delivery_charge ,
product_service_charge ,
transaction_uuid ,
product_code ,
signed_field_names ,
signature
});
} catch ( error ) {
return c . json ({ error : 'Payment initiation failed' }, 500 );
}
});
Utility Functions
These functions help generate signatures and product codes:
import * as crypto from 'crypto' ;
export function createSignature ( data : string ) {
const secret = "8gBm/:&EnhH.1/q" ; // Example secret key
const hmac = crypto . createHmac ( 'sha256' , secret );
hmac . update ( data );
return hmac . digest ( 'base64' );
}
export const generateProductCode = ( productName : string ) => {
// Use a test product code as per documentation
return "EPAYTEST" ;
};
export const verifySignature = ( data : any , signature : string , secretKey = "8gBm/:&EnhH.1/q" ) => {
const { signed_field_names = '' } = data ;
const fields = signed_field_names . split ( ',' );
const values = fields . map (( field : string | number ) => ` ${ data [ field ] } ` ). join ( ',' );
const generatedSignature = createSignature ( values );
return generatedSignature === signature ;
};
HTML Form for Payment
This form collects the payment details:
< div >
< form id = "payment-form" >
< div >
< label >Product Name</ label >
< input type = "text" id = "product_name" name = "product_name" required >
</ div >
< div >
< label >Amount (NPR)</ label >
< input type = "number" id = "amount" name = "amount" required >
</ div >
< button type = "submit" >Continue to Payment</ button >
</ form >
< div id = "payment-details" style = "display:none;" >
< h3 >Payment Details</ h3 >
< div >Amount: < span id = "detail-amount" ></ span ></ div >
< div >Tax Amount: < span id = "detail-tax" ></ span ></ div >
< div >Total Amount: < span id = "detail-total" ></ span ></ div >
< button id = "back-button" >Back</ button >
< button id = "confirm-payment" >Confirm Payment</ button >
</ div >
</ div >
Client-Side JavaScript
Here's the script to handle form submission and display payment details:
let paymentData = null ;
function showPaymentDetails () {
document . getElementById ( 'payment-form' ). style . display = 'none' ;
document . getElementById ( 'payment-details' ). style . display = 'block' ;
}
function showInitialForm () {
document . getElementById ( 'payment-form' ). style . display = 'block' ;
document . getElementById ( 'payment-details' ). style . display = 'none' ;
}
function updatePaymentDetails ( data ) {
document . getElementById ( 'detail-amount' ). textContent = data . amount ;
document . getElementById ( 'detail-tax' ). textContent = data . tax_amount ;
document . getElementById ( 'detail-total' ). textContent = data . total_amount ;
}
function submitToEsewa () {
const esewaForm = document . createElement ( 'form' );
esewaForm . method = 'POST' ;
esewaForm . action = 'https://rc-epay.esewa.com.np/api/epay/main/v2/form' ;
const formFields = {
... paymentData ,
success_url : 'http://localhost:3000/success' ,
failure_url : 'http://localhost:3000/failure'
};
Object . entries ( formFields ). forEach (([ key , value ]) => {
const input = document . createElement ( 'input' );
input . type = 'hidden' ;
input . name = key ;
input . value = value ;
esewaForm . appendChild ( input );
});
document . body . appendChild ( esewaForm );
esewaForm . submit ();
}
document . getElementById ( 'payment-form' ). addEventListener ( 'submit' , async ( e ) => {
e . preventDefault ();
const formData = {
product_name : document . getElementById ( 'product_name' ). value ,
amount : Number ( document . getElementById ( 'amount' ). value )
};
try {
const response = await fetch ( '/initiate-payment' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON . stringify ( formData )
});
if ( response . ok ) {
paymentData = await response . json ();
paymentData . amount = formData . amount ;
updatePaymentDetails ( paymentData );
showPaymentDetails ();
} else {
throw new Error ( 'Failed to initiate payment' );
}
} catch ( error ) {
console . error ( 'Payment initiation failed:' , error );
alert ( 'Failed to initiate payment. Please try again.' );
}
});
document . getElementById ( 'back-button' ). addEventListener ( 'click' , showInitialForm );
document . getElementById ( 'confirm-payment' ). addEventListener ( 'click' , () => {
if ( paymentData ) {
submitToEsewa ();
}
});
Success and Failure Routes
These routes handle payment success and failure:
app . get ( '/success' , async ( c ) => {
try {
const responseData = c . req . query ( 'data' );
if ( ! responseData ) return c . redirect ( '/failure' );
const decodedData = JSON . parse ( Buffer . from ( responseData , 'base64' ). toString ( 'utf-8' ));
const message = decodedData . signed_field_names
. split ( ',' )
. map (( field : string ) => ` ${ field } = ${ decodedData [ field ] || "" } ` )
. join ( ',' );
const signature = createSignature ( message );
if ( signature !== decodedData . signature ) {
return c . redirect ( '/failure?message=Signature mismatch' );
}
return c . html ( renderHTML ( SuccessPage ( decodedData )));
} catch ( error ) {
return c . redirect ( '/failure?message=Error processing payment response' );
}
});
app . get ( '/failure' , async ( c ) => {
const message = c . req . query ( 'message' ) || 'Payment failed' ;
const data = { status : 'FAILED' , message , transaction_uuid : c . req . query ( 'transaction_uuid' ) || 'N/A' };
return c . html ( renderHTML ( FailurePage ( data )));
});
Conclusion
Integrating eSewa involves setting up routes for payment initiation, creating signatures for validation, managing client-side flows, and handling payment responses. This simplified version is suitable for getting started, but always refer to the eSewa Developer Documentation for the latest updates and security best practices.
Test Credentials
For testing, use these eSewa credentials:
eSewa ID : 9806800001/2/3/4/5
Password : Nepal@123
MPIN : 1122
Token : 123456