Add Journey - Thêm Hành Trình
API Endpoint: POST /api/v1/services/booking/v1/them-hanh-trinh
📋 Overview
Add a new journey (flight segment) to existing VJ booking.
Use Case:
- Convert one-way → round-trip
- Add connecting flight
- Add return flight after initial booking
Important:
- VJ supports adding journeys to both HOLD and ISSUED bookings
- Must re-search for fresh flight UUID (bookingKey TTL ~30s)
- Backend re-validates fee before confirm
🔄 Flow Steps
1. Get Booking Details
- API:
GET /thong-tin-ve/{ve_id} - Check current journeys
2. Search New Flight
- API:
POST /tim-chuyen(withve_id) - ⚠️ CRITICAL: Include
ve_idin search request - Select flight to add
3. Preview Add Journey Fee
- API:
POST /them-hanh-trinh(preview mode) - Calculate fee before commit
4. Re-search for Fresh UUID
- Why: bookingKey expires in ~30s
- Search same flight again
- Get fresh UUID
5. Confirm Add Journey
- API:
POST /them-hanh-trinh(confirm mode) - Use fresh UUID from step 4
- Use fee from preview
🔌 API Specification
Endpoint
POST {{base_url}}/api/v1/services/booking/v1/them-hanh-trinh
Headers
{
"Content-Type": "application/json",
"Authorization": "Bearer {access_token}"
}
Preview Mode
Calculate fee before adding journey.
Request Body
{
"ve_id": 661,
"loai_phi_dai_ly": "Nhập",
"uuid_chuyen_bay": "d4259b72-b93d-11f0-8157-86d224031664",
"index_chang_bay_moi": 0,
"tong_tien": 0,
"xac_nhan": ""
}
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
ve_id | number | ✅ Yes | Booking ID |
loai_phi_dai_ly | string | ✅ Yes | Fee type: "Nhập" or "Cơ bản" |
uuid_chuyen_bay | string | ✅ Yes | Flight UUID from search |
index_chang_bay_moi | number | ✅ Yes | Fare index (0-based) |
tong_tien | number | ✅ Yes | Total amount (0 for preview) |
xac_nhan | string | ✅ Yes | Confirm flag ("" = preview) |
Response
{
"message": "Tổng tiền thanh toán là 1,086,300",
"data": {
"pnr": "ZCHMUA",
"tong_tien": 1086300,
"phi_thay_doi": 0,
"f2_info": {
"id": "432ff8ad-31a7-45ed-90cc-b92e7e542271",
"code": "ST2010",
"name": "F2 - 9S - TƯỜNG VÕ",
"balance": 694264297,
"sufficient": true
}
},
"status": "success",
"code": 200
}
Response Fields:
| Field | Type | Description |
|---|---|---|
tong_tien | number | Total fee for new journey |
phi_thay_doi | number | Modification fee (if any) |
f2_info.balance | number | Current F2 wallet balance |
f2_info.sufficient | boolean | Balance sufficient for payment |
Confirm Mode
Execute add journey with validated fee.
Request Body
{
"ve_id": 661,
"loai_phi_dai_ly": "Nhập",
"uuid_chuyen_bay": "d530daa4-b93d-11f0-8157-86d224031664",
"index_chang_bay_moi": 0,
"tong_tien": 3978500,
"xac_nhan": "y"
}
Key Differences from Preview:
xac_nhan: "y"(confirm mode)tong_tien: 3978500(use fee from preview)uuid_chuyen_bay(MUST be fresh UUID from re-search)
Response
{
"message": "Thêm hành trình thành công",
"data": {
"pnr": "ZCHMUA",
"ve_id": 661,
"hang_bay": "VJ",
"journey_added": true,
"tong_tien": 3978500,
"vj_response": null
},
"status": "success",
"code": 200
}
⚠️ Critical: Fresh UUID Required
Why Re-search?
VJ bookingKey expires in ~30 seconds:
1. Initial search → UUID: abc123
2. Preview fee (5s later) → Still valid ✅
3. User reviews (25s later) → ...
4. Confirm with abc123 → ❌ EXPIRED!
Solution: Re-search Before Confirm
// Step 3: Preview with initial UUID
const preview = await previewAddJourney({
uuid_chuyen_bay: initialUUID,
xac_nhan: ""
});
const fee = preview.data.tong_tien;
// Step 4: Re-search to get FRESH UUID
const freshSearch = await searchFlights(...);
const freshUUID = freshSearch.flights[0].uuid;
// Step 5: Confirm with FRESH UUID + PREVIEW fee
const confirm = await confirmAddJourney({
uuid_chuyen_bay: freshUUID, // ← Fresh UUID
tong_tien: fee, // ← Fee from preview
xac_nhan: "y"
});
💰 Payment Logic
HOLD Booking
- Fee validated
- Wallet NOT deducted (Payment Later)
- Will charge when issuing ticket
ISSUED Booking
- Fee validated
- Wallet deducted immediately
- Journey added to booking
🐛 Error Handling
UUID Expired
Response:
{
"status": "error",
"message": "bookingKey expired or invalid"
}
Solution: Re-search to get fresh UUID
Insufficient Balance (ISSUED)
Response:
{
"status": "error",
"message": "Số dư không đủ"
}
📝 Example Flow
// 1. Get booking
const booking = await getBookingDetails(ve_id);
// 2. Search for journey to add (INCLUDE ve_id!)
const searchResult = await searchFlights({
...searchParams,
ve_id: booking.ve.id // ← CRITICAL
});
// 3. Preview fee
const preview = await fetch('/them-hanh-trinh', {
method: 'POST',
body: JSON.stringify({
ve_id: booking.ve.id,
uuid_chuyen_bay: selectedFlight.uuid,
index_chang_bay_moi: fareIndex,
loai_phi_dai_ly: "Nhập",
tong_tien: 0,
xac_nhan: ""
})
});
const fee = preview.data.tong_tien;
// 4. Re-search for fresh UUID (after user confirms)
const freshSearch = await searchFlights({...});
const freshUUID = freshSearch.flights[0].uuid;
// 5. Confirm with fresh UUID
const result = await fetch('/them-hanh-trinh', {
method: 'POST',
body: JSON.stringify({
ve_id: booking.ve.id,
uuid_chuyen_bay: freshUUID, // Fresh!
index_chang_bay_moi: fareIndex,
loai_phi_dai_ly: "Nhập",
tong_tien: fee, // From preview
xac_nhan: "y"
})
});
🔗 Related APIs
Prerequisites:
02-SEARCH-FLIGHTS.md- Search for journey to add (with ve_id)02-GET-DETAILS.md- Get current booking info
After:
03-PAYMENT.md- Pay for HOLD booking02-GET-DETAILS.md- Verify journey added