eSewa Integration with PHP, Nepal's first Online Payment Gateway

eSewa Payment Gateway Integration with Laravel

In this article, I will show you how to integrate eSewa API v1.0 with the Laravel framework. eSewa is Nepal's first popular online payment gateway helping businesses to accept their payments online.

As like a few other payment gateways integration with PHP, I've recently worked on a new package for eSewa, which is a used in the majority of eCommerce platform in Nepal. I got a chance to work for the client to accept their payment online.

I have first explored on google to know if anyone has worked to create a better PHP package for the gateway. I found there is no any good package for the payment gateway. So decided to create a new reusable library with unit testing. I always try to share the real-time example and solution for the working developers through this blog.

Below, I'll cover complete steps needed to integrate eSewa API using the package built on top of Omnipay library.

Before diving into the code, let me first share my experience on how to grab API keys for development, production environment.

While I went through their API docs, there are no test credentials given in there. eSewa API credentials are available to the merchant after contacting the eSewa office via phone call or email support.

Now, let's jump into the code after you have grabbed the API credentials to make API calls by using the package.

Coding

The library is framework agnostic, can be used in any custom project, not only with Laravel. Here, we're going to integrate it on top of the Laravel framework. If you're a Laravel developer, hopefully, you will benefit from it to save your time.

Also, read:
Omnipay PayPal Integration
PayPal Instant Payment Notification

Include the package with your project with a composer command.

composer require league/omnipay sudiptpa/omnipay-esewa

Routes

Route::get('/checkout/payment/esewa', [
    'name' => 'eSewa Checkout Payment',
    'as' => 'checkout.payment.esewa',
    'uses' => 'EsewaController@checkout',
]);

Route::post('/checkout/payment/{order}/esewa/process', [
    'name' => 'eSewa Checkout Payment',
    'as' => 'checkout.payment.esewa.process',
    'uses' => 'EsewaController@payment',
]);

Route::get('/checkout/payment/{order}/esewa/completed', [
    'name' => 'eSewa Payment Completed',
    'as' => 'checkout.payment.esewa.completed',
    'uses' => 'EsewaController@completed',
]);

Route::get('/checkout/payment/{order}/failed', [
    'name' => 'eSewa Payment Failed',
    'as' => 'checkout.payment.esewa.failed',
    'uses' => 'EsewaController@failed',
]);

Model

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Order
 * @package App
 */
class Order extends Model
{
    const PAYMENT_COMPLETED = 1;
    const PAYMENT_PENDING = 0;

    /**
     * @var string
     */
    protected $table = 'orders';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}

Esewa

<?php

namespace App;

use Exception;
use Omnipay\Omnipay;

/**
 * Class Esewa
 * @package App
 */
class Esewa
{
    /**
     * @return \SecureGateway
     */
    public function gateway()
    {
        $gateway = Omnipay::create('Esewa_Secure');

        $gateway->setMerchantCode(config('services.esewa.merchant'));
        $gateway->setTestMode(config('services.esewa.sandbox'));

        return $gateway;
    }

    /**
     * @param array $parameters
     * @return $response
     */
    public function purchase(array $parameters)
    {
        try {
            $response = $this->gateway()
                ->purchase($parameters)
                ->send();
        } catch (Exception $e) {
            throw new Exception($e->getMessage());
        }

        return $response;
    }

    /**
     * @param array $parameters
     * @return $response
     */
    public function verifyPayment(array $parameters)
    {
        $response = $this->gateway()
            ->verifyPayment($parameters)
            ->send();

        return $response;
    }

    /**
     * @param $amount
     */
    public function formatAmount($amount)
    {
        return number_format($amount, 2, '.', '');
    }

    /**
     * @param $order
     */
    public function getFailedUrl($order)
    {
        return route('checkout.payment.esewa.failed', $order->id);
    }

    /**
     * @param $order
     */
    public function getReturnUrl($order)
    {
        return route('checkout.payment.esewa.completed', $order->id);
    }
}

Controller

<?php

namespace App\Http\Controllers;

use App\Esewa;
use App\Order;
use Exception;
use Illuminate\Http\Request;

/**
 * Class EsewaController
 * @package App\Http\Controllers
 */
class EsewaController extends Controller
{
    /**
     * @param Request $request
     */
    public function checkout(Request $request)
    {
        $order = Order::findOrFail(mt_rand(1, 20));

        return view('esewa.checkout', compact('order'));
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function payment($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $gateway = with(new Esewa);

        try {
            $response = $gateway->purchase([
                'amount' => $gateway->formatAmount($order->amount),
                'totalAmount' => $gateway->formatAmount($order->amount),
                'productCode' => 'ABAC2098',
                'failedUrl' => $gateway->getFailedUrl($order),
                'returnUrl' => $gateway->getReturnUrl($order),
            ], $request);

        } catch (Exception $e) {
            $order->update(['payment_status' => Order::PAYMENT_PENDING]);

            return redirect()
                ->route('checkout.payment.esewa.failed', [$order->id])
                ->with('message', sprintf("Your payment failed with error: %s", $e->getMessage()));
        }

        if ($response->isRedirect()) {
            $response->redirect();
        }

        return redirect()->back()->with([
            'message' => "We're unable to process your payment at the moment, please try again !",
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function completed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $gateway = with(new Esewa);

        $response = $gateway->verifyPayment([
            'amount' => $gateway->formatAmount($order->amount),
            'referenceNumber' => $request->get('refId'),
            'productCode' => $request->get('oid'),
        ], $request);

        if ($response->isSuccessful()) {
            $order->update([
                'transaction_id' => $request->get('refId'),
                'payment_status' => Order::PAYMENT_COMPLETED,
            ]);

            return redirect()->route('checkout.payment.esewa')->with([
                'message' => 'Thank you for your shopping, Your recent payment was successful.',
            ]);
        }

        return redirect()->route('checkout.payment.esewa')->with([
            'message' => 'Thank you for your shopping, However, the payment has been declined.',
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function failed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        return view('esewa.checkout', compact('order'));
    }
}

HTML

@extends('default')

@section('content')
    <div class="title m-b-md">
        eSewa Checkout
    </div>

    <div class="links">

        @if($message = session('message'))
            <p>
                {{ $message }}
            </p>
        @endif

        <p>
            <strong>QuietComfort® 25 Acoustic Noise Cancelling® headphones — Apple devices</strong>
        </p>

        <br>

        <form method="POST" action="{{ route('checkout.payment.esewa.process', $order->id) }}">

            @csrf

            <button class="btn btn-primary" type="submit">
                ${{ $order->amount }} Pay with eSewa
            </button>
        </form>
    </div>
@stop

Code

The sample code is available on github example repository.

Conclusion

Thanks for following up this article up to the end. If you have any feedback for me, feel free to leave your comment. If you think you have found a bug, please reach us via email or create an issue on github. Do not forget to share if you think worthreading and sharing this article.

Happy Coding!