
September 10, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
Nepal customers expect to pay with eSewa or Khalti at checkout. If your site only shows bank transfer details, you lose sales before the order form loads. Accept Online Payments in Nepal: eSewa and Khalti Integration is the standard stack for Laravel stores, booking portals, and service sites I ship for Nepal clients. This guide covers merchant onboarding, redirect flows, server-side verification, and the production mistakes that cause paid orders to stay pending. For deeper Laravel-specific walkthroughs, see the Laravel eSewa and Khalti payment integration guide.
How do you accept online payments in Nepal with eSewa and Khalti?
Both gateways follow the same broad pattern. Your application creates a payment session, sends the customer to eSewa or Khalti, receives a callback, and confirms status through a server API call. The customer never enters wallet credentials on your domain. That separation keeps PCI scope low and matches how e-commerce development in Nepal projects are usually architected.
Before writing code, complete merchant KYC with each provider. You will need a registered business PAN, bank account details, and a live HTTPS domain. Sandbox credentials differ from production keys. Store them in .env, never in Git.
Merchant setup checklist
- Register at eSewa Merchant Portal and Khalti Merchant with business documents.
- Obtain sandbox keys first. Test full checkout before requesting live credentials.
- Configure success, failure, and webhook URLs on HTTPS endpoints only.
- Add both gateways if your audience spans urban wallet users and Khalti-heavy demographics.
- Log every transaction ID in your orders table before redirecting the customer.
On legal-tech portals like Notary Nepal, I store gateway choice on the order row. The customer picks eSewa or Khalti at checkout. One controller handles both providers through a shared interface. That pattern keeps reporting simple when finance asks for monthly totals per gateway.
What is the difference between eSewa and Khalti for merchants?
Both wallets dominate Nepal digital payments, but their APIs, fee structures, and developer docs differ. Supporting both widens your addressable market. Picking only one often cuts conversion on service sites outside the Kathmandu valley.
| Criteria | eSewa | Khalti |
|---|---|---|
| User base | Largest wallet; strong nationwide brand trust | Fast growth; popular with younger urban users |
| Integration style | Form POST redirect + status API verification | JSON API with pidx; server verification endpoint |
| Developer docs | Merchant portal docs; PHP examples available | Structured REST docs at docs.khalti.com |
| Settlement | Bank settlement per merchant agreement | Bank settlement per merchant agreement |
| Best fit | General eCommerce, utilities, government-adjacent flows | Apps, subscriptions, API-first Laravel backends |
| Also consider | ConnectIPS for direct bank debit | IME Pay for additional wallet coverage |
For a wider comparison across all Nepal gateways, read the eCommerce payment gateway options for Nepal compared article. If you need bank-to-bank flows alongside wallets, ConnectIPS integration for bank payments covers that path separately.
When to offer both at checkout
Offer both when average order value exceeds Rs 2,000 (~USD 15). Abandonment rises sharply when a customer cannot find their preferred wallet. For low-ticket digital products, a single gateway may suffice until volume justifies dual integration maintenance.
How do you integrate eSewa payment gateway in Laravel?
Laravel 12 or 13 on PHP 8.3+ is my default stack for Nepal payment work. The eSewa flow starts when your controller creates a pending order and builds a signed form payload. eSewa posts the customer to their hosted payment page. After payment, eSewa redirects back with query parameters you must verify server-side.
Detailed file-level steps live in the eSewa integration guide for PHP apps. Below is the production skeleton I reuse across client projects.
Database and environment
Add columns to your orders table: gateway, transaction_uuid, gateway_ref, amount_paisa, payment_status. Generate a unique UUID per checkout attempt. Never reuse IDs across retries.
# .env — never commit these
ESEWA_MERCHANT_CODE=EPAYTEST
ESEWA_SECRET_KEY=your_secret
ESEWA_SUCCESS_URL=https://example.com/payment/esewa/success
ESEWA_FAILURE_URL=https://example.com/payment/esewa/failure
ESEWA_VERIFY_URL=https://rc.esewa.com.np/api/epay/transaction/status/ Controller: initiate payment
public function payWithEsewa(Order $order)
{
$txnUuid = (string) Str::uuid();
$amount = number_format($order->total, 2, '.', '');
$order->update([
'gateway' => 'esewa',
'transaction_uuid' => $txnUuid,
'payment_status' => 'pending',
]);
$payload = [
'amount' => $amount,
'tax_amount' => '0',
'total_amount' => $amount,
'transaction_uuid' => $txnUuid,
'product_code' => config('services.esewa.merchant_code'),
'product_service_charge' => '0',
'product_delivery_charge' => '0',
'success_url' => route('payment.esewa.success'),
'failure_url' => route('payment.esewa.failure'),
'signed_field_names' => 'total_amount,transaction_uuid,product_code',
];
$payload['signature'] = base64_encode(hash_hmac(
'sha256',
"total_amount={$amount},transaction_uuid={$txnUuid},product_code={$payload['product_code']}",
config('services.esewa.secret_key'),
true
));
return view('payments.esewa-redirect', compact('payload'));
} The Blade redirect view auto-submits a hidden form to the eSewa endpoint. Customers should not see an intermediate page longer than a flash. On grocery sites like Quick And Easy Nepalese Grocery, slow redirects directly correlate with cart abandonment.
Controller: verify on success callback
public function esewaSuccess(Request $request)
{
$data = $request->validate([
'transaction_uuid' => 'required|uuid',
'total_amount' => 'required|numeric',
]);
$order = Order::where('transaction_uuid', $data['transaction_uuid'])->firstOrFail();
$verifyUrl = config('services.esewa.verify_url') . '?' . http_build_query([
'product_code' => config('services.esewa.merchant_code'),
'total_amount' => $data['total_amount'],
'transaction_uuid' => $data['transaction_uuid'],
]);
$response = Http::get($verifyUrl);
$body = $response->json();
if (($body['status'] ?? '') !== 'COMPLETE') {
return redirect()->route('checkout.failed');
}
if ((float) $body['total_amount'] !== (float) $order->total) {
Log::warning('eSewa amount mismatch', ['order' => $order->id]);
return redirect()->route('checkout.failed');
}
$order->markPaid('esewa', $body['transaction_code'] ?? null);
return redirect()->route('orders.show', $order);
} Wrap markPaid() in a database transaction with an idempotency check. If the customer refreshes the success page, you must not double-fulfil or send duplicate emails. I have seen this bug on more than one live store.
How do you verify Khalti payments on your server?
Khalti uses a cleaner JSON API than eSewa. You initiate payment with a POST request, receive a pidx, redirect the customer to payment_url, then verify with your secret key. The Khalti integration guide for Laravel apps walks through package options. Here is the core flow I use on production Laravel 12 apps.
Initiate Khalti payment
public function payWithKhalti(Order $order)
{
$txnUuid = (string) Str::uuid();
$order->update([
'gateway' => 'khalti',
'transaction_uuid' => $txnUuid,
'payment_status' => 'pending',
]);
$amountPaisa = (int) round($order->total * 100);
$response = Http::withToken(config('services.khalti.secret_key'))
->post('https://khalti.com/api/v2/epayment/initiate/', [
'return_url' => route('payment.khalti.return'),
'website_url' => config('app.url'),
'amount' => $amountPaisa,
'purchase_order_id' => $txnUuid,
'purchase_order_name' => 'Order #' . $order->id,
]);
if (! $response->successful()) {
throw new PaymentInitiationException($response->body());
}
$pidx = $response->json('pidx');
$order->update(['gateway_ref' => $pidx]);
return redirect($response->json('payment_url'));
} Verify after customer return
public function khaltiReturn(Request $request)
{
$pidx = $request->query('pidx');
$order = Order::where('gateway_ref', $pidx)->firstOrFail();
$lookup = Http::withToken(config('services.khalti.secret_key'))
->post('https://khalti.com/api/v2/epayment/lookup/', ['pidx' => $pidx])
->json();
if (($lookup['status'] ?? '') !== 'Completed') {
return redirect()->route('checkout.failed');
}
if ((int) $lookup['total_amount'] !== (int) round($order->total * 100)) {
Log::warning('Khalti amount mismatch', ['order' => $order->id]);
return redirect()->route('checkout.failed');
}
$order->markPaid('khalti', $lookup['transaction_id'] ?? null);
return redirect()->route('orders.show', $order);
} Amounts for Khalti are in paisa. A Rs 1,500 order is 150000 paisa. Off-by-two-decimal bugs are the most common Khalti integration failure I debug for clients. Use the Nepal forex rates tool when displaying USD equivalents beside NPR totals on international-facing stores.
Webhooks for delayed confirmation
Redirects fail when customers close the browser early. Register webhook endpoints with both gateways. Process them in a queued job with signature validation. The dedicated webhooks for Nepal payment gateways article covers retry logic and idempotent handlers. On booking systems like Adventure Third Pole Trek, webhooks saved deposits that would otherwise stay pending overnight.
What are common mistakes when integrating Nepal payment gateways?
Most failed integrations I troubleshoot are not API bugs. They are process gaps. The gateway worked. The application trusted the wrong signal or skipped a guard clause.
- Trusting the success URL alone. Anyone can hit
/payment/success?transaction_uuid=…. Always verify with the gateway API. - Reusing transaction IDs. Generate a fresh UUID per attempt. Retries on the same ID cause reconciliation nightmares.
- Mixing sandbox and live keys. A classic post-deploy failure after merchant approval. Use separate
.envvalues per environment. - Skipping amount checks. Compare verified gateway amount against your order total. Tampered query strings have appeared on client sites.
- No webhook handler. Mobile users close tabs. Without webhooks, paid orders sit in pending for hours.
- Fulfilling before verify. Send confirmation emails only after the database transaction commits with
paidstatus. - Missing VAT line items. For B2B invoices in Nepal, store gross amount and tax breakdown separately. Finance teams need clean exports.
Legal service sites such as Court Marriage In Nepal often collect partial deposits. Map deposit and balance payments to separate order rows or line items. Partial payment state machines get messy fast if you bolt them on after launch.
Testing before go-live
Run these scenarios in sandbox before switching live keys:
- Successful payment with immediate redirect back to your site.
- Customer cancels on the gateway page and lands on your failure URL.
- Success page refresh does not duplicate fulfilment.
- Webhook arrives after redirect and still marks the order paid once.
- Amount mismatch returns a safe failure state, not a paid order.
Wire this into your deployment checklist alongside testing and optimization services. Payment bugs are expensive at scale. A Rs 3,000 order loss hurts. Fifty duplicate fulfilments hurt more.
Platform-specific notes
Not every store runs custom Laravel. WooCommerce 11.1 shops can use community gateway plugins, but I still audit them for server verification. Magento 2.4.x stores need custom payment modules for reliable dual-gateway support. Shopify merchants face platform limits; the Shopify custom payment gateway for Nepal post explains workarounds. WordPress service sites should validate on init or REST callbacks, not admin-ajax alone.
For broader payment architecture across providers, see Laravel payment integrations and the Nepal digital payment landscape 2026 overview. IME Pay adds a third wallet option covered in IME Pay integration for Nepal businesses.
Nepal Rastra Bank oversees payment system operators in the country. Merchants should keep settlement records aligned with bank statements. Dispute handling for delayed credits is covered in handling payment delays legally in Nepal. For API-first builds, API development in Nepal covers webhook endpoints and mobile app backends that consume the same payment services.
After go-live, monitor failed verifications daily for the first two weeks. Set alerts when verification error rates spike. Most issues trace to expired sandbox URLs still referenced in cached config. Run php artisan config:clear after every production key swap. Schedule ongoing checks through support and maintenance in Nepal if your team lacks dedicated DevOps.
Key Takeaways
- Register eSewa and Khalti merchant accounts, test in sandbox, then swap live keys on HTTPS only.
- Create a unique transaction UUID per checkout attempt and store it before redirecting the customer.
- Verify every payment through the gateway API on your server—never trust the browser success URL alone.
- Compare verified amounts against order totals; Khalti uses paisa, eSewa uses decimal rupees.
- Register webhooks and process them in queued, idempotent jobs to catch mobile drop-offs.
- Run five sandbox scenarios—including refresh and webhook-only paths—before accepting real NPR.
People Also Ask
Do I need both eSewa and Khalti on my website?
Not strictly, but offering both increases conversion on Nepal-facing stores. Many customers prefer one wallet and will abandon checkout if it is missing. For high-volume eCommerce, dual integration pays for itself quickly.
How long does eSewa or Khalti merchant approval take?
Timelines vary by document completeness and provider workload. Plan one to three weeks for KYC review. Build and test your full integration in sandbox while waiting so you can go live the day keys arrive.
Can I accept eSewa and Khalti payments without Laravel?
Yes. Plain PHP, WordPress, and Magento all support these gateways through custom code or plugins. The redirect-and-verify pattern is the same regardless of framework. Laravel simply makes HTTP clients, queues, and env config cleaner.
What fees do eSewa and Khalti charge merchants?
Merchant discount rates depend on your agreement, transaction volume, and business category. Confirm current rates directly with each provider during onboarding. Display NPR prices clearly at checkout so customers see the final amount before redirect.
Ship Nepal payments with confidence
Accept Online Payments in Nepal: eSewa and Khalti Integration is straightforward when you treat gateway APIs as the source of truth. Build pending orders first, redirect second, verify third, fulfil last. That sequence has held up across legal portals, grocery carts, and booking systems I have shipped since 2010. If you want dual-gateway checkout wired into a Laravel or WooCommerce store without reconciliation surprises, custom software development is the fastest path from sandbox to live NPR. Contact us with your stack and monthly order volume—we will map the integration before a single rupee hits production.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

