All projects

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.

Year

2025

Category

Website

Tool

Laravel, PHP, MySQL, Postman

Case Study

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

Kos Kita architecture diagram

  • 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 on Pembayaran marks 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

Kos Kita ERD

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.
Live Technical Simulator
Interactive Backend Simulatorv1.2 · Live

Simulate Midtrans payment webhook lifecycle and idempotency defense against replay attacks or network retries.

01. INBOUNDIDLE

POST /midtrans/webhook

Payload & SHA-512 Hash

02. SECURITYWAIT

Verify Signature

Midtrans API Double-Check

03. IDEMPOTENCYWAIT

State Transition Guard

getOriginal('status')

04. SIDE EFFECTSWAIT

DB::transaction

Assign Room & Invoice PDF

laravel-worker.log0 ms

// System ready. Click one of the action buttons above to simulate.

View More Projects

↑↓ navigate↵ selectesc close
fijri.my.id