Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Accept Online Payments in Nepal: eSewa and Khalti Integration

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.

Nepal Payment Flow OverviewYour StoreLaravel / PHPGatewayeSewa or KhaltiCustomerWallet / AppOrder DBPaid statusSix-Step Checkout Sequence1. Create pending order + txn ID2. POST payment request to gateway3. Customer pays on gateway page4. Gateway redirects to success URL5. Server verifies via API6. Mark paid, send receiptNever skip step 5
Accept online payments in Nepal: eSewa and Khalti integration follows a redirect-pay-verify pattern every production store must enforce.

Merchant setup checklist

  1. Register at eSewa Merchant Portal and Khalti Merchant with business documents.
  2. Obtain sandbox keys first. Test full checkout before requesting live credentials.
  3. Configure success, failure, and webhook URLs on HTTPS endpoints only.
  4. Add both gateways if your audience spans urban wallet users and Khalti-heavy demographics.
  5. 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.

CriteriaeSewaKhalti
User baseLargest wallet; strong nationwide brand trustFast growth; popular with younger urban users
Integration styleForm POST redirect + status API verificationJSON API with pidx; server verification endpoint
Developer docsMerchant portal docs; PHP examples availableStructured REST docs at docs.khalti.com
SettlementBank settlement per merchant agreementBank settlement per merchant agreement
Best fitGeneral eCommerce, utilities, government-adjacent flowsApps, subscriptions, API-first Laravel backends
Also considerConnectIPS for direct bank debitIME 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.

Server Verification LayerBrowserUntrustedLaravel AppVerify + IdempotencyGateway APISource of truthMySQLOrder stateVerification Rules (Both Gateways)Match transaction UUID to pending orderConfirm amount equals order total in NPRCall gateway status API from serverUpdate status inside DB transactionReject if already paidQueue receipt email after commitLog raw API response for disputes
Laravel server-side verification is mandatory for eSewa and Khalti—browser redirects alone cannot confirm payment.

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.

Gateway Integration PathseSewa PathForm POST + HMAC signatureGET status verification APIAmount in rupees (decimal)Khalti PathJSON initiate + pidx tokenPOST lookup verification APIAmount in paisa (integer)Shared Production RequirementsHTTPS onlyUnique txn IDsServer verifyWebhook backupAudit logsIdempotent jobs
eSewa and Khalti use different API shapes but share the same server-side verification discipline for Nepal payments.

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 .env values 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 paid status.
  • 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:

  1. Successful payment with immediate redirect back to your site.
  2. Customer cancels on the gateway page and lands on your failure URL.
  3. Success page refresh does not duplicate fulfilment.
  4. Webhook arrives after redirect and still marks the order paid once.
  5. 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.

Gateway Selection Decision TreeNepal Online Store?Custom LaraveleSewa + KhaltiWooCommerceAudit pluginsB2B InvoicesAdd ConnectIPSGo-Live ChecklistLive merchant keys in .envSSL certificate validWebhooks registeredQueue worker runningPayment logs enabledRollback plan ready
Decision tree for Accept Online Payments in Nepal: eSewa and Khalti Integration plus ConnectIPS when bank debit is required.

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

Your site creates a payment session, sends the customer to eSewa or Khalti, then verifies status via gateway API before marking the order paid—never on browser redirect alone.

Complete merchant KYC with each provider using a registered business PAN, bank account details, and a live HTTPS domain. Store sandbox and production keys in .env, never Git. Your application creates a payment session with a unique transaction ID, redirects the customer to the hosted wallet page, receives a callback, and confirms payment through a server API call. The customer never enters wallet credentials on your domain, which keeps PCI scope low.

eSewa has the largest nationwide wallet base and uses form POST redirect plus status API verification with merchant portal docs and PHP examples. Khalti shows fast growth among younger urban users and offers structured JSON REST APIs using pidx and a server verification endpoint at docs.khalti.com. Both settle to your bank per merchant agreement. eSewa fits general eCommerce and government-adjacent flows; Khalti suits API-first Laravel backends. Picking only one often cuts conversion outside Kathmandu valley.

