انتقل للمحتوى
كل دراسات الحالة
منصة لنقل الطلابمدة القراءة: 8 دقايق

MURI: اشتراكات ومقاعد ورحلات لنقل الطلاب

منصة لنقل الطلاب في السعودية شغالة بالاشتراكات: تطبيق للطالب وتطبيق للسواق، ولوحة للأدمن، و backend بـ Django/PostGIS. كقائد للفريق، جهّزت الـ backend والـ delivery pipeline بتاعه، وبنيت الأجزاء الخاصة بالجغرافيا والتحقق من الرحلات؛ أما أغلب مميزات المنصة اللي اتوصفت هنا فبناها زمايلي في الفريق.

قائد فريق برمجيات · ASAS IT · 2025
تطبيقات: الطالب، السواق، الأدمن
3
الجغرافيا، SRID 4326
PostGIS
الـ ledger بتاع محفظة السواق
قيد مزدوج
بلغتين من الأول للآخر
AR / EN
المشكلة
الاشتراك المدفوع لازم يتحوّل لمقاعد ورحلات فعلية، حتى لو تأكيد الدفع اتأخر أو وصل أكتر من مرة.
دوري
قُدت الفريق، وأسست الـ backend والمصادقة ومسار النشر، وبنيت نماذج الجغرافيا والتحقق من بداية الرحلة ونهايتها. الفريق نفّذ الدفع والمحفظة وتخصيص المقاعد والتطبيقات.
قرار هندسي مهم
تخصيص المقاعد جوه معاملة في قاعدة البيانات، مع قفل الصفوف أثناء الحجز، وإعادة التحقق من الدفع عند البوابة قبل تفعيل الاشتراك.
النتيجة وحدودها
الكود بيغطي الاشتراك والدفع وتوليد الرحلات ومحفظة السواق، مع معالجة للحالات اللي اتدفعت وما اتفعّلتش. مفيش هنا أرقام منشورة للاستخدام أو أثر تجاري مقاس.

المشكلة

إيه المشكلة اللي كنا بنحلها؟

الأهالي بيشتركوا للطلاب في مواصلات يومية على مسارات ثابتة. بس تنفيذ الخدمة دي محتاج تفاصيل كتير: المشغّل لازم يبيع المقاعد من غير ما يبيع أكتر من سعة العربيات، ويولّد كل رحلات فترة الاشتراك، وياخد المدفوعات بالكارت، ويتابع السواقين، ويدفعلهم — وده كله بين الطلاب والسواقين والأدمن.

والمدفوعات بتصعّب الموضوع أكتر: تأكيد بوابة الدفع ممكن يوصل متأخر، أو يوصل مرتين، أو ميوصلش خالص، والاشتراك اللي اتدفع وما اتفعّلش بيسبب مشكلة للعميل أكبر من رفض الدفع من البداية.

القيود

إيه القيود اللي أثّرت على التصميم؟

  • تخصيص المقاعد لازم يكون آمن مع عمليات الشراء المتزامنة (concurrent) — مينفعش يتباع عدد مقاعد أكبر من سعة أي عربية.
  • إشعارات HyperPay ممكن تتأخر أو تتكرر؛ فالتفعيل لازم يكون idempotent.
  • السواقين شغالين على شبكات الموبايل؛ وأي إجراء في الرحلة لازم يتحقق منه على حسب المكان والوقت اللي حصل فيهم.
  • عربي وإنجليزي في المنصة كلها، ومن ضمنها نصوص التطبيق اللي بتتغير من غير إصدار جديد للتطبيق.
  • الفريق صغير، فالأجزاء التشغيلية كان لازم تفضل بسيطة.

الـ architecture

النظام متصمم إزاي؟

