All projects
Ride-hailing · Full-stack platform Case study Active

OranjeTrip — End-to-End Ride-Hailing Platform

A production ride-hailing platform for the Netherlands: a Flutter rider app, a dual-role Flutter driver + admin app, and a .NET 10 backend — real-time dispatch, Stripe payments, an in-app wallet, and scheduled trips, all built end to end.

My role
Full-Stack Engineer (rider app, driver/admin app, backend)
Period
April 2026 – Present
Related experience
HOMSI

OranjeTrip is a ride-hailing platform for the Netherlands transport market. I build the whole system: the Flutter rider app, a dual-role Flutter driver + admin app, and the ASP.NET Core (.NET 10) backend that connects them. Owning both sides of the wire means when a real-time bug appears, I look at the mobile client and the backend together instead of filing a ticket and waiting.

The platform

  • Rider app (Flutter). Map-based booking with live tracking, fare quotes per vehicle type, saved payment methods, an in-app wallet, trip history, and PDF receipts.
  • Driver + admin app (Flutter). One app with role switching: drivers execute trips and broadcast their location; admins get a full operations console — dispatch, live fleet map, KYC review, refunds, incidents, and audio-recording playback.
  • Backend (.NET 10). Clean Architecture + DDD with CQRS/MediatR, EF Core on PostgreSQL, two SignalR hubs, Stripe, Google Maps, and a set of background services.

Real-time

Two SignalR hubs carry everything live:

  • Trip hub — pushes the full lifecycle (DriverAssigned, EnRoute, Arrived, TripStarted, TripCompleted, TripCancelled, scheduled-trip reminders) to the exact rider, driver, and admin groups involved.
  • Location hub — the driver streams coordinates; the server computes ETA and distance (Haversine) and returns a road-following polyline toward the pickup or destination, then broadcasts it to the rider and the admin fleet map.

Firebase Cloud Messaging delivers push notifications when the app is backgrounded.

Payments & wallet

Full Stripe integration, verified end to end:

  • PaymentIntents for fares, plus SetupIntent so riders can save a card.
  • Off-session charges for waiting fees and mid-trip fare adjustments.
  • Refunds with a split calculation, and a customer-facing refund-request queue.
  • Signature-verified webhooks (payment_intent.succeeded/failed, charge.refunded) handled idempotently.
  • An in-app wallet with a transaction ledger and Stripe top-ups; riders pay by card, wallet, or cash.

Trip lifecycle

The trip domain is the heart of the system — a rich state machine from PendingQuote through Completed, Cancelled, or Refunded, with:

  • Scheduled trips — future bookings with staged reminders and timed activation.
  • Waiting fees — metered waiting sessions with an airport grace window.
  • Cancellation policy — actor-aware rules that drive refund splitting.
  • Multi-stop trips and mid-trip repricing (preview → apply) with settlement.
  • Ratings, in-trip chat, and optional safety audio recordings.
  • Invoices — sequential numbering and QuestPDF-rendered PDFs with Arabic RTL fonts.

The admin console

The admin surface ships inside the driver app as a dual-role mode. Admins assign or self-take trips, watch a live fleet map with nearest-driver dispatch ranked by distance, review driver KYC documents, manage vehicle types and pricing config, process refunds and compensation claims, triage customer incidents, and play back trip recordings — all over the same SignalR + REST backend the rider app uses.

Backend architecture

  • Clean Architecture + DDD + CQRS/MediatR with validation, caching, and performance pipeline behaviours; an Outbox pattern turns domain events into reliably dispatched messages.
  • JWT auth with refresh-token rotation and separate roles (rider / driver / admin); OTP sign-in over SMS and email, plus Google sign-in.
  • Background hosted services for scheduled-trip activation, no-driver detection, pending edit expiry, outbox dispatch, and cleanup jobs.
  • PostgreSQL via EF Core with ~50 migrations; HybridCache; Google Maps Directions and Geocoding; observability via Serilog/Seq and OpenTelemetry; localized (multi-language) with JSON resources; Dockerized behind Caddy.

Why full-stack ownership pays off

When a rider’s map stalled after “driver accepted,” a mobile-only view would blame the SignalR subscription and a backend-only view would blame the broadcast. Owning both, I traced it to a connection-group routing mismatch in minutes — a cross-cutting bug that would have been a multi-day, cross-team coordination problem otherwise. OranjeTrip is my clearest proof of end-to-end ownership: designing, building, and operating the clients and the backend of a real-time production system.