Skip to main content

Buy Seat - Mua Chỗ Ngồi

APIs:

  1. POST /api/v1/services/booking/v1/danh-sach-phu-tro-dat-ve - Get seat map
  2. POST /api/v1/services/booking/v1/cap-nhat-cho-ngoi - Select & confirm seat

📋 Overview

Mua/chọn chỗ ngồi cho booking đã tồn tại.

Flow:

1. Get seat map (from services API)
2. Filter available seats
3. Select seat
4. Confirm & Payment

Important: Seat prices may vary depending on fare class and seat position.


🔌 Step 1: Get Seat Map

API Endpoint

POST {{base_url}}/api/v1/services/booking/v1/danh-sach-phu-tro-dat-ve

Same API as Update Services - Returns both services AND seats.

Headers

{
"Content-Type": "application/json",
"Authorization": "Bearer {access_token}"
}

Request Body

{
"chang_bay": [
{
"index": 0,
"index_chuyen_bay": 0,
"ve_id": 94
}
]
}

Response

{
"message": "Lấy danh sách phụ trợ thành công",
"data": {
"0": {
"cho_ngoi": {
"1": {
"A": {
"ma_ghe": "1A",
"ma_cho_ngoi": "encoded-seat-code",
"uuid": "dc3fc6f0-aeb7-11f0-8b81-86d224031662",
"gia_net": 64800,
"gia_fare": 60000,
"thue_phi": 4800,
"hop_le": true,
"trang_thai": false,
"loi_thoat": true,
"hang_bay": "VJ",
"index_chuyen_bay": 0
}
},
"12": {
"C": {
"ma_ghe": "12C",
"uuid": "dc435226-aeb7-11f0-8b81-86d224031662",
"gia_net": 54000,
"hop_le": true,
"trang_thai": false,
"index_chuyen_bay": 0
}
}
}
}
},
"status": "success"
}

Seat Fields:

FieldTypeDescription
uuidstringSeat UUID (for booking)
ma_ghestringSeat number (e.g., "12C")
ma_cho_ngoistringEncoded seat code
gia_netnumberSeat price (VND)
hop_lebooleanValid: true = can book
trang_thaibooleanOccupied: false = available
loi_thoatbooleanExit row
index_chuyen_baynumberFlight index (0-based)

Availability Check:

// Seat is available if:
seat.hop_le === true && seat.trang_thai === false;

🔌 Step 2: Confirm Seat

API Endpoint

POST {{base_url}}/api/v1/services/booking/v1/cap-nhat-cho-ngoi

Headers

{
"Content-Type": "application/json",
"Authorization": "Bearer {access_token}"
}

Request Body

{
"ve_id": 94,
"cho_ngoi": [
{
"hanh_khach_id": 542,
"uuid_cho_ngoi": "dc435226-aeb7-11f0-8b81-86d224031662",
"index_chang_bay": 1,
"index_hanh_trinh": 1
}
],
"xac_nhan": "y",
"xuat_ve": "",
"tong_tien": 54000
}

Fields:

FieldTypeRequiredDescription
ve_idnumber✅ YesBooking ID
cho_ngoiarray✅ YesList of seats to book
cho_ngoi[].hanh_khach_idnumber✅ YesPassenger ID
cho_ngoi[].uuid_cho_ngoistring✅ YesSeat UUID (from step 1)
cho_ngoi[].index_chang_baynumber✅ YesSegment index (1-based)
cho_ngoi[].index_hanh_trinhnumber✅ YesJourney index (same as index_chang_bay for single segment)
xac_nhanstring✅ YesConfirmation: "y" = confirm, "" = preview
xuat_vestring✅ YesIssue ticket: usually ""
tong_tiennumber✅ YesTotal seat price (VND)

⚠️ Important Index Rules:

  • index_chang_bay = 1-based (1, 2, 3...)
  • index_hanh_trinh = Same as index_chang_bay for VietJet single segment
  • For multi-segment, check frontend logic

Success Response

{
"message": "Cập nhật chỗ ngồi thành công",
"data": null,
"status": "success",
"code": 200
}

💡 Complete Example: cURL

Step 1: Get Seat Map

curl -X POST "{{base_url}}/api/v1/services/booking/v1/danh-sach-phu-tro-dat-ve" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {access_token}" \
-d '{
"chang_bay": [{
"index": 0,
"index_chuyen_bay": 0,
"ve_id": 94
}]
}'

Step 2: Confirm Seat

curl -X POST "{{base_url}}/api/v1/services/booking/v1/cap-nhat-cho-ngoi" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {access_token}" \
-d '{
"ve_id": 94,
"cho_ngoi": [{
"hanh_khach_id": 542,
"uuid_cho_ngoi": "dc435226-aeb7-11f0-8b81-86d224031662",
"index_chang_bay": 1,
"index_hanh_trinh": 1
}],
"xac_nhan": "y",
"xuat_ve": "",
"tong_tien": 54000
}'

🎯 Implementation Steps

1. Get Booking Details

const details = await getBookingDetails(ve_id);
const hanh_khach_id = details.data.hanh_khach[0].id;