backend بـ Django REST متقسّم لـ modules، على PostgreSQL/PostGIS، مع Celery و Redis للمواعيد المجدولة والشغل اللي في الخلفية. وفيه تلات clients بيستخدموه: تطبيقين Flutter ولوحة أدمن بـ Angular.

  1. الـ clients

    • تطبيق الطالب (Flutter)

      الاشتراك والدفع، ومتابعة الرحلة على الخريطة، وإظهار QR code وقت الركوب.

    • تطبيق السواق (Flutter)

      الرحلات المخصصة له، ورفع الموقع، ومسح الـ QR وقت الركوب، وبلاغات الـ SOS، والمحفظة.

    • لوحة الأدمن (Angular 19)

      المستخدمين، والجغرافيا، والمسارات والرحلات، والباقات، والمدفوعات، والإعدادات.

  2. الـ Backend (Django/DRF)

    • الـ modules بتاعة الـ auth والمستخدمين

      JWT مع OTP على رقم الموبايل؛ و apps منفصلة للعميل والسواق والأدمن.

    • الاشتراكات والرحلات

      الباقات، وتخصيص المقاعد، وتوليد الرحلات، والإلغاءات وطلبات التغيير.

    • المدفوعات والحسابات

      الـ client بتاع HyperPay والـ webhooks؛ ledger بالقيد المزدوج (double-entry) ومحافظ السواقين.

    • المناطق، والإشعارات، والدعم

      الجغرافيا بـ PostGIS، وقوالب الـ push بتاعة Firebase، وترجمات بيتحكم فيها السيرفر.

  3. الـ async، والبيانات، والبنية التحتية

    • Celery + beat

      التذكيرات، وانتهاء الصلاحية، والرحلات اللي اتفوّتت، والـ reconciliation بتاع المدفوعات، وترتيب المسارات، وإشعارات push على حسب القرب.

    • PostgreSQL + PostGIS

      أعمدة geography للمدن، و polygons للأحياء، والمحطات، ونقط الرحلات.

    • HyperPay · FCM · S3

      الدفع بالكارت و Apple Pay، وإشعارات الـ push، والمستندات والميديا.

    • الـ delivery على AWS

      Docker Compose على EC2 جوه Auto Scaling group؛ وكل deploy بيعمل bake لـ machine image جديدة.

القرارات الهندسية

اخترنا إيه، وليه؟

  1. تخصيص المقاعد تحت row locks، وبترتيب ثابت

    تخصيص المقاعد بيحصل في transaction واحدة بتعمل lock على الـ rows بتاعة رحلات المسار بـ SELECT … FOR UPDATE، ودايمًا بنفس الترتيب (priority, id) لتقليل تعارض ترتيب الأقفال بين عمليات الشراء المتزامنة، وبعدين بتشيّك على المقاعد المتبقية في كل slot، وبترفض الحجز بوضوح لو المسار مليان. وتأكيد الدفع كمان بيعمل lock على الاشتراك، ومبيعملش أي حاجة لو الاشتراك مش مستني دفع، فالتأكيد المكرر ملوش أي ضرر.

  2. الـ webhook بيبدأ التحقق؛ وحالة الدفع بناخدها من البوابة

    إشعارات HyperPay بيتفك تشفيرها ويتعملها authentication (AES-256-GCM)، وبتتسجل في الـ log، وبعدين بتتسلّم لـ Celery. الـ task بتعمل lock على الـ transaction، وبتجيب حالتها تاني من HyperPay بدل ما تثق في الـ payload؛ والتفعيل مبيتبعتش غير بعد ما الـ transaction بتاعة قاعدة البيانات تعمل commit.

  3. الـ reconciliation بدل الاعتماد على الحظ

    تلات آليات مجدولة لمعالجة مشاكل الدفع: إعادة الـ polling على الـ transactions اللي واقفة في حالة in progress، وإعادة محاولة تفعيل الاشتراكات اللي اتخصم تمنها ومتفعّلتش أبدًا (لحد خمس محاولات، وبعدها تنبيه للأدمن)، واسترداد أوتوماتيك للفلوس لو المقاعد مينفعش تتخصص بعد الدفع.

  4. دفتر حسابات (ledger) لمستحقات السواقين

    أرباح السواقين والعمولات والمكافآت والخصومات كلها قيود يومية (journal entries) في ledger بالقيد المزدوج، بيرفض أي قيد مش متوازن. والمبالغ المحجوزة في المحفظة (wallet holds) بتتغير تحت row locks بـ F-expressions، والإيراد ممكن يتسجل (revenue recognition) وقت الدفع أو مع اكتمال الرحلات.

  5. التحقق من إجراءات الرحلة بالمكان والوقت

    الجغرافيا متخزنة كـ PostGIS geography (SRID 4326). السواق يقدر يبدأ الرحلة وهو في حدود 100 متر من بداية المسار أو أول نقطة ركوب نشطة، وخلال الفترة من 20 دقيقة قبل المعاد لحد 10 دقايق بعده. ونهاية الرحلة لازم تكون في حدود 100 متر من نهاية المسار أو آخر نقطة نزول نشطة؛ والركوب بالـ QR لازم يحصل في حدود 50 متر من المحطة، وبالترتيب.

  6. نصوص التطبيق جاية من السيرفر

    نصوص التطبيق موجودة في مخزن ترجمات على الـ backend، وبتتبعت للتطبيقين، فالكلام بالعربي والإنجليزي ممكن يتغير من غير ما نطلع build جديد للتطبيقات.

