AerTicket Integration
Interface to AerTicket API for flight search, verification, booking, ticket issuance, void, ancillaries, retrieval, and cancellation. Supports both UAT and Production environments with automatic authentication, retry logic, and comprehensive error handling.
Quick Start
Section titled “Quick Start”Installation
Section titled “Installation”Environment Variables (.env)
Section titled “Environment Variables (.env)”# Environment: uat (development) or productionAERTICKET_ENVIRONMENT=uat
# API credentials provided by AERTICKETAERTICKET_LOGIN=your_api_loginAERTICKET_PASSWORD=your_api_password
# Optional timeout/retry overrides# AERTICKET_UAT_TIMEOUT=30# AERTICKET_UAT_RETRY_ATTEMPTS=3Test Connection
Section titled “Test Connection”./vendor/bin/sail artisan aerticket:test-connectionBasic Flight Search
Section titled “Basic Flight Search”use App\Services\AerticketCabinetService;use App\Services\Flights\Aerticket\AerticketSearchService;
$cabinet = app(AerticketCabinetService::class);$search = new AerticketSearchService($cabinet);
$results = $search->search([ 'origin' => 'BCN', 'destination' => 'MAD', 'departure_date' => '2025-12-15', 'return_date' => '2025-12-20', 'adults' => 2, 'cabin_class' => 'economy',]);
foreach ($results->getFares() as $fare) { echo "Price: {$fare->getTotalPrice()} {$fare->getCurrency()}\n";}Multi-City Flight Search
Section titled “Multi-City Flight Search”use App\Services\Flights\Aerticket\AerticketSearchService;use App\Services\Flights\Aerticket\DTOs\Request\PassengerType;use App\Services\Flights\Aerticket\DTOs\Request\SearchOptions;use App\Services\Flights\Aerticket\DTOs\Request\SearchRequest;
// Define multi-city segments (2-6 segments allowed)$segments = [ ['departure' => 'BCN', 'destination' => 'BKK', 'date' => '2025-12-20'], ['departure' => 'BKK', 'destination' => 'CNX', 'date' => '2025-12-25'], ['departure' => 'CNX', 'destination' => 'BCN', 'date' => '2026-01-05'],];
// Build passenger types$passengers = [ PassengerType::adult(2), PassengerType::child(1),];
// Create multi-city search request$searchRequest = SearchRequest::multiCity( segments: $segments, passengerTypeList: $passengers, searchOptions: new SearchOptions(cabinClassList: ['ECONOMY']));
// Execute search$searchService = new AerticketSearchService();$response = $searchService->search($searchRequest);Service Components
Section titled “Service Components”| Service | Purpose |
|---|---|
AerticketCabinetService | HTTP client, authentication, environment switching |
AerticketSearchService | Flight search |
AerticketSearchUpsellService | Fare upgrade search |
AerticketVerifyService | Fare verification |
AerticketBookService | Booking creation |
CheckoutFlightBookingService | Per-leg checkout flight booking (international and domestic, economy and business) |
AerticketTicketIssueService | Ticket issuance |
AerticketFastlaneTicketingService | Fast-track ticket issuance |
AerticketRePriceService | Booking re-pricing |
AerticketVoidService | Booking void |
AerticketAncillaryService | Ancillary services (baggage, meals) |
AerticketFareRulesService | Fare rules and restrictions |
AerticketRetrieveService | PNR retrieval |
AerticketCancelService | Booking cancellation |
FlightRouteValidationService | Validate flight route availability for supplier tours |
Configuration
Section titled “Configuration”Environment Variables
Section titled “Environment Variables”# Environment: "uat" or "production" (determines base URLs automatically)AERTICKET_ENVIRONMENT=uat
# Credentials (shared across environments)AERTICKET_LOGIN=your_api_loginAERTICKET_PASSWORD=your_api_password
# Optional: Override default timeout settings (seconds)# AERTICKET_UAT_TIMEOUT=30# AERTICKET_PROD_TIMEOUT=30
# Optional: Configure retry settings# AERTICKET_UAT_RETRY_ATTEMPTS=3# AERTICKET_UAT_RETRY_DELAY=100
# Optional: Logging configuration# AERTICKET_LOGGING_ENABLED=true# AERTICKET_LOG_CHANNEL=singleBase URLs are configured automatically per environment in config/aerticket.php:
- UAT:
https://apihub-uat.aerticket-it.de/cabinet/and/api/v1/ - Production:
https://apihub.aerticket-it.de/cabinet/and/api/v1/
Staging Environment Safeguards
Section titled “Staging Environment Safeguards”To prevent accidental low-cost carrier bookings in staging/UAT:
- Flydubai flights (airline code: FZ) are blocked
- Flights requiring instant purchase are blocked
Controlled by AERTICKET_ENVIRONMENT variable - active when set to "uat".
Key Operations
Section titled “Key Operations”Verify Fare
Section titled “Verify Fare”use App\Services\Flights\Aerticket\AerticketVerifyService;
$verify = new AerticketVerifyService($cabinet);
$response = $verify->verify([ 'fare_id' => $fare->getId(), 'session_id' => $searchResponse->getSessionId(),]);
if ($response->isAvailable()) { $updatedPrice = $response->getPrice();}Create Booking (Async)
Section titled “Create Booking (Async)”use App\Jobs\AerticketCreateBookingJob;
AerticketCreateBookingJob::dispatch( fareId: 'fare-123', instantTicketOrder: true, userId: auth()->id())->onQueue('aerticket-bookings');Checkout Flight Booking (Per-Leg, Admin-Triggered)
Section titled “Checkout Flight Booking (Per-Leg, Admin-Triggered)”After successful checkout payment, bookings with flights move to pending_flight_booking. Admin users trigger flight booking from the booking detail page. Each flight leg is booked independently with its own FlightBooking record and PNR.
Trigger: Filament booking actions (Book All Flights / Book Flight per-leg / Retry).
Process (per leg):
- Admin action transitions booking to
flight_booking_in_progress - One
CreateCheckoutFlightBookingJobdispatched per unbooked leg toaerticket-bookingsqueue - Each job calls
CheckoutFlightBookingService::bookLegForCheckout($booking, $legIndex):- International legs:
bookInternationalLeg()re-searches round-trip viaEconomyFlightSearchService::searchInternationalOnly()(economy) orBusinessFlightSearchService::searchBusinessFlightsRaw()(business), matches by flight numbers + price - Domestic legs:
bookDomesticLeg()searches one-way viaAerticketSearchService, matches by flight numbers + price
- International legs:
- Verifies fare availability via
AerticketVerifyService - Books via
AerticketBookServicewithinstantTicketOrder=false - Creates
FlightBookingrecord withsource=CHECKOUT,booking_id,leg_index, andflight_type - Dispatches
AerticketRetrieveBookingJobfor PNR details - All-legs-booked check: Only transitions to
flights_confirmedwhen ALL legs haveFlightBookingrecords
Business class specifics:
- Business fares are bundled (1 fare = both legs, itinerary index 1 only), unlike economy mix-and-match
BookingUpsellwith typeflight_upgradecontinues to be created for financial tracking
Retry logic:
- 3 attempts, 2 max exceptions
- 120s backoff between attempts
- 420s timeout per attempt
ShouldBeUniquewithuniqueId = "checkout-flight:{bookingId}:{legIndex}"
Idempotency: Per-leg check via FlightBooking::where(booking_id, leg_index)->exists() with row-level lock.
Error handling:
- Failures produce structured error context via
CheckoutFlightBookingException::$context, which is stored inbooking_status_transitions.metadata(includingleg_index) and displayed in the admin status timeline - Context is automatically extracted from Aerticket exception types (price change, fare expired, timeout, validation, verify error, booking error) — see Checkout API - Structured error context for details
- API error responses include
provider_errorsin context for debugging with Aerticket support - Admin users receive Filament database notifications on success/failure (per-leg and all-legs-complete)
- Failed bookings can be retried from the booking detail page (top-level or per-leg)
Limitations:
- No instant ticketing (manual ticketing required)
Source: backend/app/Services/Checkout/CheckoutFlightBookingService.php
Related: Checkout API - Admin-Driven Flight Booking
Issue Ticket
Section titled “Issue Ticket”use App\Jobs\AerticketIssueTicketJob;
AerticketIssueTicketJob::dispatch( bookingReference: 'ABC123', bookingId: $flightBooking->id, userId: auth()->id())->onQueue('aerticket-tickets');Fastlane Ticketing
Section titled “Fastlane Ticketing”Faster alternative to regular ticket issuance. Booking must be at least 5 minutes old.
use App\Jobs\AerticketFastlaneTicketingJob;
AerticketFastlaneTicketingJob::dispatch( bookingReference: 'ABC123', bookingId: $flightBooking->id, userId: auth()->id())->onQueue('aerticket-fastlane');Re-Price Booking
Section titled “Re-Price Booking”use App\Services\Flights\Aerticket\AerticketRePriceService;use App\Services\Flights\Aerticket\DTOs\Request\PriceRange;
$rePriceService = new AerticketRePriceService($cabinet);
// Re-pricing with price tolerance (±5 EUR)$priceRange = new PriceRange(min: 5.0, max: 5.0);$response = $rePriceService->rePrice('ABC123', $priceRange);
if ($response->hasNewFares()) { // Price outside tolerance - requires approval $subFareToken = $response->getSubFareToken();}View Ancillaries
Section titled “View Ancillaries”use App\Services\Flights\Aerticket\AerticketAncillaryService;
$ancillaryService = new AerticketAncillaryService($cabinet);
// For bookings (after booking)$response = $ancillaryService->availableBookingAncillaries( pnrLocator: 'ABC123', ancillaryTypes: ['BAGGAGE', 'MEAL']);
foreach ($response->getAncillariesByType() as $type => $ancillaries) { foreach ($ancillaries as $ancillary) { echo "{$ancillary->name}: {$ancillary->getFormattedPrice()}\n"; }}Cancel Booking
Section titled “Cancel Booking”use App\Services\Flights\Aerticket\AerticketCancelService;
$cancel = new AerticketCancelService($cabinet);
// Normal cancellation$result = $cancel->cancelBooking('ABC123');
// Force cancellation (even if tickets issued)$result = $cancel->cancelBooking('ABC123', forceCancellation: true);Validate Flight Route
Section titled “Validate Flight Route”Performs a test multi-city search to verify that a supplier tour’s international flight legs have actual flight availability. Used in the admin panel when creating or editing supplier tours.
How it works:
- Extracts international legs from the ProductTemplate itinerary via
FlightRouteConfigGenerator - Builds a multi-city
SearchRequestwith 2 adults, sample dates ~60 days out, origin MAD - Calls
AerticketSearchService::search() - Returns structured result with success/failure, route summary, fare count, and cheapest fare
Error types: no_legs, airport_resolution, no_results, timeout, validation, api_error
Admin integration:
- Create wizard: Checkbox in Step 1 triggers validation on Next (blocks progression on failure)
- Edit/View pages: Header button with confirmation modal
Source: backend/app/Services/Flights/FlightRouteValidationService.php
Related: Supplier Tours
Passenger Validation
Section titled “Passenger Validation”Name Validation
Section titled “Name Validation”Per AerTicket API v3.17 specification:
- ASCII letters and spaces only (
/^[a-zA-Z\s]+$/) — no accents, numbers, or symbols - No
+character (causes booking failures) - Last name minimum 2 characters
- Each name maximum 57 characters
- Combined firstName + lastName: 2-57 characters total (
PassengerNameLengthrule) - Valid titles: MR, MRS, MS, CHD, INF, DR MR, etc.
These rules are enforced in both the Filament admin (camelCase fields: firstName/lastName) and the checkout API (snake_case fields: first_name/last_name). The PassengerNameLength rule auto-detects the naming convention.
Usage: See PassengerValidationRules::firstName() and ::lastName() for the rule arrays.
Source: backend/app/Rules/PassengerValidationRules.php, backend/app/Rules/PassengerNameLength.php
Age Category Validation
Section titled “Age Category Validation”- Infant (INF): 0-23 months
- Child (CHD): 2-15 years
- Adult (ADT): 16+ years
use App\Enums\AgeCategory;
$ageCategory = AgeCategory::fromDateOfBirth($dateOfBirth);
if (!$ageCategory->isValidForDateOfBirth($dateOfBirth)) { $errorMessage = $ageCategory->getValidationErrorMessage($dateOfBirth);}Error Handling
Section titled “Error Handling”Exception Types
Section titled “Exception Types”| Exception | Description |
|---|---|
AerticketValidationException | Invalid input |
AerticketTimeoutException | Request timeout |
AerticketSearchException | Search error |
AerticketVerifyException | Fare verification error |
AerticketBookingException | Booking error |
AerticketTicketIssueException | Ticket issuance error |
AerticketRePricingException | Re-pricing error |
AerticketFareExpiredException | Fare expired (410) |
AerticketPriceChangeException | Price changed |
Laravel Context Enrichment (Nightwatch)
Section titled “Laravel Context Enrichment (Nightwatch)”All Aerticket API requests and exceptions automatically add context data to Laravel’s Context facade, which is included in Nightwatch error reports for debugging.
Transport-level context (added by AerticketCabinetService):
aerticket_endpoint- API operation (e.g., “verify-fare”, “search”, “create-booking”)aerticket_environment- “uat” or “production”aerticket_timeout- Configured timeout in secondsaerticket_apihubflowid- Aerticket’s internal tracking ID from response header
Domain-level context (added by exception classes):
aerticket_fare_id- Fare ID (on booking/verify exceptions)aerticket_booking_reference- PNR locator (on retrieve/cancel/ticket exceptions)aerticket_error_details- Error details array when available
This context is automatically captured in Nightwatch reports when exceptions occur, making it easier to debug issues with AerTicket support.
Source: backend/app/Services/AerticketCabinetService.php, backend/app/Exceptions/Aerticket/*.php
Example Error Handling
Section titled “Example Error Handling”try { $results = $search->search($data);} catch (AerticketValidationException $e) { return response()->json(['error' => $e->getMessage()], 422);} catch (AerticketTimeoutException $e) { return response()->json(['error' => 'Request timed out'], 504);} catch (AerticketSearchException $e) { Log::error('Search failed', ['error' => $e->getMessage()]); return response()->json(['error' => 'Search failed'], 500);}Testing
Section titled “Testing”# All AerTicket tests./vendor/bin/sail artisan test --filter=Aerticket
# Connection test./vendor/bin/sail artisan aerticket:test-connection
# Re-pricing tests./vendor/bin/sail artisan test --filter=RePrice
# Ancillary tests./vendor/bin/sail artisan test --filter=AncillaryTroubleshooting
Section titled “Troubleshooting”Authentication Failure (401)
Section titled “Authentication Failure (401)”php artisan tinker>>> config('aerticket.credentials.login')Connection Timeout
Section titled “Connection Timeout”Increase timeout in .env:
AERTICKET_TIMEOUT=60Missing apihubflowid
Section titled “Missing apihubflowid”Check logs:
./vendor/bin/sail artisan pail --filter="apihubflowid"The apihubflowid is required when contacting AerTicket support.
Related Documentation
Section titled “Related Documentation”- Async Booking Workflow - Queue-based booking
- Flight Search UI - FilamentPHP interface
- Airport Search - Airport lookup service
- Queue System - Queue infrastructure