OranjeTrip — منصّة نقل تشاركي متكاملة
منصّة نقل تشاركي إنتاجية لهولندا: تطبيق راكب بفلاتر، وتطبيق مزدوج الدور للسائق والإدارة بفلاتر، وخادم على .NET 10 — توزيع لحظي، ومدفوعات Stripe، ومحفظة داخل التطبيق، ورحلات مجدولة، طوّرتُها بالكامل من التطبيقات إلى الخادم.
- دوري
- مهندس متكامل (تطبيق الراكب، تطبيق السائق/الإدارة، الخادم)
- الفترة
- أبريل ٢٠٢٦ – حتى الآن
- الخبرة المرتبطة
- 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.