- ✓A working Laravel 11/12 application running PHP 8.2 or higher
- ✓MySQL database connected and migrations executed
- ✓Basic understanding of Blade views, Controllers, and Routes
- ✓eSewa sandbox test credentials from developer.esewa.com.np
🚀 What You Will Learn & Build
- ▸How to configure eSewa sandbox environment variables in Laravel .env
- ▸Generating the HMAC-SHA256 signature required by eSewa ePay v2
- ▸Bypassing CSRF verification for eSewa POST callbacks in Laravel 11/12
- ▸Verifying transaction status securely using Laravel Http client
- ▸Preventing fraudulent order tampering and duplicate payments
1. Understanding eSewa ePay v2 Flow in Laravel 12
2. Setting Up Environment Variables (.env)
# eSewa v2 Sandbox Credentials (Official Test Keys)
ESEWA_PRODUCT_CODE=EPAYTEST
ESEWA_SECRET_KEY=8gBm/:&EnhH.1/q
ESEWA_GATEWAY_URL=https://rc-epay.esewa.com.np/api/epay/main/v2/form
ESEWA_VERIFICATION_URL=https://rc-epay.esewa.com.np/api/epay/transaction/status/
ESEWA_SUCCESS_URL=http://localhost:8000/payment/esewa/success
ESEWA_FAILURE_URL=http://localhost:8000/payment/esewa/failure3. Creating the eSewa Payment Controller
<?php
namespace AppHttpControllers;
use IlluminateHttpRequest;
use IlluminateSupportFacadesHttp;
use IlluminateSupportStr;
class EsewaPaymentController extends Controller
{
/**
* Initiate checkout: generate signature and redirect to eSewa
*/
public function initiatePayment(Request $request)
{
$amount = 100.00; // Total amount in NPR
$taxAmount = 0.00;
$totalAmount = $amount + $taxAmount;
$transactionUuid = Str::uuid()->toString();
$productCode = config('services.esewa.product_code', env('ESEWA_PRODUCT_CODE'));
$secretKey = config('services.esewa.secret_key', env('ESEWA_SECRET_KEY'));
// Format: total_amount,transaction_uuid,product_code
$dataToSign = "total_amount={$totalAmount},transaction_uuid={$transactionUuid},product_code={$productCode}";
$signature = base64_encode(hash_hmac('sha256', $dataToSign, $secretKey, true));
$formData = [
'amount' => $amount,
'tax_amount' => $taxAmount,
'total_amount' => $totalAmount,
'transaction_uuid' => $transactionUuid,
'product_code' => $productCode,
'product_service_charge' => 0,
'product_delivery_charge' => 0,
'success_url' => env('ESEWA_SUCCESS_URL'),
'failure_url' => env('ESEWA_FAILURE_URL'),
'signed_field_names' => 'total_amount,transaction_uuid,product_code',
'signature' => $signature,
];
return view('payment.esewa_redirect', [
'gatewayUrl' => env('ESEWA_GATEWAY_URL'),
'formData' => $formData
]);
}
/**
* Handle payment success callback from eSewa
*/
public function paymentSuccess(Request $request)
{
$encodedData = $request->query('data');
if (!$encodedData) {
return redirect()->route('cart')->with('error', 'Invalid callback payload from eSewa.');
}
// Decode base64 response from eSewa
$decodedJson = base64_decode($encodedData);
$response = json_decode($decodedJson, true);
if (!$response || !isset($response['transaction_uuid'], $response['total_amount'])) {
return redirect()->route('cart')->with('error', 'Malformed payment confirmation payload.');
}
// Double check status server-side
$verifyRes = Http::get(env('ESEWA_VERIFICATION_URL'), [
'product_code' => env('ESEWA_PRODUCT_CODE'),
'total_amount' => $response['total_amount'],
'transaction_uuid' => $response['transaction_uuid'],
]);
if ($verifyRes->successful() && ($verifyRes->json('status') === 'COMPLETE' || $response['status'] === 'COMPLETE')) {
// Fulfill order in database atomically
// Order::where('transaction_uuid', $response['transaction_uuid'])->update(['status' => 'PAID']);
return view('payment.success', ['details' => $response]);
}
return redirect()->route('cart')->with('error', 'Payment verification failed with eSewa.');
}
public function paymentFailure(Request $request)
{
return view('payment.failure', ['message' => 'Transaction was cancelled or declined by eSewa.']);
}
}4. Exclude Payment Callback from CSRF Protection in Laravel 12
<?php
use IlluminateFoundationApplication;
use IlluminateFoundationConfigurationExceptions;
use IlluminateFoundationConfigurationMiddleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// Exclude eSewa and Khalti callback routes from CSRF verification
$middleware->validateCsrfTokens(except: [
'payment/esewa/*',
'payment/khalti/*',
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();✨ Expected Output & Verification
A frictionless checkout experience that redirects users to eSewa's test wallet, processes test payment, and redirects back with cryptographic proof and server status confirmation.
🧪 How to Test the Implementation
- 1Start your local server: 'php artisan serve'
- 2Trigger the checkout route in your browser: http://localhost:8000/payment/esewa
- 3Log into eSewa test sandbox using mobile '9841000000' and MPIN '1122'
- 4Enter test OTP '123456' to confirm transaction
- 5Verify that redirect lands on /payment/esewa/success with status COMPLETE
⚠️ Common Errors & How to Fix Them
Root Cause: Concatenation string does not match signed_field_names exactly, or floating points are formatted with trailing spaces.
Fix: Format amount strictly with number_format($amt, 2, '.', '') without thousands commas.
Root Cause: Laravel CSRF middleware rejecting incoming POST callbacks from eSewa servers.
Fix: Add 'payment/esewa/*' to validateCsrfTokens(except: [...]) in bootstrap/app.php.
🛡️ Security Considerations & Best Practices
- !Always query eSewa verification endpoint with your merchant credentials before updating order status.
- !Check that response['total_amount'] matches the recorded order amount to prevent client-side cart tampering.
❓ Frequently Asked Questions (FAQ)
Test eSewa ID: 9841000000, MPIN: 1122, Token/OTP: 123456. Product code is EPAYTEST.
📌 Summary of Key Takeaways
Integrating eSewa ePay v2 in Laravel 12 requires precise HMAC-SHA256 hash generation, bootstrap/app.php CSRF exemption, and server-side HTTP status verification.
Official References & Further Reading
Written by Manoj Neupane
AuthorSoftware developer and programming instructor, Nepal
Building software systems and SaaS platforms with Laravel, Next.js, and Node.js. Passionate about helping students and developers in Nepal master practical, real-world development skills.