Framework-agnostic, zero-dependency PHP SDK for eSewa ePay v2, with an optional Omnipay v3 bridge.
This package exposes two public namespaces:
Sujip\\EsewaOmnipay\\Esewa
- checkout, callback, and transaction flows built around request and result models
- typed value objects for amount, transaction UUID, product code, and reference ID
toArray()andfromArray()where they are useful- callback verification with explicit states:
verified,invalid_signature,replayed - zero-dependency core with built-in
CurlTransport - configurable retry policy and clock handling
- replay protection backed by filesystem or PDO storage
- optional PSR-18 transport support
- optional Omnipay v3 bridge
- PHP
8.2to8.5
composer require sudiptpa/omnipay-esewaOptional PSR-18 usage:
composer require symfony/http-client nyholm/psr7<?php
declare(strict_types=1);
use Sujip\Esewa\Esewa;
use Sujip\Esewa\Domain\Checkout\CheckoutRequest;
$client = Esewa::make(
merchantCode: 'EPAYTEST',
secretKey: $_ENV['ESEWA_SECRET_KEY'],
environment: 'uat',
);
$intent = $client->checkout()->createIntent(new CheckoutRequest(
amount: '100',
taxAmount: '0',
serviceCharge: '0',
deliveryCharge: '0',
transactionUuid: 'TXN-1001',
productCode: 'EPAYTEST',
successUrl: 'https://merchant.example.com/esewa/success',
failureUrl: 'https://merchant.example.com/esewa/failure',
));The request constructor accepts strings for convenience. Internally, those values are normalized into value objects. If you want stricter typing in your own code, build the value objects directly:
use Sujip\Esewa\Domain\Checkout\CheckoutRequest;
use Sujip\Esewa\ValueObject\Amount;
use Sujip\Esewa\ValueObject\ProductCode;
use Sujip\Esewa\ValueObject\TransactionUuid;
$request = new CheckoutRequest(
amount: Amount::fromString('100'),
taxAmount: Amount::fromString('0'),
serviceCharge: Amount::fromString('0'),
deliveryCharge: Amount::fromString('0'),
transactionUuid: TransactionUuid::fromString('TXN-1001'),
productCode: ProductCode::fromString('EPAYTEST'),
successUrl: 'https://merchant.example.com/esewa/success',
failureUrl: 'https://merchant.example.com/esewa/failure',
);The core models can be converted to arrays. That is mainly useful when you are crossing controller boundaries, queueing work, or saving fixtures for tests.
$payload = $request->toArray();
$restored = CheckoutRequest::fromArray($payload);The client stays small. Most integrations only need three modules:
$client->checkout()$client->callbacks()$client->transactions()
use Sujip\Esewa\Domain\Verification\CallbackPayload;
use Sujip\Esewa\Domain\Verification\VerificationExpectation;
$payload = CallbackPayload::fromArray([
'data' => $_GET['data'] ?? '',
'signature' => $_GET['signature'] ?? '',
]);
$result = $client->callbacks()->verifyCallback(
$payload,
new VerificationExpectation(
totalAmount: '100.00',
transactionUuid: 'TXN-1001',
productCode: 'EPAYTEST',
)
);
if ($result->state->value === 'replayed') {
http_response_code(409);
exit('Replay detected');
}
if (!$result->isSuccessful()) {
http_response_code(400);
exit('Invalid callback');
}Do not treat the success redirect alone as proof of payment. Verify the callback on your backend and keep a status check as a fallback when something looks off.
use Sujip\Esewa\Domain\Transaction\TransactionStatusRequest;
$status = $client->transactions()->fetchStatus(new TransactionStatusRequest(
transactionUuid: 'TXN-1001',
totalAmount: '100.00',
productCode: 'EPAYTEST',
));
if ($status->isSuccessful()) {
// mark paid
}For live traffic, turn on replay protection and use persistent storage:
use Sujip\Esewa\Config\ClientOptions;
use Sujip\Esewa\Esewa;
use Sujip\Esewa\Infrastructure\Idempotency\FilesystemIdempotencyStore;
$client = Esewa::make(
merchantCode: 'EPAYTEST',
secretKey: $_ENV['ESEWA_SECRET_KEY'],
environment: 'uat',
options: new ClientOptions(
preventCallbackReplay: true,
idempotencyStore: new FilesystemIdempotencyStore(__DIR__ . '/storage/esewa-idempotency'),
),
);Retry behavior is also configurable:
use Sujip\Esewa\Config\ClientOptions;
use Sujip\Esewa\Support\FixedDelayRetryPolicy;
$options = new ClientOptions(
retryPolicy: new FixedDelayRetryPolicy(
maxRetries: 3,
delayUs: 250000,
),
);use Omnipay\Esewa\SecureGateway;
$gateway = new SecureGateway();
$gateway->setMerchantCode('EPAYTEST');
$gateway->setSecretKey($_ENV['ESEWA_SECRET_KEY']);
$gateway->setProductCode('EPAYTEST');
$gateway->setTestMode(true);
$gateway->setReturnUrl('https://merchant.example.com/esewa/success');
$gateway->setFailureUrl('https://merchant.example.com/esewa/failure');Supported bridge flows:
purchase()completePurchase()verifyPayment()
composer test
composer stan
composer rector:check