User Guide
March 13, 2026 ยท View on GitHub
There are two ways to use this package:
Sujip\\Esewafor the framework-agnostic clientOmnipay\\Esewafor the Omnipay bridge
1. Framework-Agnostic Client
Create a client
<?php
declare(strict_types=1);
use Sujip\Esewa\Esewa;
$client = Esewa::make(
merchantCode: 'EPAYTEST',
secretKey: $_ENV['ESEWA_SECRET_KEY'],
environment: 'uat',
);
Build requests
You can pass plain strings to the request models. Internally, the SDK normalizes those values into typed objects.
use Sujip\Esewa\Domain\Checkout\CheckoutRequest;
$request = 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',
);
If you want stricter input handling in your own codebase, construct 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',
);
Convert models to arrays
Core models support toArray() and fromArray().
$payload = $request->toArray();
$restored = CheckoutRequest::fromArray($payload);
That is useful for:
- controller boundaries
- queue payloads
- config-driven integrations
- fixtures and tests
Create a checkout intent
$intent = $client->checkout()->createIntent($request);
$form = $intent->form();
Verify callbacks
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',
)
);
Handle verification states
switch ($result->state->value) {
case 'verified':
// continue
break;
case 'replayed':
http_response_code(409);
exit('Replay detected');
case 'invalid_signature':
http_response_code(400);
exit('Invalid signature');
}
Enable replay protection
For production, use persistent storage. The SDK ships with these store options:
FilesystemIdempotencyStorePdoIdempotencyStoreInMemoryIdempotencyStorefor tests
Filesystem example:
use Sujip\Esewa\Config\ClientOptions;
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'),
)
);
PDO example:
use PDO;
use Sujip\Esewa\Infrastructure\Idempotency\PdoIdempotencyStore;
$pdo = new PDO('sqlite:' . __DIR__ . '/storage/esewa.sqlite');
$store = new PdoIdempotencyStore($pdo);
Reconcile transaction state
use Sujip\Esewa\Domain\Transaction\TransactionStatusRequest;
$status = $client->transactions()->fetchStatus(new TransactionStatusRequest(
transactionUuid: 'TXN-1001',
totalAmount: '100.00',
productCode: 'EPAYTEST',
));
Customize retries
use Sujip\Esewa\Config\ClientOptions;
use Sujip\Esewa\Support\FixedDelayRetryPolicy;
$options = new ClientOptions(
retryPolicy: new FixedDelayRetryPolicy(
maxRetries: 3,
delayUs: 250000,
),
);
Use a custom transport
The default transport is CurlTransport, so the core stays zero-dependency at runtime. If your application already has a PSR-18 client, you can swap it in.
use Nyholm\Psr7\Factory\Psr17Factory;
use Sujip\Esewa\Infrastructure\Transport\Psr18Transport;
use Symfony\Component\HttpClient\Psr18Client;
$client = Esewa::make(
merchantCode: 'EPAYTEST',
secretKey: $_ENV['ESEWA_SECRET_KEY'],
transport: new Psr18Transport(new Psr18Client(), new Psr17Factory()),
environment: 'uat',
);
2. Omnipay Bridge
Use the bridge if the rest of your payment layer already follows Omnipay conventions.
Create the gateway
use Omnipay\Esewa\SecureGateway;
$gateway = new SecureGateway();
$gateway->setMerchantCode('EPAYTEST');
$gateway->setSecretKey($_ENV['ESEWA_SECRET_KEY']);
$gateway->setProductCode('EPAYTEST');
$gateway->setTaxAmount('0');
$gateway->setServiceCharge('0');
$gateway->setDeliveryCharge('0');
$gateway->setTestMode(true);
$gateway->setReturnUrl('https://merchant.example.com/esewa/success');
$gateway->setFailureUrl('https://merchant.example.com/esewa/failure');
Purchase flow
$response = $gateway->purchase([
'amount' => '100',
'transactionId' => 'TXN-1001',
])->send();
if ($response->isRedirect()) {
$redirectUrl = $response->getRedirectUrl();
$fields = $response->getRedirectData();
}
Complete purchase
$response = $gateway->completePurchase([
'transactionUuid' => 'TXN-1001',
'totalAmount' => '100.00',
'referenceNumber' => 'REF-1001',
])->send();
if ($response->isSuccessful()) {
$reference = $response->getTransactionReference();
}
Verify payment
$response = $gateway->verifyPayment([
'amount' => '100.00',
'transactionId' => 'TXN-1001',
])->send();
if ($response->isSuccessful()) {
$referenceId = $response->getReferenceId();
}
3. Production Guidance
- Keep secrets outside source control.
- Verify callbacks on the server before fulfillment.
- Compare verified values with your stored order state.
- Use persistent replay protection in production.
- Treat redirect success as customer UX, not payment proof.
- Reconcile ambiguous states with transaction status checks.
The common mistake here is trusting the browser redirect too early. eSewa can redirect a user back while your application still needs to verify the callback or reconcile the final state.
4. Error Handling
Core exceptions:
Sujip\\Esewa\\Exception\\EsewaExceptionSujip\\Esewa\\Exception\\InvalidPayloadExceptionSujip\\Esewa\\Exception\\SignatureExceptionSujip\\Esewa\\Exception\\FraudValidationExceptionSujip\\Esewa\\Exception\\TransportExceptionSujip\\Esewa\\Exception\\ApiErrorException