Migration Guide
March 13, 2026 ยท View on GitHub
What changed
The package now leans on request and result models instead of passing loose arrays and strings through every layer.
The main differences are:
- checkout, callback, and status flows are centered around domain models
- request models support
toArray()andfromArray() - core identifiers and money values are normalized into typed value objects
- callback verification returns an explicit state
- retry behavior can be configured as a policy
- replay protection can use persistent zero-dependency storage
Public Namespaces
- framework-agnostic client:
Sujip\\Esewa - Omnipay bridge:
Omnipay\\Esewa
Framework-Agnostic Client Migration
Earlier style
Older integrations mostly passed strings around and treated request objects as thin containers.
Current style
You can still pass strings to the constructors. The difference is that the models now normalize and validate those values more aggressively.
use Sujip\Esewa\Domain\Checkout\CheckoutRequest;
use Sujip\Esewa\Esewa;
$client = Esewa::make(
merchantCode: 'EPAYTEST',
secretKey: 'secret',
environment: 'uat',
);
$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 typing in your own code:
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',
);
Model conversion
Models support array conversion, which is useful at controller boundaries, in queued jobs, and in test fixtures.
$payload = $request->toArray();
$restored = CheckoutRequest::fromArray($payload);
The same pattern applies to:
VerificationExpectationTransactionStatusRequestCallbackPayloadTransactionStatusPayload
Callback verification
Verification now returns a richer result object.
$result = $client->callbacks()->verifyCallback($payload, $expectation);
Available states:
verifiedinvalid_signaturereplayed
That makes it easier to tell a bad signature from a replayed callback.
Retry and replay protection
Retry behavior is no longer limited to a couple of scalar options. You can provide a policy object instead.
use Sujip\Esewa\Config\ClientOptions;
use Sujip\Esewa\Support\FixedDelayRetryPolicy;
$options = new ClientOptions(
retryPolicy: new FixedDelayRetryPolicy(maxRetries: 3, delayUs: 250000),
);
Replay protection can use persistent storage:
FilesystemIdempotencyStorePdoIdempotencyStore
Omnipay migration
The bridge remains under Omnipay\\Esewa.
Supported methods:
purchase()completePurchase()verifyPayment()
Production checklist
- Keep
merchantCodeandsecretKeyin environment configuration. - Verify callbacks on the backend before fulfillment.
- Compare verified values against stored order state.
- Use filesystem or PDO-backed replay protection in production.
- Reconcile uncertain states with transaction status checks.
- Treat redirect success as a user-facing signal, not as proof of payment.