docs(examples): describe the demo app

This commit is contained in:
sankalp committed 2026-09-03 00:40:57 +05:45
1 parent 683d20ea4f
commit 96b2696f33
1 file changed
+151
+151
View File
@@ -0,0 +1,151 @@
# 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](https://docs.astral.sh/uv/):
```bash
# 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:
```bash
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:
```bash
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 `9806800001/2/3/4/5`, 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.