Khalti
This module integrates Drupal Commerce with the Khalti ePay API to allow customers to pay online using Nepalese Rupees. It redirects customers to Khalti's secure payment page and verifies transactions server-side before updating the order status.
Integrates the Khalti ePay v2 API (KPG-2) with Drupal Commerce, enabling merchants to accept online payments in Nepalese Rupees (NPR) on Drupal 10 and 11 sites via an offsite redirect checkout flow.
Khalti is Nepal's leading digital wallet, payment gateway, and banking API provider. This Drupal module uses the Khalti ePay v2 Web Checkout (KPG-2) to redirect customers to a Khalti-hosted payment page, then verifies the payment server-side via the Khalti Lookup API before updating the order status.
Key design principles:
- Server-side payment initiation — the secret key never leaves your server.
- Lookup API verification on return — callback query parameters are never trusted alone.
- All seven Khalti payment statuses are handled explicitly.
- Orders are always unlocked after the redirect, regardless of outcome.
Supported Payment Methods
Method Notes Khalti wallet Available to all Khalti users eBanking Khalti-partnered banks only Mobile banking Khalti-partnered banks only SCT / VISA cards Requires separate approval from Khalti ConnectIPS Available to all ConnectIPS usersRequirements
Dependency Version Link Drupal core 10.x or 11.x drupal.org/project/drupal PHP 8.1 or higher php.net Drupal Commerce 3.x drupal.org/project/commerce Khalti merchant account — admin.khalti.com (live) / test-admin.khalti.com (sandbox)Currency: Your Commerce store must use NPR (Nepalese Rupee). Khalti only accepts NPR and requires amounts expressed in paisa (1 NPR = 100 paisa; minimum Rs. 10 = 1000 paisa).
Recommended Modules
- Commerce Cart and Commerce Checkout — included with Drupal Commerce; required for the standard checkout flow.
- Commerce Log — logs order state transitions for audit purposes.
Installation
Option A — Composer (recommended)
composer require drupal/khalti drush en khalti drush cr
Option B — Manual
- Download the module archive from drupal.org/project/khalti.
- Extract it to
modules/contrib/khalti(ormodules/custom/khaltifor a custom copy). - Go to Administration › Extend (
/admin/modules). - Find Khalti, tick the checkbox, and click Install.
- Run
drush cror clear caches at Administration › Performance.
Configuration
Step 1 — Enable NPR currency
- Go to Administration › Commerce › Configuration › Currencies (
/admin/commerce/config/currencies). - If NPR is not listed, click Add currency, search for Nepalese Rupee (NPR), and save.
Step 2 — Set store currency to NPR
- Go to Administration › Commerce › Configuration › Stores (
/admin/commerce/config/stores). - Edit your store and set the Default currency to NPR.
- Save.
Step 3 — Add a Khalti payment gateway
- Go to Administration › Commerce › Configuration › Payment Gateways (
/admin/commerce/config/payment-gateways). - Click Add payment gateway.
- Fill in the form:
Field Value Name Khalti (or any label) Plugin Khalti Payment Mode Test for sandbox · Live for production Test Secret Key Your sandboxlive_secret_keyfrom test-admin.khalti.com Live Secret Key Your productionlive_secret_keyfrom admin.khalti.com - Save.
Note: Khalti's
Authorizationheader uses the formatKey <secret_key>— notBearer. The module handles this automatically.
Step 4 — Verify the checkout flow
- Go to Administration › Commerce › Configuration › Checkout Flows (
/admin/commerce/config/checkout-flows). - Edit your checkout flow and confirm the Payment and Payment Process panes are present and in the correct order.
Sandbox Testing
The Khalti sandbox environment is completely isolated from production. No real money moves.
1. Get sandbox credentials
Sign up at test-admin.khalti.com/#/join/merchant or log in if you already have an account. Copy the live_secret_key from the sandbox merchant dashboard.
2. Configure the gateway in Test mode
Follow Step 3 of Configuration, set Mode → Test, and paste the sandbox key into Test Secret Key.
3. Place a test order
Add any product to the cart, proceed to checkout, and select Khalti as the payment method.
4. Sandbox payment credentials
Field Value(s) Khalti IDs 9800000000, 9800000001, 9800000002, 9800000003, 9800000004 MPIN 1111 Transaction OTP 987654 Login OTP 9876545. View transactions
Go to Administration › Commerce › Khalti ( /admin/commerce/khalti) to see all recorded transactions. You can view details, edit, or delete records from here.
Note: The Khalti payment link expires in 60 minutes in production (sandbox links may differ).
Going Live
- Create a production merchant account at admin.khalti.com.
- Complete the merchant agreement and KYC verification with the Khalti business team.
- The Khalti team will generate and share your production
live_secret_key. - In Drupal, edit your payment gateway, switch Mode → Live, and enter the production key in Live Secret Key.
- Place a real low-value test transaction (e.g. Rs. 10) end-to-end.
- Contact the Khalti business team to raise transaction limits and agree on fee slabs.
How It Works
Payment flow
- Customer adds a product to cart and proceeds to checkout.
- Customer selects Khalti as the payment method and clicks Pay.
- Module calls
POST /api/v2/epayment/initiate/server-side → receivespayment_urlandpidx. - Customer is redirected to
pay.khalti.com(ordev.khalti.comin sandbox). - Customer completes or cancels payment on the Khalti-hosted page.
- Khalti redirects back to
/khalti/success/{token}?pidx=…&status=… - Module calls
POST /api/v2/epayment/lookup/with thepidxto verify the payment status server-side. - Order and payment status are updated; customer is forwarded to the result page.
API endpoints
Endpoint Method Purpose/api/v2/epayment/initiate/
POST
Create a payment request; returns payment_url and pidx
/api/v2/epayment/lookup/
POST
Verify payment status by pidx — always call before granting service
-
Sandbox base URL:
https://dev.khalti.com -
Production base URL:
https://khalti.com
Amount conversion
Commerce stores prices in NPR. Khalti requires amounts as integer paisa:
amount_paisa = (int) round(order_total_NPR × 100)
Minimum accepted value: Rs. 10 → 1000 paisa.
Security
Measure Description CSRF token A random session token is generated at checkout initiation and verified when Khalti redirects back. Mismatched tokens return HTTP 403. Lookup API verification The module always calls the Lookup API server-side. Thestatus query parameter in the callback URL is never trusted alone.
Order unlock
Orders are unlocked after every redirect return (success, cancel, or error), preventing permanently locked orders.
Payment Statuses
All seven statuses returned by the Khalti Lookup API are handled:
Khalti Status Order State MeaningCompleted
Completed
Payment confirmed ✓ — safe to grant service
Pending
Pending
Not yet settled — do not grant service; contact Khalti support
Initiated
Cancelled
Customer never reached the Khalti payment page
User canceled
Cancelled
Customer pressed cancel on the Khalti page
Expired
Cancelled
Payment link timed out (60 min in production)
Refunded
Cancelled
Payment fully reversed by Khalti
Partially Refunded
Cancelled
Partial refund issued — do not grant full service
Rule: Only
Completedshould trigger order fulfilment.
Hooks for Developers
See khalti.api.php for full documentation of all available hooks.
hook_khalti_customer_info_alter(array &$customer_info)
Alter the customer information sent to Khalti during payment initiation. Blank values are stripped automatically before the API call.
Key Type Required Description<?php /** * Implements hook_khalti_customer_info_alter(). */ function mymodule_khalti_customer_info_alter(array &$customer_info): void { // Example: pull phone number from a custom customer profile field. $account = \Drupal::currentUser(); $profiles = \Drupal::entityTypeManager() ->getStorage('profile') ->loadByProperties(['uid' => $account->id(), 'type' => 'customer']); if ($profile = reset($profiles)) { $customer_info['phone'] = $profile->get('field_phone')->value ?? ''; } }
name
string
Yes
Customer full name
email
string
No
Customer email address
phone
string
No
Customer phone number
hook_khalti_success_message(string &$message)
Override the on-screen success message shown after a completed payment.
<?php /** * Implements hook_khalti_success_message(). */ function mymodule_khalti_success_message(string &$message): void { $message = 'Thank you! Your Khalti payment was received successfully.'; }
Troubleshooting
Always start by checking Administration › Reports › Recent log messages ( /admin/reports/dblog), filtered by type khalti.
"An error occurred while initiating Khalti payment"
Likely cause Solution Invalid secret key Verify the key in your Khalti dashboard. It must match the selected mode (test vs. live). Amount below minimum Ensure the order total is at least Rs. 10 (1000 paisa). Currency not NPR Set the store default currency to NPR. Server cannot reach Khalti API Check outbound firewall rules — HTTPS must be allowed todev.khalti.com and khalti.com.
"Your session has expired or is invalid"
The checkout session was lost between redirect and return.
- Ensure the PHP session lifetime is long enough (at least 10–15 minutes).
- If you use a CDN or reverse proxy, confirm session cookies are not stripped on the return URL path (
/khalti/success/*). - Ensure
trusted_host_patternsinsettings.phpallows the return URL host.
"Payment verification failed" / empty API response
- The Khalti API returned an empty body — check Drupal logs for the HTTP status code.
- In sandbox, confirm the
pidxbelongs to the current session;pidxvalues cannot be reused. - Verify the server clock is accurate — Khalti tokens are time-sensitive.
"This order is locked and cannot be edited"
Unlock from Administration › Commerce › Orders, or via Drush:
drush php:eval " \$order = \Drupal\commerce_order\Entity\Order::load(ORDER_ID); if (\$order && \$order->isLocked()) { \$order->unlock(); \$order->save(); echo 'Order ' . ORDER_ID . ' unlocked.'; } "
Payment shows as Pending indefinitely
Pending means Khalti has received the payment but it is not yet settled with the bank. Do not fulfil the order. Contact Khalti merchant support with the pidx and transaction_id from the Khalti Transactions admin page.
FAQ
-
Can I use multiple currencies?
- No. Khalti only accepts NPR. Multi-currency stores must convert totals to NPR before initiating a Khalti payment.
-
Is the customer redirected away from my site?
- Yes. Khalti ePay v2 is an offsite/hosted checkout. The customer leaves your site, pays on Khalti's page, and is redirected back.
-
What happens if the customer closes the browser on the Khalti page?
- The payment link expires after 60 minutes. The module receives a status of
ExpiredorInitiatedand cancels the order, allowing the customer to retry. -
Are refunds handled automatically?
- The module detects
RefundedandPartially Refundedfrom the Lookup API and cancels the order. Refund initiation must be done from the Khalti merchant dashboard. -
Where are Khalti transactions stored?
- Each verified payment creates a
KhaltiTransactionentity. View them at Administration › Commerce › Khalti (/admin/commerce/khalti). -
Is the module compatible with Commerce Recurring / Subscriptions?
- Not currently. Khalti ePay v2 does not support automatic recurring charges.
Contact Khalti
Channel Details Business enquiries [email protected] · +977 9801165557 Merchant technical support [email protected] · 9801301162 · 9801165565(Viber / WhatsApp) API documentation docs.khalti.com/khalti-epayment/ Sandbox admin test-admin.khalti.com Production admin admin.khalti.com