المفاضلات

كسبنا إيه، وإيه اللي اتنازلنا عنه؟

polling للموقع اللحظي

بنية تحتية بسيطة؛ السواقين بيرفعوا الموقع بحد أقصى مرة كل 10 ثواني، والطلاب بيعملوا poll كل 10 ثواني، فالخريطة ممكن تتأخر لحد 20 ثانية تقريبًا.

حساب المسافة بين نقطتين في Python

مناسب للتحقق من المسافة بين سواق واحد ومحطة واحدة؛ لكن البحث عن أقرب سواق على مستوى الجدول كله هيحتاج spatial SQL بدل كده.

توليد كل الرحلات وقت الدفع

الجدولة والتقارير بسيطة؛ والتغييرات بتمشي عن طريق الإلغاءات وطلبات التغيير.

الـ beat و Redis على نفس الـ instance بتاعة التطبيق

بسيط طول ما هي instance واحدة؛ لكن الـ scale out محتاج الأول نقل الـ scheduler والـ cache لبنية تحتية مشتركة.

حساب الـ ETA بسرعة ثابتة لترتيب المحطات

سلوكه متوقع وتكلفته قليلة (والمسارات الأطول بتترتب بـ OR-Tools)، بس مش بياخد الزحمة في الاعتبار.

النتايج

وصلنا لإيه؟

الاشتراك من أوله لآخره

اختيار الباقة، والدفع، وتخصيص المقاعد، واستلام الرحلات المتولّدة للفترة كلها — مع تذكيرات بالانتهاء والتجديد.

إدارة رحلات السواقين

بداية ونهاية الرحلة بـ geofence، والركوب بالـ QR، وتسجيل الغياب، وبلاغات SOS ليها workflow عند الأدمن، والسحب من المحفظة.

معالجة مشاكل الدفع

إعادة التحقق من البوابة والمهام المجدولة بيعالجوا تأكيدات الدفع المتأخرة أو الناقصة. فشل تخصيص المقاعد بيبدأ مسار استرداد المبلغ؛ ولو الاسترداد نفسه فشل، الحالة بتحتاج مراجعة يدوية.

التطبيقات اتنشرت

تطبيق الطالب وتطبيق السواق منشورين على Google Play و App Store.

دوري

  • قُدت فريق البرمجيات، وأسّست الـ backend: هيكل المشروع، والـ settings، والـ logging، والـ authentication بـ JWT و OTP على رقم الموبايل، والـ permission classes الأساسية.
  • بنيت الـ deployment pipeline: الـ build بـ Docker Compose، والـ health check، والـ bake بتاع الـ machine image، والـ rollout بتاع الـ launch template.
  • عملت الـ modelling للجغرافيا بـ PostGIS (مراكز المدن و polygons الأحياء)، وحمّلت الـ seed data بتاعة السعودية.
  • بنيت التحقق من بداية/نهاية الرحلة بالـ geofence، ومخزن الترجمات اللي بيتحكم فيه السيرفر.
  • زمايلي في الفريق هم اللي بنوا التطبيقات ولوحة الأدمن، والـ Terraform، والمدفوعات، والـ ledger بتاع المحفظة، والإشعارات، وترتيب المسارات، والركوب بالـ QR، والـ SOS، وتقريبًا كل الاختبارات؛ والمميزات دي مذكورة هنا باعتبارها شغل الفريق.

حدود الشغل

  • مفيش هنا أرقام منشورة للمستخدمين أو الاستخدام أو أثر تجاري مقاس.
  • مفيش WebSockets: الموقع اللحظي عبارة عن رفع بـ REST مع polling.
  • الـ repository فيه مئات الاختبارات، بس الـ CI بيشغّل الـ deployments وفحص أمني (security scan)، مش الـ test suite.
DjangoDRFPostGISCeleryRedisHyperPayFirebaseFlutterAngular

دراسة الحالة اللي بعدها

Maxpeak

الـ backend بتاع marketplace بلغتين بيوصّل شركات سعودية بمهندسين من مصر. بنيت الـ API بـ Django، والـ authentication بتاعه، والمرحلة الأولى الآلية من مسار تقييم من تلات مراحل: pipeline على Celery بيطلّع النص من الـ CV ويحسبله درجة بـ LLM. المنتج لسه قبل الإطلاق.

اقرأها