Skynet eBilling — ISP Billing & Operations Platform
Billing and operations platform for an FTTH ISP: customers, invoices, online payments, automatic isolate/reconnect on MikroTik, and the tools NOC uses every day.
Problem
Billing, payments and network access lived in different places. Admins marked payments by hand, then someone logged into the router to isolate or reconnect the customer. Unpaid customers stayed online, paid customers stayed isolated, and nobody had one view of who owes what and who is actually online. App changes were deployed by hand and sometimes broke things that used to work.
My role
Product owner, ops lead and reviewer. I designed the features, wrote the plans, reviewed every change, and ran every deploy and network-side rollout myself.
Scale
- 2,000+ PPPoE subscribers billed monthly
- 10+ MikroTik routers kept in sync
- 500+ automated test files, 300+ in blocking CI groups
- 20+ Playwright end-to-end specs
What I built
- Laravel + Inertia (React/TypeScript) app for customers, packages, areas, monthly invoices (PDF), receipts and a passwordless customer pay page.
- Online payment through a gateway (Midtrans Snap, Duitku being onboarded) with signed callbacks, idempotent payment recording, a kill switch and a manual bank-transfer fallback.
- Overpayments are kept as customer credit and applied automatically to the next invoice.
- Automatic isolate / reconnect: overdue customers move to an isolation PPPoE profile over the RouterOS API; a full payment reconnects them without an admin.
- Isolation portal: isolated customers land on a "please pay" page (reverse-proxy catch-all + MikroTik dst-nat and address-list; only DNS and the billing site are reachable).
- Router sync: PPPoE secrets are read from MikroTik as the source of truth; router-only accounts are staged for review instead of imported silently.
- Guard rails for destructive actions: terminate or ONU delete is refused while the session or ONU is still active; an override needs a superadmin and a written reason. Data fixes run dry-run → backup → apply → revert.
- Full UI redesign: light/dark tokens, new shell, dashboard KPIs, mobile pages for technicians, plain Indonesian status labels, global Ctrl-K search and an in-app FAQ with generated screenshots.
- Reports: revenue, collection rate, outstanding aging, revenue per area, plus WhatsApp recap commands for installs and migrations.
Architecture
-
Users
- Admin / NOC
- Customer pay page
-
App
- eBilling web (Laravel + React)
- Queue workers + scheduler
-
Data
- MySQL
- Redis
-
Outside
- Payment gateway
- MikroTik routers
- WhatsApp gateway
How it ships
- Every change goes plan → code → strict review → CI → merge → deploy → post-deploy check. Behaviour changes need an explicit "yes" before any code is written.
- GitHub Actions on self-hosted runners: blocking PHPUnit groups, frontend contract tests, TypeScript check, build, Playwright end-to-end tests, secret scan and screenshot workflows for visual QA.
- A local pre-push script runs lint, formatting, the touched tests and the build before anything reaches CI.
- One scripted deploy: only from a clean main, database backup first, previous image kept for rollback, and post-deploy guards that must print OK for the WhatsApp install and migration paths.
- A separate demo instance with its own database user and sandbox payment keys, so nothing is tested on production.
Outcome
- Isolation and reconnection follow payment status automatically; admins no longer log into routers for it.
- Overpayments are no longer lost: they show up as credit and are used on the next invoice.
- Deploys are one command with guards and a known rollback, shipped in batches.
- Data clean-ups (duplicate accounts, wrong statuses, wrong ONU serials) ran from dry-run lists with a revert path.
- One consistent admin UI that works on phones and uses wording staff understand.
Screenshots
Demo / test data only. Real customer data never leaves production.
Stack
Next project
New Installs & Customer Migration over WhatsApp