🌟 Inspiration Every day, thousands of Durham Region Transit riders dig through pockets for a PRESTO card, fumble with a phone, or hold up a line hunting for exact change. For students rushing between campuses, seniors navigating a busy stop, or anyone with their hands full — boarding a bus is more friction than it needs to be. We asked a simple question: what if your face was your fare? Biometric payments are already transforming airports and retail globally, but transit in mid-sized Canadian cities hasn't caught up. We wanted to build something real, local, and immediately usable — a system that works for DRT riders in Oshawa today, not five years from now.

🚌 What It Does FacePay is a biometric transit payment system for Durham Region Transit. Riders register once — capture their face, select their fare category, and link a payment card. After that, boarding is as simple as looking at a terminal camera.

Under 3 seconds from scan to boarding approval No card. No phone. No wallet needed. Automatically applies the correct DRT PRESTO fare — including U-Pass validity for Ontario Tech, Durham College, and Trent Durham GTA students Charges payment via Stripe in the background Confidence tiering handles edge cases: high confidence auto-charges, mid-range triggers a 4-digit PIN fallback, and low confidence prompts cash and registration

🔧 How We Built It We built FacePay as a full-stack system with two interfaces running in parallel:

Registration App (/register) — a 6-screen onboarding flow where riders create an account, select their fare category, complete a liveness-verified face capture, and save a payment card via Stripe SetupIntent. Bus Terminal (/terminal) — a fullscreen kiosk that runs in an always-on scanning loop, identifies passengers in real time, applies the correct fare, and triggers a Stripe PaymentIntent automatically.

Tech stack: LayerTechnologyFrontendReact 18 + Vite (single monorepo)UIshadcn/uiBackendFastAPI (Python 3.11)Face embeddingsDeepFace — Facenet model, 128-dim float vectorsLiveness detectionMediaPipe (registration) + OpenCV frame-delta (terminal)DatabaseSupabase — PostgreSQL + pgvector + Auth + RealtimeVector searchpgvector IVFFlat cosine similarity indexPaymentsStripe test mode — SetupIntent + PaymentIntentFare dataDurham Region Open Data GTFS static feed Face identification works by converting camera frames into 128-dimensional float vectors via DeepFace and running a cosine similarity search against stored embeddings using pgvector. Raw images are never written to disk — only the math reaches the database.

🧱 Challenges We Ran Into Liveness detection at the terminal was the hardest problem. We needed to prevent photo spoofing without adding friction. We settled on frame-delta motion analysis using OpenCV — detecting the micro-movements natural to a real face versus the stillness of a printed photo or phone screen. Confidence thresholding required careful tuning. Too aggressive and legitimate passengers get rejected; too lenient and lookalikes could trigger false charges. We landed on a three-tier system (auto-charge / PIN fallback / hard reject) that mirrors how human operators handle ambiguity. DRT fare complexity was deeper than expected. The fare table has 8 distinct categories — including TAP transit assistance monthly caps, U-Pass expiry logic, and Canadian Armed Forces exemptions. Fares needed to be seeded from DRT's official PRESTO schedule and dynamically applied per passenger, not hardcoded. GTFS feed integration was tricky due to feed format inconsistencies. We implemented 30-second TTL caching and graceful degradation so payment flow is never blocked if the feed goes unreachable.

🏆 Accomplishments That We're Proud Of

Zero stored images. Raw frames are processed in memory and discarded. A leaked database cannot be reverse-engineered into a photograph — a privacy standard that exceeds most commercial biometric systems. Full end-to-end payment flow working in under 24 hours — from face capture to Stripe charge to boarding confirmation. Real fare data. We're not using placeholder numbers — every fare reflects actual DRT 2025 PRESTO rates pulled from open data. Graceful edge case handling across 6 known failure scenarios: photo spoofing, Stripe failure, GTFS outage, multiple faces in frame, U-Pass expiry, and unknown passengers. Built a working PIN fallback flow as a safety net for mid-confidence matches, mirroring how chip-and-PIN works as a card fallback.

📚 What We Learned

pgvector is remarkably powerful for real-time biometric search at this scale. Cosine similarity on a 128-dim IVFFlat index is fast enough for a live boarding scenario without any caching layer. Liveness detection is a product problem as much as a technical one. The bar isn't just "does it work" — it's "will a tired bus driver trust it at 7am." Transit fare systems are complicated. What looks like a simple price list is actually a rules engine with expiry dates, monthly caps, eligibility windows, and fallback logic. Privacy-by-design is achievable without sacrificing UX. Choosing to never persist images wasn't a constraint — it was an architectural decision that made the system simpler and more trustworthy.

🚀 What's Next for FacePay

Institutional U-Pass verification — real-time enrollment API integration with Ontario Tech, Durham College, and Trent Durham GTA to auto-validate student eligibility Real-time GTFS vehicle positions — show live bus locations on the terminal and registration screens Multi-zone and time-of-day fare rules — support peak/off-peak pricing and zone-based fares as DRT expands PRESTO card reconciliation — bridge FacePay accounts with existing PRESTO infrastructure for riders who use both Accessibility accommodations — audio prompts, high-contrast mode, and extended scan windows on the terminal Fleet-wide terminal management dashboard — remote monitoring, fare audit logs, and device health for DRT operations

Built With

Share this project:

Updates