Skip to main content

Overview

The booking system handles the complete lifecycle of trip reservations, from initial seat requests to confirmation and completion. It supports multiple payment methods and manages seat availability in real-time.

Booking Flow

1

Search for Trips

Passengers search for available trips based on route and date preferences.
2

Select Trip

Choose a trip and specify the number of seats needed.
3

Choose Payment Method

Select from available payment options: Cash, Wallet, Bank Transfer, or Moyasar.
4

Submit Booking Request

Create a pending booking that requires driver approval.
5

Driver Reviews

Driver accepts or rejects the booking request.
6

Confirmation

Upon acceptance, booking is confirmed and payment is processed.

Creating a Booking

API Endpoints

There are two booking endpoints:

JSON Endpoint

POST /api/trips/{tripId}/book-seat
For simple bookings without image uploads.

Form Data Endpoint

POST /api/v1/trips/{tripId}/book-seat
Supports bank transfer receipt image upload.

Request Body (JSON)

{
  "passengerId": "p_123456",
  "requestedSeatsCount": 2,
  "paymentMethod": "Wallet",
  "bankTransferData": null
}

Request Body (Form Data with Receipt)

passengerId: p_123456
requestedSeatsCount: 2
paymentMethod: BankTransfer
bankAccountId: ba_789012
note: Transfer completed on 2026-03-10
receiptImage: [image file]

Implementation

