Skip to content

Async Booking Workflow

Queue-based asynchronous flight booking workflow with real-time status updates and automatic ticket issuance.

The async booking system provides:

  • Immediate API response (<1s vs 60-90s synchronous)
  • Queue-based processing with automatic retries
  • Real-time notifications via FilamentPHP
  • Instant ticket order support with 2-minute delay
  • Status polling endpoint for frontend integration
use App\Jobs\AerticketCreateBookingJob;
AerticketCreateBookingJob::dispatch(
fareId: 'fare-123',
instantTicketOrder: true, // 2-minute delay for ticket issuance
userId: auth()->id()
)->onQueue('aerticket-bookings');
return response()->json([
'success' => true,
'message' => 'Booking is being created asynchronously',
'status' => 'processing',
]);
GET /api/bookings/{ref}/retrieve-status
Response:
{
"retrieve_status": "completed",
"is_ready": true,
"pnr_status": "TICKETED"
}
User Request
AerticketCreateBookingJob (aerticket-bookings queue)
├── Success ──► FlightBooking created
│ │
│ ▼
│ AerticketRetrieveBookingJob (2-min delay if instant ticket)
│ │
│ ▼
│ PNR status updated
└── Failure ──► Retry (3 attempts) ──► Failed notification
QueuePurposePriority
aerticket-ticketsTicket issuanceHighest
aerticket-bookingsBooking creationHigh
aerticket-fastlaneFastlane ticketingHigh
defaultGeneral tasksNormal
public int $tries = 3;
public int $timeout = 120;
public array $backoff = [30, 60, 180]; // 30s, 1m, 3m

Properties:

  • fareId - The fare identifier from search/verify
  • instantTicketOrder - Whether to auto-issue tickets
  • userId - User who initiated the booking

Behavior:

  1. Creates booking via AerTicket API
  2. Stores FlightBooking record
  3. Dispatches retrieve job (with delay if instant ticket)
  4. Sends success/failure notification
public int $tries = 3;
public int $timeout = 60;
public array $backoff = [10, 30, 60];

Delay Logic:

  • Instant ticket order: 2-minute delay before retrieve
  • Manual ticketing: Immediate retrieve

Purpose:

  • Updates PNR status from AerTicket
  • Syncs ticket numbers if issued
  • Updates retrieve_status for polling
StatusDescription
pendingBooking in progress
confirmedPNR created successfully
ticketedTickets issued
cancelledBooking cancelled
failedBooking failed
StatusDescription
pendingRetrieve not started
in_progressRetrieve job running
completedRetrieve successful
failedRetrieve failed
async function pollBookingStatus(bookingRef) {
const maxAttempts = 30;
const interval = 5000; // 5 seconds
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const response = await fetch(`/api/bookings/${bookingRef}/retrieve-status`);
const data = await response.json();
if (data.is_ready) {
return data;
}
await new Promise(resolve => setTimeout(resolve, interval));
}
throw new Error('Booking status check timed out');
}
// Usage
try {
const status = await pollBookingStatus('ABC123');
console.log('PNR Status:', status.pnr_status);
} catch (error) {
console.error('Failed to get booking status');
}
function useBookingStatus(bookingRef: string) {
const [status, setStatus] = useState<BookingStatus | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const poll = async () => {
try {
const response = await fetch(`/api/bookings/${bookingRef}/retrieve-status`);
const data = await response.json();
setStatus(data);
if (!data.is_ready) {
setTimeout(poll, 5000);
} else {
setLoading(false);
}
} catch (error) {
setLoading(false);
}
};
poll();
}, [bookingRef]);
return { status, loading };
}
public function failed(\Throwable $exception): void
{
Log::error('Booking creation failed', [
'fare_id' => $this->fareId,
'user_id' => $this->userId,
'error' => $exception->getMessage(),
]);
Notification::make()
->title('Booking Failed')
->body($exception->getMessage())
->danger()
->sendToDatabase(User::find($this->userId));
}

Jobs retry with exponential backoff:

  1. First retry: 30 seconds
  2. Second retry: 1 minute
  3. Third retry: 3 minutes

After all retries exhausted, job moves to failed_jobs table.

Terminal window
# Check queue status
./vendor/bin/sail artisan queue:monitor
# View failed jobs
./vendor/bin/sail artisan queue:failed
# Retry failed job
./vendor/bin/sail artisan queue:retry {job_id}
Terminal window
# Real-time logs
./vendor/bin/sail artisan pail --filter="Aerticket"
# View booking-specific logs
./vendor/bin/sail artisan pail --filter="AerticketCreateBookingJob"
ColumnTypeDescription
booking_referenceVARCHARPNR locator
booking_statusENUMconfirmed, pending, etc.
retrieve_statusENUMpending, completed, etc.
pnr_statusVARCHARAerTicket PNR status
ticket_statusENUMnot_issued, issued, voided
total_amountDECIMALTotal booking amount
travel_dateDATEDeparture date
return_dateDATEReturn date (nullable)
  1. Check queue worker is running:
Terminal window
docker ps | grep queue
  1. Check for failed jobs:
Terminal window
./vendor/bin/sail artisan queue:failed
  1. View job exception:
Terminal window
./vendor/bin/sail artisan tinker
>>> DB::table('failed_jobs')->latest()->first()->exception;
  1. Check retrieve job was dispatched:
Terminal window
./vendor/bin/sail artisan pail --filter="AerticketRetrieveBookingJob"
  1. Manually trigger retrieve:
use App\Jobs\AerticketRetrieveBookingJob;
AerticketRetrieveBookingJob::dispatch(
bookingReference: 'ABC123',
bookingId: 123
)->onQueue('aerticket-bookings');