Kos Kita
Kos Kita is a boarding house management system designed to digitalize manual boarding house operations. It features distinct roles for the boarding house owner, super admin, and tenants. The platform integrates with the Gemini API to provide an AI-powered dashboard for owners, enabling insightful data analysis, and automates billing notifications to tenants. It also includes Midtrans for secure payment processing and a daily cron job to automatically track and check for due bill payments. The system incorporates a booking process that requires an initial DP (down payment) to secure the room.
2025
Website
Laravel, PHP, MySQL, Postman
Problem
Small boarding houses (“kos”) are often run by hand: rooms, tenants, and monthly rent are tracked manually and payments are confirmed one by one. Kos Kita puts that into one system. Visitors browse rooms, tenants register and pay online, monthly bills are generated automatically, and each owner gets a dashboard for their own properties. A superadmin verifies new owners before they can list.
My role
This was a five-person team project (PBL). I owned the repository and the backend, and authored roughly 120 of the ~235 commits:
- Project setup, database schema, and most migrations (15 of 26)
- Role architecture: three Filament panels, the middleware that gates them, and per-owner data scoping
- Registration with WhatsApp OTP, booking and down-payment logic, and Midtrans integration (Snap + webhook)
- The daily billing and late-fee cron job
- Gemini AI insights widget on the owner dashboard
- Activity log (audit trail) across 9 models, and the superadmin panel
Teammates built the public catalogue, the tenant-facing pages (bill history, PDF receipts), and the tenant Filament panel.
Architecture
- Laravel 13 + Filament 5, MySQL. Server-rendered Blade for the public site and the tenant flow; Filament for the three back offices.
- Roles live in
users.role(admin,penghuni,superadmin).User::canAccessPanel()decides which panel a user can open. Middleware also blocks owners not yet verified and tenants whose payment hasn’t been approved. - Side effects live in one place. When a payment flips to
verified, a model event onPembayaranmarks the bill as paid, assigns the tenant to the room, and sets the room unavailable. This works the same whether the payment came from Midtrans or was approved by hand.
Database
Each owner can have several kos, and each kos has many kamar (rooms). A room’s price depends on how many people share it (harga_kapasitas, JSON). tagihan is the monthly bill and pembayaran is a payment attempt against it, or a booking/owner-registration fee.
Key endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/register/send-otp |
Send WhatsApp OTP (throttled to 1 per 5 min) |
POST |
/register |
Create tenant + pending payment, return Midtrans Snap token |
POST |
/kamar/booking/{id} |
Book a room with a down payment |
GET |
/waiting-approval/pay-midtrans |
Re-open or refresh an expired Snap payment |
POST |
/midtrans/webhook |
Midtrans payment notification |
GET |
/midtrans/finish |
Redirect target after Snap checkout |
GET |
/tagihan-bulanan |
Tenant’s monthly bills |
POST |
/tagihan/{id}/upload |
Pay a bill (Midtrans or manual transfer proof) |
GET |
/nota/{id} |
Download PDF receipt |
Technical challenges
Trusting the Midtrans webhook. The webhook never trusts the request body. Midtrans\Notification takes the transaction ID and fetches the real status from the Midtrans API with the server key, so a forged POST can’t mark a payment as paid. capture/settlement map to verified (credit-card challenge is held back), and cancel/deny/expire map to rejected.
Duplicate notifications. Midtrans may send the same notification more than once, and the browser redirect can arrive before the webhook. The verification side effects only run when the status changes to verified (getOriginal('status') !== 'verified'). Repeated notifications therefore don’t assign the room twice or re-mark the bill.
Down payment and the first bill. A booking costs 50% of the room price plus a Rp 50,000 booking fee. When the cron creates that tenant’s first monthly bill, it charges only the remaining 50%.
Daily billing cron. app:process-billing-daily runs at 00:00 Asia/Jakarta. It bills each tenant on the day of the month they moved in. Move-in dates of 29–31 are billed on the last day of shorter months. Re-running it is safe: it skips tenants who already have a bill for that month, and it adds a late fee only to overdue bills that don’t have one yet. Fixing the scheduler timezone to WIB was one of the bugs I tracked down.
AI insights without burning quota. The owner widget builds a summary of that owner’s data (occupancy per kos, this month’s bills, outstanding bills, open damage reports), sends it to Gemini 2.5 Flash with responseMimeType: application/json and temperature 0.1, and caches the parsed result per owner for 6 hours. Malformed or failed responses are logged, and the widget shows a retry message instead of breaking the dashboard.
What I’d improve
- Add a unique index on
tagihan(user_id, bulan, tahun)so the database enforces the “one bill per month” rule, not just the code. - Move the Midtrans and Gemini calls to a queue so slow third-party responses don’t block page loads.
- Add feature tests for the payment state transitions.
- Strip tenant names and phone numbers from the payload sent to Gemini; aggregates are enough for the insights.
Simulate Midtrans payment webhook lifecycle and idempotency defense against replay attacks or network retries.
POST /midtrans/webhook
Payload & SHA-512 Hash
Verify Signature
Midtrans API Double-Check
State Transition Guard
getOriginal('status')
DB::transaction
Assign Room & Invoice PDF
// System ready. Click one of the action buttons above to simulate.
Benchmark impact of compound index on 50,000 monthly bill records for midnight billing cron.
ref (Index Seek)
idx_user_bulan_tahun
1 row
0.002% of table
1.2 ms
99.2% faster
O(log N)
B+ Tree Traversal
View More Projects

Backend, database, and documented REST API for a lab profile website and CMS.
- PHP Native
- MySQL
- JWT
- +3

Backend for a mental-health platform: AI diary emotion analysis, psychologist consultations, and regional stress mapping.
- Node.js
- Express
- Sequelize
- +3

Tailor marketplace built during my Unimasoft internship: orders, progress tracking, chat, and cashless payments.
- Laravel 11
- Livewire
- MySQL
- +2