Offer both when average order value exceeds Rs 2,000 (~USD 15). Abandonment rises sharply when customers cannot find their preferred wallet.

Register at the eSewa Merchant Portal and Khalti Merchant with business documents. Obtain sandbox keys first and test the full checkout before requesting live credentials. Configure success, failure, and webhook URLs on HTTPS endpoints only. Log every transaction ID in your orders table before redirecting the customer. Add both gateways if your audience spans urban wallet users and Khalti-heavy demographics. Swap to live keys only after sandbox scenarios pass.

On Laravel 12 or 13 with PHP 8.3+, add gateway, transaction_uuid, gateway_ref, amount_paisa, and payment_status columns to orders. Generate a fresh UUID per checkout attempt—never reuse IDs on retries. Build a signed form payload with HMAC-SHA256 over total_amount, transaction_uuid, and product_code, then auto-submit via a Blade redirect view. On the success callback, call the eSewa status API, confirm status is COMPLETE, compare verified total_amount against the order total, then call markPaid() inside a database transaction with idempotency checks.

POST to Khalti initiate with your secret key, amount in paisa, purchase_order_id set to your transaction UUID, and return_url configured. Store the returned pidx as gateway_ref, redirect the customer to payment_url, then on return POST to the lookup endpoint with the same secret key. Confirm status is Completed and total_amount in paisa matches round(order total times 100). Off-by-two-decimal bugs are the most common Khalti failure I debug. Only then call markPaid() with idempotency protection.

Anyone can hit your success URL with a fabricated transaction_uuid. eSewa and Khalti both require server-side API verification before marking an order paid. Skipping amount checks is equally dangerous—compare verified gateway amount against your stored order total because tampered query strings have appeared on client sites. Send confirmation emails and fulfil orders only after the database transaction commits with paid status, not when the browser lands on success.

The gateway often worked; the application trusted the wrong signal. Typical causes include no webhook handler when mobile users close tabs early, trusting redirects without API verification, reusing transaction UUIDs on retries, mixing sandbox and live keys after merchant approval, or stale cached config still pointing to sandbox URLs. Run php artisan config:clear after every production key swap. Monitor failed verifications daily for the first two weeks and alert when error rates spike.

Trusting the success URL alone, reusing transaction IDs, mixing sandbox and live keys, skipping amount checks against order totals, having no webhook handler, fulfilling before verify completes, and missing VAT line items for B2B invoices in Nepal. On legal service sites collecting partial deposits, map deposit and balance payments to separate order rows or line items upfront—partial payment state machines get messy fast if bolted on after launch.

Yes. Redirects fail when customers close the browser early or lose connectivity before returning to your site. Register webhook endpoints with both gateways on HTTPS, process them in a queued job with signature validation, and handle them idempotently so a webhook arriving after redirect still marks the order paid once. On booking systems I have shipped, webhooks saved deposits that would otherwise stay pending overnight.

Khalti expects integer amounts in paisa. Rs 1,500 equals 150000 paisa.

Run successful payment with immediate redirect back, customer cancel landing on your failure URL, success page refresh not duplicating fulfilment or emails, webhook arriving after redirect still marking paid once, and amount mismatch returning a safe failure state rather than a paid order. Wire these into your deployment checklist alongside other pre-launch checks. Payment bugs are expensive—a Rs 3,000 order loss hurts, but fifty duplicate fulfilments hurt more.

WooCommerce 11.1 shops can use community gateway plugins, but audit them for server-side verification—not redirect trust alone. WordPress service sites should validate on init or REST callbacks, not admin-ajax alone. Magento 2.4.x stores need custom payment modules for reliable dual-gateway support. Shopify merchants face platform limits; workarounds exist but differ from the standard Laravel redirect-pay-verify pattern described here.

eSewa and Khalti remain the standard wallet stack for Laravel stores, booking portals, and service sites targeting Nepal customers. Add ConnectIPS when you need direct bank debit alongside wallets—useful for bank-to-bank flows merchants sometimes request. IME Pay adds a third wallet option for additional coverage. If your audience spans urban wallet users and Khalti-heavy demographics, supporting both primary wallets at checkout widens addressable market more than adding a single alternative alone.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: