The challenge
Hart College ran admissions on a public marketing site, course registration on a separate spreadsheet, tuition collection by cheque and e-transfer, and student communication through a shared inbox. Nothing reconciled. Admins re-keyed every new student into three places, the registrar had no single answer to 'who is enrolled in this intake?', and finance burned a full week each month chasing missing payments and matching them against names that didn't quite agree across systems.
The ask was deceptively simple: replace all of it with one Student Information System (SIS) where a prospect could discover a course on a public marketing page, enrol in a specific intake, pay tuition online, see their schedule and payment history in a self-serve portal — and an admin could create batches, courses, coupons, candidates, and attachments from a single dashboard with no parallel spreadsheets and no reconciliation work at month-end.
Our solution
We built a single, custom SIS in three coordinated surfaces: a public Next.js course catalog, a student-facing enrolment + tuition portal, and a Django + Django REST Framework admin console — all backed by PostgreSQL and a Stripe Payments + Webhooks integration that keeps the SIS as the single source of truth for every dollar.
The public site ships a fast, SEO-tuned Next.js catalog: each course has its own indexable detail page, intake schedule, prerequisites, tuition breakdown, and inline 'Enrol now' CTA — so the marketing site is also the top of the enrolment funnel, not a separate brochure. Once a prospect clicks through, they land in a Stripe Checkout session created server-side with the right amount, currency, coupon, and intake ID baked into the metadata.
On payment success, a Stripe webhook lands on the Django API and idempotently creates or updates the student, links them to the chosen batch, attaches the receipt, and emails confirmation — all in a single signed transaction. The student's portal then exposes 'My Courses', 'My Schedule', 'My Tuition' (with full payment + refund history) and a documents tab for transcripts, ID, and signed agreements.
The Django admin console gives the registrar one place to run the entire program: create a batch, attach courses and faculty, define coupon codes (percentage or flat, scoped per intake or per course), upload candidate records and attachments, push announcements, and pull enrolment + revenue reports without ever opening Excel. Roles separate registrar, finance, faculty, and admin so each user only sees the surface they need.
Finally, we wired Stripe webhooks idempotently, locked tuition records against a checksum so finance can prove the SIS and Stripe agree, instrumented every endpoint with Sentry + CloudWatch, and stored every signed PDF / ID upload in S3 with time-bounded signed URLs (no raw object access).
- Public, SEO-tuned Next.js course catalog with one indexable page per course, intake schedule, and inline 'Enrol now' CTA
- Stripe Checkout sessions created server-side with intake ID, coupon code, currency, and tuition breakdown baked into metadata
- Idempotent Stripe webhooks — student, batch link, tuition row, receipt email all written in one signed transaction
- Student portal with My Courses, My Schedule, My Tuition (full payment + refund history), and document uploads
- Django admin console: batches, courses, coupons (percentage or flat, scoped per intake or course), candidates, attachments, announcements
- Role-separated access for registrar, finance, faculty, and admin — each user only sees the surface they actually use
- Tuition checksum reconciliation so finance can prove SIS and Stripe agree at any moment without opening Excel
- S3 + signed time-bounded URLs for every transcript, ID, and signed agreement — no raw object access in the wild