Project
ProjectsUA SMARTPAY
SmartPay is a cashless campus payments platform for the University of the Assumption. It replaces cash at bookstore and canteen counters with RFID tap-to-pay, a digital wallet, POS, and inventory in one system for students, parents, store staff, and admins. Students and parents load wallets (in-store RFID or PayMongo QR Ph), spend with a tap or PIN, and track history. Staff run POS and stock. Admins manage users, RFID cards, parent approvals, reports, and canteen digital-cash turnover. The web app also ships as an Android client via Capacitor.

React · Vite · Tailwind · Zustand · Express · MongoDB · JWT · Socket.io · Google OAuth · PayMongo · Capacitor.
SmartPay — Cashless Campus Payments for University of the Assumption
Role: Full-stack engineer
Live: ua-smartpay.com
Stack: React, Vite, Tailwind CSS, Zustand, Express, MongoDB, JWT, Socket.io, Google OAuth, PayMongo, Capacitor (Android)
Overview
SmartPay is a production campus payments platform that replaces cash at University of the Assumption bookstore and canteen counters. Students pay with RFID or a digital wallet, parents load funds, store staff run POS and inventory, and admins control users, cards, reports, and daily cash settlement — in one connected system.
Problem
Campus stores still ran on cash. That meant slow queues, no shared ledger, weak spending visibility for parents, and no reliable way for finance to reconcile digital sales at the end of the day. Existing tools (spreadsheets, separate POS, paper RFID logs) did not talk to each other.
The product had to work for five audiences at once: students, parents, cashiers, store managers, and administrators — on the web and as an Android app, with school-email identity and Philippine payment rails.
Approach
I designed SmartPay as a role-based platform with a single wallet and RFID identity at the center:
- One account model — student, parent, staff, manager, admin; each role gets a dedicated shell, not a bolted-on dashboard.
- Wallet as source of truth — load (cashier RFID top-up or PayMongo QR Ph / e-wallet), spend (POS tap or PIN), transfer, and history all hit the same ledger.
- Stores as operators — bookstore and canteen POS, catalog, bundles, inventory, voids/refunds, and (canteen) digital-cash turnover for finance.
- Trust and audit — Google
@ua.edu.phstudent signup, parent ID + admin approval, access codes, session idle lock, audit logs, and signed PayMongo webhooks.
The frontend is a React SPA (Vite, Tailwind, Zustand). The backend is Express + MongoDB with JWT auth and Socket.io for live notifications. The same web build is wrapped with Capacitor for Android.
What shipped
Students
RFID card + wallet, campus store catalog, cash-in, spending analytics, transaction history, PIN and security settings.
Parents
Register with government ID, bind children, load their own wallet, send funds, and monitor spending.
Store staff / managers
POS terminal, in-store wallet top-up, products/bundles/categories/inventory, transaction void/refund, sales views.
Admins
Users and parent approvals, RFID assignment, school year, analytics (spending, RFID/wallet stats), reports/exports, news, system control, and canteen turnover review.
Platform
Google OAuth, OTP activation, dark mode, real-time notifications, bug reporting, and a native Android client.
Hard problems
Canteen digital-cash turnover
Daily settlement is not “today’s sales.” Available cash = today’s net digital sales + prior carry − already submitted amounts. Pending finance requests must be reserved, not double-counted, and unsubmitted carry-over drafts cannot block leftover funds. I isolated the settlement math, added tests, and fixed a production bug where idle days plus a pending ₱600 request produced the wrong remaining due.
Payments
PayMongo checkout and QR Ph top-ups require quoting fees, verifying webhook signatures on the raw body (before express.json()), and crediting the correct wallet exactly once.
Identity vs. names
Google does not give first / middle / last. Splitting on spaces broke compound given names (e.g. Hanna Faith). Student Google signup now stores and displays a single full name.
Auth UX on real devices
Login and student signup had to sit correctly in Chrome device frames that matter locally: iPhone SE 375×667 and Galaxy A55 360×800. Flex min-height + overflow-y-auto left leftover space unused (and clipped the login footer on SE). The auth shell now fills 100dvh minus the navbar and centers with CSS grid.
Privileged sessions
Staff/admin sessions lock after idle time and require an access code; JWT lifetime is separate so a lock is not a silent logout.
Outcome
SmartPay is a live campus product, not a demo: cashless tap-to-pay, parent loading, store operations, and finance-facing turnover in one stack. The work that mattered most was making money paths correct (wallet, webhooks, turnover leftovers) and making auth usable on the phones students actually use.
Gallery