The booking handler performs comprehensive validation and transaction management:
src/services/Trips/Trips.Api/Features/BookSeat/BookSeatHandler.cs
public async Task<BookSeatResponse> Handle(BookSeatCommand request, CancellationToken cancellationToken)
{
    Trip trip = await tripRepository.GetAsync(request.TripId, cancellationToken)
                ?? throw new ArgumentException("الرحلة غير موجودة");

    if (!string.Equals(trip.Status?.Trim(), TripStatuses.Scheduled, StringComparison.OrdinalIgnoreCase))
    {
        throw new InvalidOperationException("لا يمكن حجز مقعد في رحلة غير مجدولة");
    }

    int clampedAvailable = Math.Min(Math.Max(trip.AvailableSeatCount, 0), trip.TotalSeats);
    int clampedReserved = Math.Min(Math.Max(trip.ReservedSeatCount, 0), trip.TotalSeats);
    int availableForBooking = Math.Max(0, clampedAvailable - clampedReserved);
    
    if (availableForBooking < request.RequestedSeatsCount)
    {
        throw new InvalidOperationException("عدد المقاعد المتاحة غير كافٍ لإتمام الحجز");
    }

Seat Management

Seats are reserved immediately upon booking request to prevent double-booking:
src/services/Trips/Trips.Api/Features/BookSeat/BookSeatHandler.cs
trip.ReservedSeatCount += request.RequestedSeatsCount;
logger.LogInformation("Created booking {BookingId} with {RequestedSeatsCount} requested seats", 
    bookingId, bookingMaster.RequestedSeatsCount);
Reserved seats are held even for pending bookings. If a booking is rejected, seats are automatically released back to the available pool.

Payment Methods

The system supports four payment methods:

1. Cash Payment

{
  "paymentMethod": "Cash"
}
Payment collected in person by the driver during the trip.

2. Wallet Payment

Deducts from passenger’s wallet balance immediately upon driver acceptance:
{
  "paymentMethod": "Wallet"
}
Validation:
src/services/Trips/Trips.Api/Features/BookSeat/BookSeatHandler.cs
if (paymentMethod == PaymentMethods.Wallet)
{
    await BookingPaymentValidator.ValidateWalletBalanceAsync(
        request.PassengerId, tripCurrency, totalPrice,
        passengerWalletRepository, cancellationToken);
}

3. Bank Transfer

Passengers upload a receipt for admin verification:
{
  "paymentMethod": "BankTransfer",
  "bankTransferData": {
    "bankAccountId": "ba_789012",
    "note": "Transfer completed on 2026-03-10",
    "receiptImageUrl": "https://storage.example.com/receipts/abc123.jpg"
  }
}
Receipt Upload Validation:
src/services/Trips/Trips.Api/Features/BookSeat/BookSeatEndpoint.cs
string[] allowedExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp"];
string extension = Path.GetExtension(receiptImage.FileName).ToLowerInvariant();

if (!allowedExtensions.Contains(extension))
{
    return Results.BadRequest(new BookSeatResponse
    {
        Success = false,
        Message = "نوع الملف غير مدعوم. الأنواع المسموح بها: JPG, JPEG, PNG, GIF, WEBP"
    });
}

if (receiptImage.Length > 10 * 1024 * 1024)
{
    return Results.BadRequest(new BookSeatResponse
    {
        Success = false,
        Message = "حجم الملف كبير جداً. الحد الأقصى 10 ميجابايت"
    });
}

4. Moyasar Payment Gateway

Integration with Moyasar for online credit/debit card payments:
{
  "paymentMethod": "Moyasar"
}
Response includes payment order:
{
  "success": true,
  "bookingId": "b_abc123",
  "paymentOrderId": "po_xyz789",
  "publishableKey": "pk_test_...",
  "requiresPayment": true,
  "message": "تم إرسال طلب الحجز بنجاح. يرجى إتمام الدفع عبر Moyasar لإكمال الحجز"
}

Transaction Management

Bookings use database transactions to ensure data consistency:
src/services/Trips/Trips.Api/Features/BookSeat/BookSeatHandler.cs
using (DataConnectionTransaction transaction = await db.BeginTransactionAsync(cancellationToken))
{
    try
    {
        await bookingRepository.AddBookingAsync(bookingMaster, cancellationToken);
        await tripRepository.UpdateAsync(trip, cancellationToken);

        if (paymentMethod == PaymentMethods.Moyasar)
        {
            var paymentOrder = new PaymentOrder { /* ... */ };
            await paymentOrderRepository.CreatePaymentOrderAsync(paymentOrder, cancellationToken);
        }

        if (paymentMethod == PaymentMethods.BankTransfer)
        {
            var bankTransfer = new BankTransfer { /* ... */ };
            await bankTransferRepository.AddBankTransferAsync(bankTransfer, cancellationToken);
        }

        await transaction.CommitAsync(cancellationToken);
    }
    catch (Exception ex)
    {
        await transaction.RollbackAsync(cancellationToken);
        trip.ReservedSeatCount -= request.RequestedSeatsCount;
        throw;
    }
}

Driver Booking Management

View Booking Requests

Drivers can view all pending booking requests:
GET /api/bookings/driver/{driverId}

Accept Booking

POST /api/bookings/{bookingId}/accept
{
  "driverId": "d_789012",
  "driverNotes": "Welcome aboard!"
}
Acceptance Handler:
src/services/Trips/Trips.Api/Features/ManageBookings/AcceptBooking/AcceptBookingHandler.cs
public async Task<AcceptBookingResponse> Handle(AcceptBookingCommand request, CancellationToken cancellationToken)
{
    Booking? booking = await bookingRepository.GetBookingAsync(request.BookingId, cancellationToken);
    
    if (booking.Status != BookingStatuses.Pending)
    {
        return new AcceptBookingResponse { 
            Success = false, 
            Message = "لا يمكن قبول طلب حجز غير معلق" 
        };
    }

    var context = new BookingAcceptanceContext(
        booking, trip,
        PerformedById: request.DriverId,
        PerformedByType: PerformedByTypes.User,
        DriverNotes: request.DriverNotes ?? string.Empty,
        RejectionReason: request.Reason);

    BookingAcceptanceResult result = await acceptanceService.ProcessAcceptanceAsync(context, cancellationToken);
    
    // Payment processing and wallet updates happen here
}

Reject Booking

POST /api/bookings/{bookingId}/reject
{
  "reason": "Trip is fully booked"
}

Cancel Booking

Passengers can cancel their own bookings:
POST /api/bookings/{bookingId}/cancel
Cancellation policies and refund rules are applied based on the time remaining until trip departure.

Booking Statuses

Bookings progress through these statuses:
1

Pending

Initial state after booking request. Awaiting driver approval.
2

Confirmed

Driver has accepted the booking. Payment processed (if applicable).
3

Completed

Trip has finished successfully.
4

Rejected

Driver declined the booking request.
5

Cancelled

Booking was cancelled by passenger, driver, or admin.

Notifications

The system sends notifications at key booking events:
src/services/Trips/Trips.Api/Features/BookSeat/BookSeatHandler.cs
if (paymentMethod != PaymentMethods.Moyasar)
{
    var notification = new BookingCreatedNotification(
        bookingMaster.Id,
        trip.Id,
        trip.DriverId,
        request.PassengerId,
        bookingMaster.PassengerName,
        request.RequestedSeatsCount,
        totalPrice,
        trip.From,
        trip.To,
        trip.DepartureTimeUtc);
    await messageBus.PublishAsync(notification);
}

Wallet Deduction Notification

src/services/Trips/Trips.Api/Features/ManageBookings/AcceptBooking/AcceptBookingHandler.cs
if (result.PaymentBranch == "Wallet" && result.PassengerWalletResult != null)
{
    var walletNotification = new WalletDeductedNotification(
        booking.Id, trip.Id, booking.PassengerId,
        booking.TotalPrice, tripCurrency, result.PassengerWalletResult.BalanceAfter,
        trip.From, trip.To);
    await messageBus.PublishAsync(walletNotification);
}

Admin Booking Operations

Administrators have full control over bookings:

View All Requests

GET /api/admin/trips/{tripId}/requests?status=Pending

Approve Pending Bookings

POST /api/admin/trips/{tripId}/requests/{bookingId}/accept

Add Passenger Directly

POST /api/admin/trips/{tripId}/passengers
{
  "passengerId": "p_123456",
  "requestedSeatsCount": 1,
  "paymentMethod": "Cash",
  "notes": "Special request from customer service"
}

Error Handling

{
  "success": false,
  "message": "عدد المقاعد المتاحة غير كافٍ لإتمام الحجز"
}
{
  "success": false,
  "message": "رصيد المحفظة غير كافٍ. الرصيد الحالي: 100 SAR، المطلوب: 250 SAR"
}
{
  "success": false,
  "message": "لا يمكن حجز مقعد في رحلة غير مجدولة"
}
{
  "success": false,
  "message": "نوع الملف غير مدعوم. الأنواع المسموح بها: JPG, JPEG, PNG, GIF, WEBP"
}

Best Practices

Reserve Early

Book seats early to ensure availability, especially for popular routes.

Choose Payment Wisely

Use wallet payment for instant confirmation. Bank transfers require admin approval.

Upload Clear Receipts

When using bank transfer, ensure receipt images are clear and legible.

Monitor Status

Check booking status regularly and respond to driver communications promptly.

Build docs developers (and LLMs) love