DEV Community

Pemba Sherpa
Pemba Sherpa

Posted on

Step-by-Step Guide to Integrate eSewa EPAY v2 with Laravel and Livewire

This blog guides you through integrating eSewa v2 into your Laravel application using Livewire. I assume you already have basic knowledge of Livewire, and that Laravel and Livewire are installed and set up.

Step 1: Create the eSewa Configuration File

First, create a config file (e.g. config/esewa.php) where you can add your eSewa product code, secret key, production URL, and sandbox URL. Make sure to add these values to your .env file as well.

<?php

declare(strict_types=1);

return [
    'product_code' => env('ESEWA_PRODUCT_CODE'),
    'secret' => env('ESEWA_SECRET'),
    'production_url' => env('ESEWA_PRODUCTION_URL'),
    'sandbox_url' => env('ESEWA_SANDBOX_URL'),
];

Enter fullscreen mode Exit fullscreen mode

Step 2: Create Your Checkout Component

Assuming you already have a Livewire Checkout Component, you will handle the fields required for the eSewa checkout page inside it. You can name the method anything you like to process this logic; in my case, I used a method called save().

According to eSewa documentation there are a few important fields to keep in mind such as transaction_uuid and signature. The transaction_uuid needs be unique for every transaction.

Step 3: Create the Esewa Helper Class (Optional)

To keep your code clean, I created a helper class Esewa.php that contains reusable logic like fetching credentails and generating signatures.

<?php

declare(strict_types=1);

namespace App\Helpers;

use App\Models\Client;

class Esewa
{
    public static function generateSignature(array $fields): string
    {
        $signatureMessage = collect($fields)->map(fn ($value, $key): string => "{$key}={$value}")->implode(',');

        return base64_encode(hash_hmac('sha256', $signatureMessage, config('esewa.secret'), true));
    }

