Express Elwadi API v1.0 Stable

واجهة برمجية تتيح لك ربط موقعك أو تطبيقك بنظام حجز إكسبريس الوادى. يمكنك البحث عن الرحلات، اختيار المقاعد، الحجز، الاسترجاع، ونقل التذاكر.

Base URL

https://clinic.khobraelmal.com/api/v1

كيف تبدأ؟

1. تواصل معنا للحصول على API Key
2. ننشئ لك حساب موظف API
3. نشحن رصيدك
4. تبدأ تحجز!

نظام الحسابات

حسابك يعمل بنظام دائن ومدين:

العمليةالتأثير
شحن الرصيد➕ يزيد رصيدك (دائن)
حجز تذكرة➖ ينقص رصيدك (مدين)
استرجاع تذكرة➕ يرجع المبلغ - الغرامة (دائن)
نقل تذكرة➖ غرامة النقل + فرق السعر (مدين)

مهم

لا يمكن الحجز إذا كان رصيدك غير كافي. تواصل مع الإدارة لشحن رصيدك.

المصادقة مطلوب

جميع طلبات API تحتاج مصادقة. أرسل API Key و Secret في الـ Headers:

الطريقة 1: Headers منفصلة

X-API-KEY: your_api_key
X-API-SECRET: your_api_secret

الطريقة 2: Bearer Token

Authorization: Bearer your_api_key:your_api_secret

مثال cURL

curl -X GET "https://clinic.khobraelmal.com/api/v1/stations" \
  -H "X-API-KEY: abc123" \
  -H "X-API-SECRET: xyz789"

مثال JavaScript

const response = await fetch('https://clinic.khobraelmal.com/api/v1/stations', {
  headers: {
    'X-API-KEY': 'your_api_key',
    'X-API-SECRET': 'your_api_secret',
  }
});

أمان

لا تشارك API Key في كود الـ Frontend. استخدمه من السيرفر فقط (Backend).

الأخطاء

كل الاستجابات ترجع بصيغة JSON موحدة:

{
  "success": false,
  "message": "وصف الخطأ بالعربي"
}
الكودالمعنى
200نجاح
400بيانات ناقصة أو غير صحيحة
401API Key غير صحيح
404غير موجود
500خطأ في السيرفر

المحطات

GET/api/v1/stationsجميع المحطات النشطة

الاستجابة

{
  "success": true,
  "data": [
    {
      "id": 1,
      "name": "الترجمان",
      "code": "CS2",
      "address": "السبتية - ميناء القاهرة البرى",
      "phone": "0123456789",
      "latitude": 30.0626,
      "longitude": 31.2497,
      "city_name": "القاهرة",
      "governorate_name": "القاهرة"
    }
  ]
}

الخطوط

GET/api/v1/routesجميع خطوط السير
{
  "data": [
    { "id": 1, "name": "القاهرة - الخارجة", "code": "CAI-KHG" }
  ]
}

الرحلات

GET/api/v1/trips?from_station_id=1&to_station_id=4&date=2026-03-15

المعاملات (Query Parameters)

المعاملالنوعمطلوبالوصف
from_station_idintegerمعرف محطة القيام
to_station_idintegerمعرف محطة الوصول
datestringالتاريخ (YYYY-MM-DD)

الاستجابة

{
  "data": [
    {
      "instance_id": 15,
      "trip_date": "2026-03-15",
      "departure_time": "06:00:00",
      "route_name": "القاهرة - الخارجة",
      "service_class_name": "VIP",
      "price": 140,
      "available_seats": 22,
      "total_seats": 33
    }
  ]
}

المقاعد

GET/api/v1/trips/{instance_id}/seats?from_station_id=1

الاستجابة

{
  "data": [
    {
      "id": 42,
      "seat_number": 5,
      "row_number": 2,
      "column_number": 1,
      "cell_type": "seat",
      "status": "available"  // available | confirmed | pending | blocked
    }
  ]
}

