Files
django-nepali-payments/examples/README.md
T

7.2 KiB

Django example project

A ready-to-run Django application demonstrating how to integrate every gateway in django-nepali-payment the way you would in a real project, through views, models, URL callbacks and a management command, rather than as standalone scripts.

Component Purpose
manage.py / example_project/ A minimal Django project (settings, urls, wsgi/asgi).
payments/models.py An Order model storing the payment lifecycle (status, provider ref, raw payload).
payments/views.py create_order (initiate), callback (provider return), verify_order (re-check) for Khalti, eSewa, Fonepay and ConnectIPS.
payments/urls.py Routed pages for creating payments and receiving provider callbacks.
payments/management/commands/monitor_fonepay.py Background worker that polls Fonepay for settlements (shared-hosting safe).
.env.sample Credential template, copy to .env and fill in your keys.

Run it

From this directory, using uv:

# create the venv and install the library + example deps
uv sync

# copy the credential template and fill in your keys
cp .env.sample .env

# create migrations from the Order model and set up the database
uv run python manage.py makemigrations payments
uv run python manage.py migrate

# run the dev server
uv run python manage.py runserver

Migrations for the example payments app are generated on the fly (they're not committed to the repo), so run makemigrations the first time to create the Order table.

Open http://127.0.0.1:8000/ to see the home page and start payments.

How the flow works per gateway

All follow the same pattern in views.py:

  1. create_order builds the gateway request and calls PaymentManager.initiate_payment(...).
  2. Khalti / eSewa the customer is redirected to result.data.payment_url (the provider's hosted page).
    • Khalti calls back with ?pidx= to /callback/khalti/.
    • eSewa calls back with a base64 ?data= to /callback/esewa/.
  3. Fonepay result.data.qr_message is shown for scanning; settlement is watched by the background monitor (below).
  4. ConnectIPS result.data.form_fields is rendered as a hidden form that posts to result.data.target_url (the bank's sign-in page). The demo ships a simulated multi-step checkout (login → select bank → confirm) that reports back to /callback/connectips/ the same way a real session would.

The Order row is updated as the payment moves through pending -> initiated -> paid / failed / timeout.

Which order you can "Verify" after the fact: Khalti re-checks server-side by pidx, and Fonepay is polled by the background monitor. eSewa and ConnectIPS do not expose a status lookup by transaction id, so the demo only updates them from their callback payload — the "Verify" button on the orders table explains this rather than calling the API blindly.

Monitor a Fonepay payment

Start the polling worker in a terminal:

uv run python manage.py monitor_fonepay

It scans for fonepay orders still in initiated state, starts FonepayPaymentMonitor for each PRN, and updates orders to paid / failed / timeout when the settlement events fire. This runs on a plain background thread, no WebSocket, no daemon, nothing that breaks on shared hosting.

Going live

Set these environment variables in .env before starting the server:

export PAID_MODE=production
export PAID_BASE_URL=...            # public HTTPS base for callback URLs
export ESEWA_SECRET=...
export ESEWA_PRODUCT_CODE=...
export KHALTI_SECRET=...
export FONEPAY_SECRET=...
export FONEPAY_MERCHANT=...
export FONEPAY_USERNAME=...
export FONEPAY_PASSWORD=...
# optional monitor tuning (seconds); defaults shown
export FONEPAY_MONITOR_TIMEOUT=900
export FONEPAY_MONITOR_INTERVAL=5
export CONNECTIPS_MERCHANT_ID=...
export CONNECTIPS_APP_ID=...
export CONNECTIPS_APP_NAME=...
export CONNECTIPS_APP_PASSWORD=...
export CONNECTIPS_CERT_PATH=...       # .pfx / .p12 / .pem
export CONNECTIPS_CERT_PASSWORD=...
export CONNECTIPS_DEMO_USER=...
export CONNECTIPS_DEMO_PASSWORD=...
export CONNECTIPS_DEMO_CAPTCHA=...
export CONNECTIPS_DEMO_TPIN=...
export CONNECTIPS_DEMO_OTP=...

When PAID_BASE_URL is empty the demo derives callback URLs from the incoming request host, which works locally but is unreliable behind a proxy or on a real domain — set it to a public HTTPS base before going live.

Without them the example app runs in sandbox mode with placeholder keys so you can explore the UI and the request/response shapes safely. ConnectIPS runs a simulated checkout when CONNECTIPS_MERCHANT_ID is empty.

Working test credentials

The default .env.sample ships with test keys that are actually accepted today:

Gateway Test credentials
Khalti Mobile 9800000001/2/3/4/5, Pin 1111, OTP 987654, secret live_secret_key_68791341fdd94846a146f0457ff7b455
eSewa Username 9711111111/2/3/4, Password Nepal@123, Token 123456, secret 8gBm/:&EnhH.1/q, product code EPAYTEST
Fonepay See note below — no public sandbox credentials exist

Fonepay sandbox caveat

Fonepay's sandbox API endpoint (dev-merchantapi.fonepay.com) currently serves an expired TLS certificate, so any request to it fails with SSLCertVerificationError. This is on Fonepay's side, not the library's.

The demo targets the production endpoint instead (via PAID_MODE=production for Fonepay) and clearly warns that it performs real transactions. Fonepay has not published shared test credentials, so you need your own merchant account to exercise the QR/status flows.

Real-host callback caveat

success_url / return_url are built from the incoming request's host, so when running on a LAN or a real domain point your provider dashboard (and ALLOWED_HOSTS in example_project/settings.py) at the reachable public URL.

Note on the example server

This project exists to demonstrate the library's API, it uses the Django development server and SQLite. For production, run behind a real WSGI server (e.g. Gunicorn/uWSGI), confirm all callbacks hit HTTPS endpoints, and mark PAID_MODE=production only when you are truly live.