    public static function getCredentials(): array
    {
        $productCode = config('esewa.product_code');
        $secret = config('esewa.secret');

        return [
            'product_code' => $productCode,
            'secret' => $secret,
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

Signature Explanation

According to eSewa's documentation, to generate the signature, you need to includes these fields along with their values: total_amount, transaction_uuid, and product_code.

For example:

use App\Helpers\Esewa;

['product_code' => $productCode,'secret' => $secret] = Esewa::getCredentials();

$signatureFields = [
  'total_amount' => $this->cart->total,
  'transaction_uuid' => $transactionUuid,
  'product_code' => $productCode,
];

$signature = Esewa::generateSignature($signatureFields, $secret);

Enter fullscreen mode Exit fullscreen mode

In the code, I created an array $signatureFields containing the required fields for the signature along with their values and passed it to the generateSignature function.

The signature message should look like this:

total_amount=100,transaction_uuid=128481264,product_code=EPAYTEST

Enter fullscreen mode Exit fullscreen mode

After that, you need to hash the string using HMAC-SHA256 and then encode it in base64. This process is implemented in the generateSignature function.

Step 4: Creating the form fields for Esewa Checkout Page

All the required fields are mentioned in the eSewa documentation. Please refer to the official documentation here.

 $data = [
        'amount' => $this->cart->sub_total,
        'tax_amount' => $this->cart->vat,
        'total_amount' => $this->cart->total,
        'transaction_uuid' => $transactionUuid,
        'product_code' => $productCode,
        'product_service_charge' => $this->cart->service_charge,
        'product_delivery_charge' => 0,
        'success_url' => route('esewa.success'),
        'failure_url' => route('esewa.failure'),
        'signed_field_names' => 'total_amount,transaction_uuid,product_code',
        'signature' => $signature,
    ];

$this->dispatch('esewa-form-submit', data: $data);

Enter fullscreen mode Exit fullscreen mode

Step 5: Handling the eSewa Form Submission Using JavaScript

eSewa does not support backend checkout directly. Instead, you must create an HTML form with the required fields and submit it to eSewa’s URL. To achieve this, we will use JavaScript to dynamically create and submit the form.

To avoid exposing JS logic directly in your Blade templates, I created a JavaScript module esewa-form.js in resources/js:

export default () => ({
    formSubmit(data, isProduction = false){
        const form = document.createElement('form');
        form.method = 'POST';
        form.action = isProduction 
            ? 'https://epay.esewa.com.np/api/epay/main/v2/form'
            : 'https://rc-epay.esewa.com.np/api/epay/main/v2/form';

        Object.entries(data).forEach(([key, value]) => {
            const input = document.createElement('input');
            input.type = 'hidden';
            input.id = key;
            input.name = key;
            input.value = value;
            form.appendChild(input);
        });

        document.body.appendChild(form);
        form.submit();
    }
});

Enter fullscreen mode Exit fullscreen mode

Register this JS file in your main app.js file:

import './bootstrap';
import esewaForm from './esewa-form';

document.addEventListener('alpine:init', () => {
    Alpine.data('esewa', esewaForm);
});

Enter fullscreen mode Exit fullscreen mode

Step 6: Listen for the Emit Event in Blade

In your checkout.blade.php, add a <div> and use Alpine.js to listen for the Livewire esewa-form-submit event:

<div x-data="esewa" @esewa-form-submit.window="formSubmit($event.detail.data)">
  <!-- Your checkout form goes here -->
</div>
Enter fullscreen mode Exit fullscreen mode

Important: Don't forget to include Livewire’s @livewireScriptConfig as explained in the Livewire docs. Without it, it doesn't work.

Step 7: Handling the Payment Success Response

After payment completion, eSewa redirects to your success route with a base64-encoded response string. You need to decode it and verify the signature:

$transactionResponse = json_decode(base64_decode($request->data), true);

Enter fullscreen mode Exit fullscreen mode

A typical response looks like:

{
  "transaction_code": "000AWEO",
  "status": "COMPLETE",
  "total_amount": 1000.0,
  "transaction_uuid": "250610-162413",
  "product_code": "EPAYTEST",
  "signed_field_names": "transaction_code,status,total_amount,transaction_uuid,product_code,signed_field_names",
  "signature": "62GcfZTmVkzhtUeh+QJ1AqiJrjoWWGof3U+eTPTZ7fA="
}

Enter fullscreen mode Exit fullscreen mode

According to the eSewa documentation, it is necessary to verify the response signature by generating a new signature from the response data and comparing it with the one provided in the response. To verify the response signature, you generate a signature from the response data excluding the signature field:

use Illuminate\Support\Arr;

$signatureFields = Arr::except($transactionResponse, ['signature']);

$signature = Esewa::generateSignature($signatureFields, config('esewa.secret'));

if ($signature !== $transactionResponse['signature']) {
    // Cancel the transaction due to signature mismatch
    return ;
}

Enter fullscreen mode Exit fullscreen mode

Step 8: Check Payment Status Using eSewa API

Once the signature is verified, it is also necessary to confirm the payment status. To do this, we create an service EsewaService.php file that calls eSewa’s payment status API.

<?php

declare(strict_types=1);

namespace App\Services;

use Illuminate\Support\Facades\Http;

class EsewaService
{
    public static function paymentStatusCheck(array $data): array
    {
        $url = match (app()->environment()) {
            'production' => config('esewa.production_url'),
            default => config('esewa.sandbox_url')
        } . 'transaction/status/?';

        $urlWithQueryParams = $url . http_build_query([
            'product_code' => $data['product_code'],
            'total_amount' => $data['total_amount'],
            'transaction_uuid' => $data['transaction_uuid'],
        ]);

        $response = Http::get($urlWithQueryParams)->json();

        return $response;
    }
}

Enter fullscreen mode Exit fullscreen mode

Call this service after verifying the signature:

$statusResponse = EsewaService::paymentStatusCheck($transactionResponse);

if ($statusResponse['status'] === 'COMPLETE') {
    // Payment successful, proceed with creating order
}

Enter fullscreen mode Exit fullscreen mode

Bonus Tip:

If you want to store customer information (like name, email, and contact) when they submit the checkout form, you can use session to store the data and clear it after payment success or failure.

If you are working with a team, consider using Data Transfer Objects (DTOs) to clearly define response fields with their types.

If you have any questions or would like me to share a GitHub repo or full working example, feel free to reach out or leave a comment.

Happy coding! 🚀

Top comments (0)