أنواع الخلايا (cell_type)

seat مقعد عادي — door باب — driver سائق — aisle ممر — stairs سلم — bathroom حمام — empty فارغ

حجز تذكرة

POST/api/v1/bookings

الطلب (Body JSON)

الحقلالنوعمطلوبالوصف
instance_idintegerمعرف الرحلة
from_station_idintegerمحطة القيام
to_station_idintegerمحطة الوصول
seat_idsarrayمعرفات المقاعد [42, 43]
passenger_namestringاسم الراكب
passenger_phonestringتليفون الراكب
ticket_category_idintegerفئة التذكرة (لو فيه خصم)

مثال

curl -X POST "https://clinic.khobraelmal.com/api/v1/bookings" \
  -H "X-API-KEY: abc123" \
  -H "X-API-SECRET: xyz789" \
  -H "Content-Type: application/json" \
  -d '{
    "instance_id": 15,
    "from_station_id": 1,
    "to_station_id": 4,
    "seat_ids": [42, 43],
    "passenger_name": "أحمد محمد",
    "passenger_phone": "01012345678"
  }'

الاستجابة

{
  "success": true,
  "message": "تم الحجز بنجاح",
  "data": {
    "booking_number": "BK202603151234",
    "booking_id": 58,
    "total_amount": 280,
    "tickets": [
      { "ticket_id": 101, "seat_number": 5, "price": 140 },
      { "ticket_id": 102, "seat_number": 6, "price": 140 }
    ]
  }
}

الرصيد

المبلغ يتخصم من رصيدك تلقائياً. لو رصيدك غير كافي هيرجع خطأ 400.

عرض حجز

GET/api/v1/bookings/{booking_number}

يرجع تفاصيل الحجز مع كل التذاكر.

{
  "data": {
    "booking_number": "BK202603151234",
    "total_amount": 280,
    "trip_date": "2026-03-15",
    "route_name": "القاهرة - الخارجة",
    "tickets": [
      { "id": 101, "seat_number": 5, "ticket_status": "confirmed", "final_price": 140 }
    ]
  }
}

استرجاع تذكرة

POST/api/v1/bookings/{booking_number}/cancel

يلغي كل التذاكر المؤكدة في الحجز. المبلغ يرجع لرصيدك (ناقص الغرامة لو فيه).

{
  "success": true,
  "message": "تم إلغاء الحجز"
}

قيود الاسترجاع

لا يمكن الاسترجاع بعد قفل التابلو من المحطة. وقد تكون هناك غرامة استرجاع حسب إعدادات النظام.

استرجاع تذاكر محددة

POST/api/v1/bookings/retrieve
// Body
{ "ticket_ids": [101, 102] }

// Response
{ "success": true, "data": { "refund_amount": 280 } }

نقل تذكرة

نقل تذكرة من رحلة لرحلة تانية (نفس الخط أو خط مختلف).

1. ابحث عن رحلات بديلة
2. اختر المقعد الجديد
3. أكّد النقل

حساب التكلفة

غرامة نقل ثابتة + فرق السعر (لو الجديدة أغلى يتخصم، لو أرخص يرجع الفرق لرصيدك).

حالياً النقل متاح عبر واجهة العميل الأونلاين فقط. لو محتاج API endpoint مخصص للنقل تواصل مع الإدارة.

الرصيد

GET/api/v1/account/balance
{ "success": true, "data": { "balance": 4500.00 } }

المعاملات

GET/api/v1/account/transactions?page=1
{
  "data": [
    {
      "booking_number": "BK202603151234",
      "total_amount": 280,
      "booking_type": "confirmed",
      "trip_date": "2026-03-15",
      "route_name": "القاهرة - الخارجة",
      "created_at": "2026-03-12 14:30:00"
    }
  ],
  "page": 1
}

سيناريو حجز كامل

