Skip to main content

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:

  1. HTTP POST → Trigger search trên server
  2. 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:

OptionValueDescription
transports['websocket']Force websocket (no polling)
query.tokenaccess_tokenOAuth token for authentication

Events to Listen

Event NameDescriptionData
connectSocket connected-
sever-sent-data-chieu-diEach flight resultFlight object
sever-sent-data-endSearch completedAirlines 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:

FieldTypeRequiredDescription
sidstring✅ YesSocket.IO session ID from socket.id
chang_bayarray✅ YesArray of flight segments
chang_bay[].indexnumber✅ YesSegment index (0-based)
chang_bay[].diem_distring✅ YesDeparture airport code (e.g., "SGN")
chang_bay[].diem_denstring✅ YesArrival airport code (e.g., "HAN")
chang_bay[].ngay_distring✅ YesDeparture date (format: "DD-MM-YYYY")
nhieu_changnumber✅ YesMulti-city flag (0 = one-way/round-trip, 1 = multi-city)
hanh_khachobject✅ YesPassenger counts
hanh_khach.nguoi_lonnumber✅ YesNumber of adults (≥ 1)
hanh_khach.tre_emnumber✅ YesNumber of children (0-11 years)
hanh_khach.em_benumber✅ YesNumber of infants (< 2 years)
hang_bayarray✅ YesAirline codes: ["VJ"] for VietJet
ve_re_thangnumberMonthly ticket flag (0 = no, 1 = yes)
ma_khuyen_maistringPromotion code
ve_idnumber⚠️ SpecialRequired for update-journey flow (see below)

⚠️ Important Notes:

  1. Date Format: Must be DD-MM-YYYY (e.g., "01-11-2025")
  2. Socket ID: Must be obtained from Socket.IO connection first
  3. 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)

✅ 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:

FieldTypeDescription
so_hieustringFlight number (e.g., "VJ 168")
uuidstringFlight UUID (critical for booking)
hang_baystringAirline code ("VJ" = VietJet)
gio_distringDeparture time (HH:mm)
gio_denstringArrival time (HH:mm)
ngay_distringDeparture datetime (ISO 8601)
ngay_denstringArrival datetime (ISO 8601)
diem_diobjectDeparture airport details
diem_denobjectArrival airport details
thoi_gian_baynumberFlight duration (minutes)
may_baystringAircraft type
gia_vearrayFare options (ECONOMY, DELUXE, SKYBOSS)
tuy_chon_gia_ve_mac_dinhobjectDefault selected fare
endbooleanAlways 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:

Cabinhang_vePrice RangeFeatures
ECONOMYEco1, Eco2, Eco31.0M - 1.5M VNDBasic fare
DELUXEDeluxe2.0M - 3.0M VNDPriority boarding, free baggage
SKYBOSSSkyBoss4.0M+ VNDBusiness 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;

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:

  1. No flights available for the route/date
  2. Route not supported by airline
  3. Date too far in future (> 6 months)
  4. 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:

  1. Network connectivity issues
  2. Invalid search parameters
  3. 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:

  1. Hold Booking04-HOLD-BOOKING.md
  2. Issue Ticket05-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