Search Flights - Tìm Kiếm Chuyến Bay
API Endpoint: POST /api/v1/services/booking/v1/tim-chuyen
Real-time: Socket.IO sever-sent-data-chieu-di + sever-sent-data-end
📋 Overview
Tìm kiếm chuyến bay sử dụng hybrid approach:
- HTTP POST → Trigger search trên server
- Socket.IO → Nhận kết quả real-time khi server tìm được flight
Flow:
1. Client connects Socket.IO → Get socket.id
2. Client calls HTTP POST /tim-chuyen with socket.id
3. Server searches flights and pushes to Socket.IO
4. Client receives flights via Socket.IO events:
- sever-sent-data-chieu-di (each flight)
- sever-sent-data-end (complete)
🔌 Socket.IO Setup
Connection
const io = require("socket.io-client");
const socket = io("{{socket_url}}", {
transports: ["websocket"],
query: {
token: accessToken, // ← Pass access_token as query param
},
});
Configuration:
| Option | Value | Description |
|---|---|---|
transports | ['websocket'] | Force websocket (no polling) |
query.token | access_token | OAuth token for authentication |
Events to Listen
| Event Name | Description | Data |
|---|---|---|
connect | Socket connected | - |
sever-sent-data-chieu-di | Each flight result | Flight object |
sever-sent-data-end | Search completed | Airlines array |
🔌 HTTP Search API
Endpoint
POST {{base_url}}/api/v1/services/booking/v1/tim-chuyen
Headers
{
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": "Bearer {access_token}"
}
Request Body
{
"sid": "JTT0VfCd_TQs6Pz5AAAD",
"chang_bay": [
{
"index": 0,
"diem_di": "SGN",
"diem_den": "HAN",
"ngay_di": "01-11-2025"
}
],
"nhieu_chang": 0,
"hanh_khach": {
"nguoi_lon": 1,
"tre_em": 0,
"em_be": 0
},
"hang_bay": ["VJ"],
"ve_re_thang": 0,
"ma_khuyen_mai": ""
}
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
sid | string | ✅ Yes | Socket.IO session ID from socket.id |
chang_bay | array | ✅ Yes | Array of flight segments |
chang_bay[].index | number | ✅ Yes | Segment index (0-based) |
chang_bay[].diem_di | string | ✅ Yes | Departure airport code (e.g., "SGN") |
chang_bay[].diem_den | string | ✅ Yes | Arrival airport code (e.g., "HAN") |
chang_bay[].ngay_di | string | ✅ Yes | Departure date (format: "DD-MM-YYYY") |
nhieu_chang | number | ✅ Yes | Multi-city flag (0 = one-way/round-trip, 1 = multi-city) |
hanh_khach | object | ✅ Yes | Passenger counts |
hanh_khach.nguoi_lon | number | ✅ Yes | Number of adults (≥ 1) |
hanh_khach.tre_em | number | ✅ Yes | Number of children (0-11 years) |
hanh_khach.em_be | number | ✅ Yes | Number of infants (< 2 years) |
hang_bay | array | ✅ Yes | Airline codes: ["VJ"] for VietJet |
ve_re_thang | number | ❌ | Monthly ticket flag (0 = no, 1 = yes) |
ma_khuyen_mai | string | ❌ | Promotion code |
ve_id | number | ⚠️ Special | Required for update-journey flow (see below) |
⚠️ Important Notes:
- Date Format: Must be
DD-MM-YYYY(e.g.,"01-11-2025") - Socket ID: Must be obtained from Socket.IO connection first
- ve_id: Only include when searching for update-journey (đổi hành trình)
- Normal search: Omit
ve_id - Update journey: Include
ve_id(booking ID to update)
- Normal search: Omit
✅ HTTP Success Response
Status: 200 OK
{
"message": "Tìm chuyến bay thành công",
"data": 1,
"status": "success",
"code": 200
}
This response only confirms search was triggered.
Actual flight results come via Socket.IO events.
📡 Socket.IO Flight Events
Event: sever-sent-data-chieu-di
Emitted for each flight found. Client should collect these in an array.
Example Data:
{
"so_hieu": "VJ 168",
"uuid": "dd70d6c6-aee6-11f0-bd8c-86d224031662",
"hang_bay": "VJ",
"gio_di": "22:50",
"gio_den": "01:00",
"ngay_di": "2025-11-01T22:50:00.000+07:00",
"ngay_den": "2025-11-02T01:00:00.000+07:00",
"diem_di": {
"code": "SGN",
"name": "Tan Son Nhat International Airport",
"city": "Ho Chi Minh City"
},
"diem_den": {
"code": "HAN",
"name": "Noi Bai International Airport",
"city": "Hanoi"
},
"thoi_gian_bay": 130,
"may_bay": "A321",
"gia_ve": [
{
"index": 0,
"hang_ve": "Eco1",
"cabin": "ECONOMY",
"fare_basis": "ECO",
"loai_hang_ve": "Eco1",
"nguoi_lon": {
"gia_net": 1086300,
"gia_hien_thi": 1086300,
"thue_phi": 420000,
"phi_dich_vu": 0
},
"tre_em": {
"gia_net": 815000,
"gia_hien_thi": 815000
},
"em_be": {
"gia_net": 200000,
"gia_hien_thi": 200000
}
},
{
"index": 1,
"hang_ve": "Deluxe",
"cabin": "DELUXE",
"fare_basis": "DELUXE",
"loai_hang_ve": "Deluxe",
"nguoi_lon": {
"gia_net": 2186300,
"gia_hien_thi": 2186300,
"thue_phi": 420000,
"phi_dich_vu": 0
}
}
],
"tuy_chon_gia_ve_mac_dinh": {
"hang_ve": "Eco1",
"nguoi_lon": {
"gia_net": 1086300,
"gia_hien_thi": 1086300
}
},
"end": false
}
Key Fields:
| Field | Type | Description |
|---|---|---|
so_hieu | string | Flight number (e.g., "VJ 168") |
uuid | string | Flight UUID (critical for booking) |
hang_bay | string | Airline code ("VJ" = VietJet) |
gio_di | string | Departure time (HH:mm) |
gio_den | string | Arrival time (HH:mm) |
ngay_di | string | Departure datetime (ISO 8601) |
ngay_den | string | Arrival datetime (ISO 8601) |
diem_di | object | Departure airport details |
diem_den | object | Arrival airport details |
thoi_gian_bay | number | Flight duration (minutes) |
may_bay | string | Aircraft type |
gia_ve | array | Fare options (ECONOMY, DELUXE, SKYBOSS) |
tuy_chon_gia_ve_mac_dinh | object | Default selected fare |
end | boolean | Always false for flight data |
Event: sever-sent-data-end
Emitted when search is complete. No more flights will be sent.
Example Data:
["VJ"]
Meaning: Search completed for VietJet (VJ)
💰 Fare Options (gia_ve array)
Each flight has multiple fare classes:
| Cabin | hang_ve | Price Range | Features |
|---|---|---|---|
| ECONOMY | Eco1, Eco2, Eco3 | 1.0M - 1.5M VND | Basic fare |
| DELUXE | Deluxe | 2.0M - 3.0M VND | Priority boarding, free baggage |
| SKYBOSS | SkyBoss | 4.0M+ VND | Business class, lounge access |
Note: Fare class affects available services and pricing.
💡 Implementation Flow
Step 1: Connect Socket.IO
// Socket.IO client connection
const socket = io("{{socket_url}}", {
transports: ["websocket"],
query: { token: accessToken },
});
socket.on("connect", () => {
const sid = socket.id; // Save this for HTTP request
});
Step 2: Call HTTP Search API
curl -X POST "{{base_url}}/api/v1/services/booking/v1/tim-chuyen" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {access_token}" \
-d '{
"sid": "socket-id-from-step-1",
"chang_bay": [{
"index": 0,
"diem_di": "SGN",
"diem_den": "HAN",
"ngay_di": "01-11-2025"
}],
"nhieu_chang": 0,
"hanh_khach": {
"nguoi_lon": 1,
"tre_em": 0,
"em_be": 0
},
"hang_bay": ["VJ"],
"ve_re_thang": 0,
"ma_khuyen_mai": ""
}'
Step 3: Listen for Socket.IO Events
// Receive each flight
socket.on("sever-sent-data-chieu-di", (flightData) => {
if (flightData.end === true) return;
// Store flight data
flights.push(flightData);
});
// Search completed
socket.on("sever-sent-data-end", (airlines) => {
// All flights received
// Select flight and proceed to booking
});
Step 4: Select Flight
// Select first flight
const selectedFlight = flights[0];
// Select desired fare (e.g., ECONOMY)
const selectedFare =
selectedFlight.gia_ve.find((gv) => gv.cabin === "ECONOMY") ||
selectedFlight.gia_ve[0];
// Save for booking
const flightUUID = selectedFlight.uuid;
const fareIndex = selectedFare.index;
🔄 Special Case: Update Journey Search
When searching for update-journey (đổi hành trình), must include ve_id:
const requestBody = {
sid: socket.id,
chang_bay: [...],
hanh_khach: {...},
hang_bay: ['VJ'],
ve_id: 94 // ← CRITICAL: Include booking ID to update
};
Why ve_id is needed:
- Tells backend to persist full flight data to database
- Without
ve_id, backend only caches flight data temporarily - Update journey API needs full data in DB to work
Rule:
- Normal search: Omit
ve_id - Update journey: Include
ve_id
⏱️ Timing
Wait Time After Search:
For update-journey flow, wait 5 seconds after receiving flights:
socket.on("sever-sent-data-end", async (airlines) => {
// ...collect flights...
// Wait for DB persistence (update-journey only)
if (requestBody.ve_id) {
console.log("⏳ Waiting 5s for DB persistence...");
await new Promise((resolve) => setTimeout(resolve, 5000));
}
resolve(flights);
});
Why: Backend needs time to persist full flight data to DB.
⚠️ Important Notes
1. Socket ID Must Be Fresh
// ❌ Wrong: Reuse old socket ID
const sid = "old-socket-id-123";
// ✅ Correct: Get from current connection
socket.on("connect", () => {
const sid = socket.id;
// Use immediately in search request
});
2. Date Format
// ✅ Correct
"ngay_di": "01-11-2025" // DD-MM-YYYY
// ❌ Wrong
"ngay_di": "2025-11-01" // YYYY-MM-DD
"ngay_di": "11/01/2025" // MM/DD/YYYY
3. Fare Selection
Select desired fare class from gia_ve array:
// Select by cabin
const ecoFare = flight.gia_ve.find((gv) => gv.cabin === "ECONOMY");
const deluxeFare = flight.gia_ve.find((gv) => gv.cabin === "DELUXE");
// Or use first fare (usually cheapest)
const selectedFare = flight.gia_ve[0];
4. Flight UUID
Save uuid for booking:
const flightUUID = selectedFlight.uuid; // ← Critical for hold/issue
🐛 Common Issues
No Flights Found
Possible Causes:
- No flights available for the route/date
- Route not supported by airline
- Date too far in future (> 6 months)
- Date in the past
Solution: Try different route or date (e.g., SGN-HAN usually has many flights)
Socket Timeout
Response: No sever-sent-data-end event after 60 seconds
Possible Causes:
- Network connectivity issues
- Invalid search parameters
- Server overload
Solution: Implement timeout and retry logic
Wrong Fare Prices
Issue: Prices don't match expected values
Solution: Always use gia_ve array for accurate pricing:
const price = fare.nguoi_lon.gia_net; // Use gia_net (net price)
// NOT gia_hien_thi (may include promotions)
🔗 Next Steps
After searching flights, proceed to:
- Hold Booking →
04-HOLD-BOOKING.md - Issue Ticket →
05-ISSUE-TICKET.md
Or see complete flow: 06-BOOKING-FULL-FLOW.md
📝 Notes
- Hybrid approach: HTTP + Socket.IO
- Real-time flight updates
- Supports one-way, round-trip, multi-city
- VietJet airline (VJ)
- Multiple fare classes (ECO, DELUXE, SKYBOSS)
- Must connect Socket.IO first to get
sid - Results are real-time - may vary by date/time
- For update-journey, must include
ve_id