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
Search for Trips
Passengers search for available trips based on route and date preferences.
Select Trip
Choose a trip and specify the number of seats needed.
Choose Payment Method
Select from available payment options: Cash, Wallet, Bank Transfer, or Moyasar.
Submit Booking Request
Create a pending booking that requires driver approval.
Driver Reviews
Driver accepts or rejects the booking request.
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:
Pending
Initial state after booking request. Awaiting driver approval.
Confirmed
Driver has accepted the booking. Payment processed (if applicable).
Completed
Trip has finished successfully.
Rejected
Driver declined the booking request.
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" : "عدد المقاعد المتاحة غير كافٍ لإتمام الحجز"
}
Insufficient Wallet Balance
{
"success" : false ,
"message" : "رصيد المحفظة غير كافٍ. الرصيد الحالي: 100 SAR، المطلوب: 250 SAR"
}
{
"success" : false ,
"message" : "لا يمكن حجز مقعد في رحلة غير مجدولة"
}
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.