Buy Seat - Mua Chỗ Ngồi
APIs:
POST /api/v1/services/booking/v1/danh-sach-phu-tro-dat-ve- Get seat mapPOST /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:
| Field | Type | Description |
|---|---|---|
uuid | string | Seat UUID (for booking) |
ma_ghe | string | Seat number (e.g., "12C") |
ma_cho_ngoi | string | Encoded seat code |
gia_net | number | Seat price (VND) |
hop_le | boolean | Valid: true = can book |
trang_thai | boolean | Occupied: false = available |
loi_thoat | boolean | Exit row |
index_chuyen_bay | number | Flight 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:
| Field | Type | Required | Description |
|---|---|---|---|
ve_id | number | ✅ Yes | Booking ID |
cho_ngoi | array | ✅ Yes | List of seats to book |
cho_ngoi[].hanh_khach_id | number | ✅ Yes | Passenger ID |
cho_ngoi[].uuid_cho_ngoi | string | ✅ Yes | Seat UUID (from step 1) |
cho_ngoi[].index_chang_bay | number | ✅ Yes | Segment index (1-based) |
cho_ngoi[].index_hanh_trinh | number | ✅ Yes | Journey index (same as index_chang_bay for single segment) |
xac_nhan | string | ✅ Yes | Confirmation: "y" = confirm, "" = preview |
xuat_ve | string | ✅ Yes | Issue ticket: usually "" |
tong_tien | number | ✅ Yes | Total seat price (VND) |
⚠️ Important Index Rules:
index_chang_bay= 1-based (1, 2, 3...)index_hanh_trinh= Same asindex_chang_bayfor 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 Class | Seat Price | Payment |
|---|---|---|
| ECONOMY | ~54,000 VND | Paid |
| DELUXE | 0 VND | Free |
| SKYBOSS | 0 VND | Free |
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: Column1-30: Row number[price]: Seat price- Exit rows (1, 12, etc.) more expensive
🔗 Next Steps
After buying seat:
- Payment →
03-PAYMENT.md(if booking is HOLD) - View Updated Details →
02-GET-DETAILS.md
📝 Notes
- Same API as Update Services (
danh-sach-phu-tro-dat-ve) for getting seats - Check
hop_le: trueandtrang_thai: falsefor availability - Seat prices vary by fare class and position
- UUIDs expire in ~5-10 minutes
index_chang_bayis 1-basedindex_hanh_trinhsame asindex_chang_bayfor single segment- Exit row seats are more expensive
tong_tienmust matchgia_netof selected seat- Get fresh seat map immediately before confirm