2. Get Seat Map

const response = await fetch(
"{{base_url}}/api/v1/services/booking/v1/danh-sach-phu-tro-dat-ve",
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
chang_bay: [
{
index: 0,
index_chuyen_bay: 0,
ve_id: ve_id,
},
],
}),
},
);

const seatMap = response.data["0"].cho_ngoi;

3. Flatten & Filter Available Seats

const allSeats = [];

// Flatten nested seat map
Object.keys(seatMap).forEach((row) => {
Object.keys(seatMap[row]).forEach((col) => {
const seat = seatMap[row][col];
if (seat) {
allSeats.push(seat);
}
});
});

// Filter available seats
const availableSeats = allSeats.filter(
(s) =>
s.hop_le === true && // Valid seat
s.trang_thai === false, // Not occupied
);

console.log(`Found ${availableSeats.length} available seats`);

4. Select Seat

// Select specific seat
const selectedSeat = availableSeats.find((s) => s.ma_ghe === "12C");

// Or select random available seat
const randomSeat =
availableSeats[Math.floor(Math.random() * availableSeats.length)];

5. Confirm Seat

const confirmResponse = await fetch(
"{{base_url}}/api/v1/services/booking/v1/cap-nhat-cho-ngoi",
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
ve_id: ve_id,
cho_ngoi: [
{
hanh_khach_id: hanh_khach_id,
uuid_cho_ngoi: selectedSeat.uuid,
index_chang_bay: 1,
index_hanh_trinh: 1,
},
],
xac_nhan: "y",
xuat_ve: "",
tong_tien: selectedSeat.gia_net,
}),
},
);

⚠️ Important Notes

1. Fare Class Matters

Fare ClassSeat PricePayment
ECONOMY~54,000 VNDPaid
DELUXE0 VNDFree
SKYBOSS0 VNDFree

For payment testing: Always use ECONOMY fare class.

2. UUID Expiration

Seat UUIDs expire quickly (~5-10 minutes). Get fresh seats before confirm:

// ✅ Good: Get → immediately confirm
const seats = await getSeats(ve_id);
const selected = seats.find((s) => s.ma_ghe === "12C");
await confirmSeat(selected);

// ❌ Bad: Get → wait → confirm
const seats = await getSeats(ve_id);
await sleep(600000); // 10 minutes
await confirmSeat(seats[0]); // UUID expired!

3. Index Mapping

// For VietJet single segment
{
index_chang_bay: 1, // 1-based
index_hanh_trinh: 1, // Same as index_chang_bay
index_chuyen_bay: 0 // From seat data (0-based)
}

4. Seat Availability

function isAvailable(seat) {
return seat.hop_le === true && seat.trang_thai === false;
}

// ✅ Available
{ hop_le: true, trang_thai: false }

// ❌ Not available
{ hop_le: false, trang_thai: true } // Occupied
{ hop_le: false, trang_thai: false } // Blocked

5. Exit Row Seats

More expensive:

const exitSeats = availableSeats.filter((s) => s.loi_thoat === true);
// Usually 64,800 VND vs standard 54,000 VND

🐛 Common Issues

UUID Expired

Error: "UUID không tồn tại"

Solution: Get fresh seat map before confirm


Total Amount Mismatch

Error: "TotalAmount is invalid"

Cause: tong_tien doesn't match seat price

Solution:

const tong_tien = selectedSeat.gia_net; // Use current price

Index Error

Error: "key_chang_bay_0"

Cause: Wrong index_chang_bay or index_hanh_trinh

Solution: For single segment VietJet:

{
index_chang_bay: 1, // 1-based
index_hanh_trinh: 1 // Same value
}

📊 Seat Pricing

Seat prices vary by:

  • Seat position (exit row usually more expensive)
  • Fare class
  • Route

Example prices:

  • Standard seat: ~20,000 - 54,000 VND
  • Exit row: ~64,800 VND

🗺️ Seat Map Structure

Row 1:  A[64k] B[64k] C[64k]    D[64k] E[64k] F[64k]  (Exit row)
Row 2: A[54k] B[54k] C[54k] D[54k] E[54k] F[54k]
Row 12: A[54k] B[54k] C[54k] D[54k] E[54k] F[54k]
...

Legend:

  • A-F: Column
  • 1-30: Row number
  • [price]: Seat price
  • Exit rows (1, 12, etc.) more expensive

🔗 Next Steps

After buying seat:

  1. Payment03-PAYMENT.md (if booking is HOLD)
  2. View Updated Details02-GET-DETAILS.md

📝 Notes

  • Same API as Update Services (danh-sach-phu-tro-dat-ve) for getting seats
  • Check hop_le: true and trang_thai: false for availability
  • Seat prices vary by fare class and position
  • UUIDs expire in ~5-10 minutes
  • index_chang_bay is 1-based
  • index_hanh_trinh same as index_chang_bay for single segment
  • Exit row seats are more expensive
  • tong_tien must match gia_net of selected seat
  • Get fresh seat map immediately before confirm