HelloEnglish
Tensorik Technologies · Client: MBT Institute logo

MBT Institute LMS — Mobile Learning Platform

A production LMS built to protect paid content while serving live classes, recordings and certificates on Android.

Role
Full Stack Developer
Timeline
Jul 2026 – Oct 2026
Industry
EdTech · Mobile
TL;DR

MBT Institute sold courses through ad-hoc channels and lost revenue to account sharing and manual payment handling. I built a production LMS — a Flutter learner app, a NestJS API and a Next.js admin console on Supabase — that locks each enrolment to a single active device, splits payments into hardware and software tracks, and delivers live classes and auto-published recordings. Shipped to Google Play as com.tensorik.mbt_lms.

By the numbers

Google PlayShipped (Education)
1 deviceActive per learner
2 tracksPayment models
100%Server-validated coupons

The problem

MBT Institute was selling paid course access through a patchwork of WhatsApp groups, shared drives and manual payments. That created three compounding problems: students shared accounts freely, so one purchase served many learners; payments for hardware and software courses were mixed together, making reconciliation guesswork; and every live class or recording was distributed by hand.

The institute did not need another marketing site. It needed a single system where enrolment, payment, content access and certification were all controlled by the same rules.

Constraints that shaped the design

  • The learner experience had to ship on Android, which meant a real mobile app rather than a web wrapper.
  • A single backend had to serve both the mobile app and a separate admin console without duplicated business logic.
  • Downstream operations had to stay cheap — the institute had no DevOps team and no appetite for servers to babysit.
  • Payments had to remain flexible: some courses belong to a hardware track, others to software, and their pricing and coupon rules differ.
  • Existing content and student records needed to migrate without disrupting a running intake.

Architecture

I structured the system as three deployables sharing one source of truth, so the mobile app and the admin console are thin clients over the same API and database.

  • Mobile: a Flutter app using go_router for navigation, dedicated services and providers per domain, Supabase Auth for OTP and Google sign-in, Razorpay for checkout, and Firebase Cloud Messaging plus local notifications for delivery.
  • Backend: a modular NestJS API with clear domains for auth, courses, enrollments, payments, coupons, devices, certificates, notifications, search and support, using Redis and BullMQ for background jobs and DTO validation on every input.
  • Data: Supabase PostgreSQL with Row Level Security and RPC functions modelling profiles, devices and transfer requests, courses, sections and lessons, carts, enrollments, payments, coupons, invoices, sessions, recordings, wishlists, notifications and support tickets.
  • Media: a signed-upload asset pipeline on Cloudinary and object storage, so recordings and resources are served through authorized URLs rather than public links.
  • Admin: a Next.js console for course, syllabus and renewal-plan management, live-class scheduling, billing reconciliation, device approvals, support triage and role-based access with Zod validation.

Piracy-resistant delivery

Content protection was the reason this project existed, so it drove the data model instead of being bolted on. Each enrolment is bound to a single active device using a device fingerprint stored in secure storage, and that binding is verified server-side on every session.

  • One active device per learner: signing in on a new phone does not silently grant a second stream of access.
  • Controlled transfers: moving to a new device requires an admin-approved transfer request, which keeps legitimate upgrades possible without opening the door to sharing.
  • Capture deterrence: a no-screenshot flag is set on protected playback surfaces, and video is delivered through authorized, short-lived URLs instead of public buckets.
  • Identity anchoring: verified OTP and Google sessions are tied to a phone number, making disposable accounts harder to farm.

Payment integrity

The institute runs two commercial models in parallel — hardware courses and software courses — and mixing them was a source of revenue leakage. I separated them explicitly.

  • Dual Razorpay tracks: hardware and software enrolments run through distinct payment configuration, so pricing, settlement and reporting stay cleanly separated.
  • Server-validated coupons: discount codes are scoped per track and verified on the server, which blocks cross-category coupon exploits where a code meant for one track is applied to another.
  • Invoices per purchase: every transaction produces its own invoice, and the admin console reconciles payments and billing against enrolments rather than against spreadsheets.

Live classes and recordings

Live delivery had to feel native in the app rather than a link that gets forwarded. Zoom is connected through OAuth so sessions are created and scheduled by the admin, then surfaced to learners in a daily schedule view and a deep-linked player.

When a class ends, recordings are auto-published back to the course with chaptered playback, so learners who miss a session land on the same course page they already use — not on a private link that leaks.

Results

The platform shipped to Google Play as com.tensorik.mbt_lms in the Education category, closing the loop from enrolment through live class to certification inside a single controlled product.

Content access is now tied to a verified, single-device enrolment instead of a shareable login; hardware and software payments settle on separate, auditable tracks; and live-class operations that used to be manual are scheduled and delivered through the app.

What I would do differently

  • Scope device-transfer flows earlier — retrofitting an approval workflow onto a live enrolment model is more delicate than designing it in.
  • Add structured analytics on playback and drop-off from the first release to inform content decisions, not just access control.
  • Centralize notification templates sooner; per-event templates grow quickly across mobile and admin surfaces.

Tech stack

Frequently asked questions

How do you prevent students from sharing accounts?

Each enrolment is bound to a single active device using a device fingerprint held in secure storage, and that binding is verified on the server for every session. Moving to a new device requires an admin-approved transfer request, which keeps legitimate upgrades possible without allowing account sharing.

Why run two separate Razorpay payment tracks?

The institute sells hardware and software courses under different commercial rules. Running them through distinct payment configuration keeps pricing and settlement separate and lets coupons be scoped to a single track, which prevents discount codes from being applied across categories.

How are live classes and recordings delivered?

Live sessions are created and scheduled through Zoom OAuth in the admin console, then shown to learners in a daily schedule and deep-linked player. After a class, recordings are automatically published back into the course with chaptered playback and served through authorized URLs rather than public links.

How do you stop coupon abuse and cross-category exploits?

Coupons are scoped per payment track and validated entirely on the server before any discount is applied, so a code issued for one track cannot be reused on another. Every purchase also generates its own invoice, which makes reconciliation and abuse detection straightforward.