- TypeScript 62.5%
- JavaScript 32.2%
- CSS 3%
- HTML 2%
- Dockerfile 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| n8n | ||
| node_modules | ||
| public | ||
| scripts | ||
| server | ||
| src | ||
| .dockerignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| eslint.config.mjs | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| startup.sh | ||
| tsconfig.json | ||
| vite.config.ts | ||
Beliot Ventures — Rent Payment
Secure M-Pesa rent payment page. Tenants open a link with a token, the app loads their balance from n8n, and they pay from their phone.
Production-style URL:
https://pay.beliot.co.ke?token=TOKEN_CODE
Requirements
- Node.js 22.12 or later (Node 18 will crash Vite with
styleText is not exported) - npm 10 or later
- Network access to
https://n8n.beliot.co.ke
If node -v shows v18.x, upgrade first. On Ubuntu/WSL with nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
# close and reopen the terminal, or:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm install 22
nvm use 22
node -v # should print v22.x
Then reinstall (Vite binaries were built under Node 18):
rm -rf node_modules package-lock.json
npm install
npm run dev
Setup
unzip beliot-rent-payment.zip
cd beliot-rent-payment # or the folder you unzipped into
npm install
The HMAC signing secret is already set in src/lib/payment/n8n.server.ts. To override it without editing code, set:
export PAYMENT_HMAC_SECRET="your-hmac-secret"
Ports
| How you run it | Where to change the port |
|---|---|
npm run dev |
Env PORT (default 8080), or server.port in vite.config.ts |
Docker / npm start |
Env PORT (default 8080). Nitro also reads NITRO_PORT. Bind HOST=0.0.0.0 |
Examples:
PORT=3000 npm run dev
docker run --rm -p 80:8080 -e PORT=8080 -e PAYMENT_HMAC_SECRET=your-secret beliot-pay
The container always listens on PORT inside the image (compose maps host PORT → container 8080). To change the published host port only:
PORT=80 docker compose up --build
Docker
docker compose up --build
# or
docker build -t beliot-pay .
docker run --rm -p 8080:8080 \
-e PAYMENT_HMAC_SECRET=your-hmac-secret \
beliot-pay
Production build uses Nitro node-server (NITRO_PRESET=node-server). HMAC signing still happens only on the server.
npm run dev
Open http://localhost:8080. With no token, the page loads a sample request so you can check the layout.
Open a real payment request:
http://localhost:8080/?token=YOUR_TOKEN
Sample token used for testing:
test_pay_7f3c9e2a1b8d4f06
How a payment works
-
The page reads
tokenfrom the query string. -
The server POSTs
{"token":"..."}to
https://n8n.beliot.co.ke/webhook/payment-details
with headerX-Signature(HMAC-SHA256 hex of the exact JSON body). -
Tenant name, house/unit, outstanding balance, suggested amount, phone, and expiry are shown. Add
house_unitto the payment-details payload (used on the page and as the M-Pesa AccountReference). -
Amount is typed by the tenant. If they leave it blank, suggested_amount is sent.
-
Pay with M-Pesa POSTs this JSON to
https://n8n.beliot.co.ke/webhook/pay
with the sameX-SignatureHMAC header:{ "token": "test_pay_7f3c9e2a1b8d4f06", "phoneNumber": "254721271252", "amount": 10000, "account": "B2" }accountis the house/unit (max 12 characters for Daraja). HMAC is SHA-256 hex of that exact JSON string (key order as above). -
n8n should wait for the STK Push HTTP response and reply with JSON, not a redirect:
{ "success": true, "checkout_request_id": "ws_CO_...", "message": "Prompt sent" } -
The page then polls
https://n8n.beliot.co.ke/webhook/payment-statusevery 3s:{ "token": "...", "checkout_request_id": "ws_CO_..." }Reply
{ "success": true, "status": "pending" | "paid" | "failed" | "cancelled", "message": "..." }
using the result stored from/webhook/mpesa_cb.
Copy-paste n8n Code nodes: n8n/hmac-verify.js, n8n/check-token-cache.js, n8n/generate-stk-password.js, n8n/stk-respond.js, n8n/mpesa-callback-store.js, n8n/payment-status.js.
Kenyan numbers are accepted as 07…, 01…, 2547…, or +2547….
Build
npm run build
npm run typecheck
The production build is a Vercel / Nitro output under .vercel/output.
Deploy (Vercel)
- Push this folder to GitHub (or upload it in the Vercel dashboard).
- Import the project in Vercel. Framework preset can stay Vite; the Nitro plugin already emits a Vercel function.
- Set environment variable
PAYMENT_HMAC_SECRETto your webhook secret. - Point
pay.beliot.co.keat the Vercel deployment.
The HMAC secret must never be exposed in client-side code. Signing happens only in src/lib/payment/n8n.server.ts.
Useful scripts
| Command | What it does |
|---|---|
npm run dev |
Development server on port 8080 |
npm run build |
Production build |
npm run typecheck |
TypeScript check |
npm run preview |
Serve the production build locally |
Project layout
src/routes/index.tsx Payment page (reads ?token=)
src/components/payment/ Page UI
src/lib/payment/api.ts Server functions
src/lib/payment/n8n.server.ts HMAC signing + n8n calls
public/ Logo, favicon, share card