الخطوة 1: جيب المحطات

GET /api/v1/stations

الخطوة 2: ابحث عن رحلات

GET /api/v1/trips?from_station_id=1&to_station_id=4&date=2026-03-15

الخطوة 3: شوف المقاعد المتاحة

GET /api/v1/trips/15/seats?from_station_id=1

اعرض المقاعد حسب row_number و column_number. المقاعد اللي status = "available" هي اللي يقدر العميل يختارها.

الخطوة 4: احجز

POST /api/v1/bookings
{
  "instance_id": 15,
  "from_station_id": 1,
  "to_station_id": 4,
  "seat_ids": [42],
  "passenger_name": "أحمد محمد",
  "passenger_phone": "01012345678"
}

الخطوة 5: (اختياري) استرجاع

POST /api/v1/bookings/BK202603151234/cancel

نصيحة

احفظ booking_number و ticket_id عندك عشان تقدر تعمل استرجاع أو عرض الحجز لاحقاً.

أكواد جاهزة

JavaScript / Node.js

class ExpressElwadiAPI {
  constructor(apiKey, apiSecret) {
    this.base = 'https://clinic.khobraelmal.com/api/v1';
    this.headers = {
      'Content-Type': 'application/json',
      'X-API-KEY': apiKey,
      'X-API-SECRET': apiSecret,
    };
  }

  async getStations() {
    const r = await fetch(this.base + '/stations', { headers: this.headers });
    return r.json();
  }

  async searchTrips(from, to, date) {
    const r = await fetch(
      this.base + `/trips?from_station_id=${from}&to_station_id=${to}&date=${date}`,
      { headers: this.headers }
    );
    return r.json();
  }

  async getSeats(instanceId, fromStationId) {
    const r = await fetch(
      this.base + `/trips/${instanceId}/seats?from_station_id=${fromStationId}`,
      { headers: this.headers }
    );
    return r.json();
  }

  async book(instanceId, from, to, seatIds, name, phone) {
    const r = await fetch(this.base + '/bookings', {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify({
        instance_id: instanceId,
        from_station_id: from,
        to_station_id: to,
        seat_ids: seatIds,
        passenger_name: name,
        passenger_phone: phone,
      })
    });
    return r.json();
  }

  async cancel(bookingNumber) {
    const r = await fetch(this.base + `/bookings/${bookingNumber}/cancel`, {
      method: 'POST', headers: this.headers
    });
    return r.json();
  }

  async getBalance() {
    const r = await fetch(this.base + '/account/balance', { headers: this.headers });
    return r.json();
  }
}

// الاستخدام
const api = new ExpressElwadiAPI('your_key', 'your_secret');
const trips = await api.searchTrips(1, 4, '2026-03-15');
const booking = await api.book(15, 1, 4, [42], 'أحمد', '01012345678');

PHP

class ExpressElwadiAPI {
    private $base = 'https://clinic.khobraelmal.com/api/v1';
    private $key, $secret;

    public function __construct($key, $secret) {
        $this->key = $key;
        $this->secret = $secret;
    }

    private function request($method, $path, $body = null) {
        $ch = curl_init($this->base . $path);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'X-API-KEY: ' . $this->key,
                'X-API-SECRET: ' . $this->secret,
            ],
        ]);
        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
        }
        $response = curl_exec($ch);
        curl_close($ch);
        return json_decode($response, true);
    }

    public function searchTrips($from, $to, $date) {
        return $this->request('GET', "/trips?from_station_id=$from&to_station_id=$to&date=$date");
    }

    public function book($instanceId, $from, $to, $seatIds, $name, $phone) {
        return $this->request('POST', '/bookings', [
            'instance_id' => $instanceId,
            'from_station_id' => $from,
            'to_station_id' => $to,
            'seat_ids' => $seatIds,
            'passenger_name' => $name,
            'passenger_phone' => $phone,
        ]);
    }
}