Luồng User Request
User request là yêu cầu thay đổi hoặc hủy chuyến bay đã đặt của một hành khách. Admin báo giá bằng một danh sách khoản phí — ghế, hành lý hoặc phí khác. Sau đó người yêu cầu xác nhận báo giá, chọn phần công ty trả cho từng khoản, hoặc từ chối. Nếu người yêu cầu giữ lại một phần để tự trả, họ thanh toán phần đó trước; người phê duyệt của công ty xem xét phần còn lại; cuối cùng admin áp dụng từng khoản phí vào booking.
Trang này trình bày luồng trạng thái và object UserRequest. Mỗi thao tác có trang riêng trong mục
APIs, và các mã lỗi nằm trong mục
Mã lỗi.
Những gì thay đổi
- Báo giá là danh sách khoản phí.
process_user_requestnhậnfeesthay choamount;amountgiờ là tổng các khoản. Các khoản phí không đổi được sau khi đã phát hành. - Chia tiền theo từng khoản.
confirm_user_requestnhận phần công ty trả của từng khoản (fees: [{ id, corporate_amount }]) thay cho mộtcorporate_amount. - Người yêu cầu thanh toán phần của mình ngay trong ứng dụng. Có trạng thái mới
AWAITING_PERSONAL_PAYMENTvà thao tác mớibegin_personal_payment, thanh toán từ ví của người yêu cầu qua checkout của store — xem Người yêu cầu thanh toán thế nào. - Áp dụng từng khoản phí một.
add_passenger_feenhậnuser_request_fee_idcùnguser_request_id, và dùng dữ liệu và giá của chính khoản phí — không còn phải gửi giá bằng 0. - Trạng thái thanh toán đã chuyển chỗ. Bỏ
extra_purchase_idkhỏiUserRequest. Mỗi khoản phí cópaidvàextra_purchase_ids; yêu cầu cópaid_personal_amount. claimedcủaadmin_user_requestsgiờ nghĩa là "mọi khoản phí đã thanh toán".
Luồng trạng thái
Người yêu cầu thao tác tối đa ba lần: khi tạo yêu cầu, khi xác nhận hoặc từ chối báo giá, và khi thanh toán phần của mình. Giá trị trạng thái được trả về dạng viết hoa như bên dưới.
Admin vẫn có thể hoàn tất hoặc từ chối mà không cần báo giá. Chỉ khi có báo giá thì người yêu cầu mới phải thao tác.
| Trạng thái | Ý nghĩa | Ai thao tác tiếp |
|---|---|---|
PENDING | Đã tạo, chưa có admin tiếp nhận | Admin tiếp nhận; người yêu cầu có thể hủy |
IN_CHARGE | Admin đang xử lý | Admin báo giá, hoàn tất hoặc từ chối; người yêu cầu có thể hủy |
ISSUED | Admin đã báo giá các khoản phí; chờ người yêu cầu | Người yêu cầu xác nhận hoặc từ chối |
AWAITING_PERSONAL_PAYMENT mới | Đã xác nhận, người yêu cầu tự trả một phần; chờ họ thanh toán | Người yêu cầu thanh toán — xem Người yêu cầu thanh toán thế nào |
CONFIRMED | Người yêu cầu đã chấp nhận, và phần của họ (nếu có) đã thanh toán; chờ phê duyệt | Người phê duyệt của công ty, lần lượt từng cấp; tất cả các cấp phải duyệt |
RESOLVED | Hoàn tất | Admin áp dụng từng khoản phí bằng add_passenger_fee |
REJECTED | Bị admin hoặc người phê duyệt từ chối | Không ai (trạng thái cuối) |
CANCELED | Người yêu cầu rút lại trước khi có báo giá | Không ai (trạng thái cuối) |
DECLINED | Người yêu cầu từ chối báo giá | Không ai (trạng thái cuối) |
Người yêu cầu thanh toán trước khi phê duyệt. Người phê duyệt chỉ thấy những yêu cầu mà phần cá
nhân đã được thanh toán. Yêu cầu đang ở AWAITING_PERSONAL_PAYMENT không hủy hay từ chối được, và
admin cũng không xử lý được.
CONFIRMED có thể chuyển thẳng sang RESOLVED. Khi công ty không trả gì, hoặc chính sách của
công ty không cần phê duyệt, yêu cầu được hoàn tất ngay khi tới CONFIRMED. Các mutation trả về
trạng thái do chính nó đặt, nên hãy fetch lại yêu cầu trước khi hiển thị trạng thái.
Khoản phí và cách chia tiền
Mỗi khoản phí là một thứ hành khách nhận được — ghế, hành lý hoặc phí khác — trên một chặng, tính theo
currency của yêu cầu. Người yêu cầu quyết định, từng khoản một, công ty trả bao nhiêu.
| Số tiền | Nằm ở đâu | Cách tính |
|---|---|---|
amount | Yêu cầu | Tổng price của mọi khoản phí, có khi admin phát hành báo giá |
corporate_amount | Từng khoản phí | Người yêu cầu chọn khi xác nhận, từ 0 đến price của khoản đó |
corporate_amount | Yêu cầu | Tổng corporate_amount của các khoản phí |
personal_amount | Yêu cầu | amount - corporate_amount: phần người yêu cầu trả |
Hai phần được thanh toán vào hai thời điểm khác nhau:
| Phần | Ai trả | Khi nào | Qua đâu |
|---|---|---|---|
personal_amount | Người yêu cầu, từ ví của họ | Sau khi xác nhận, trước khi phê duyệt | Người yêu cầu thanh toán thế nào |
corporate_amount của từng khoản | Công ty | Khi admin áp dụng khoản phí đó | add_passenger_fee, mỗi khoản một lần gọi |
Người yêu cầu thanh toán thế nào
Người yêu cầu trả personal_amount từ ví của mình, qua checkout của store. FE bắt đầu thanh toán
bằng hai lần gọi — begin_personal_payment ở flight service, rồi book_extra_purchase ở store
service — và hoàn tất bằng Payment Flow của store.
| # | Lần gọi | Service | Dùng để |
|---|---|---|---|
| 1 | begin_personal_payment | Flight | Trả về extra purchase cần thanh toán: amount là personal_amount tính bằng VND, status là PAYING. |
| 2 | book_extra_purchase(input: { flight: { id: "<id của extra purchase>" } }) | Store | Đưa extra purchase đó vào giỏ của người yêu cầu thành một shopping item. Chỉ người yêu cầu mới book được. |
| 3 | Phần còn lại của Payment Flow | Store | begin_payment_checkout_session trên giỏ hàng, để tắt thanh toán công ty, rồi pay_with_wallet. |
Bước 1–2 chính là bước 3 của Payment Flow — "create shopping_item" — nên hãy xóa giỏ hàng và hủy checkout session còn mở trước, như trang đó mô tả.
FE không cần gọi gì sau khi thanh toán. Store báo cho flight service, và yêu cầu chuyển sang
CONFIRMED khi thành công; khi thất bại, yêu cầu vẫn ở AWAITING_PERSONAL_PAYMENT, và FE bắt đầu
lại từ bước 1.
Mỗi lần thử là một extra purchase riêng, mang user_request_id của yêu cầu và personal: true;
mỗi lúc chỉ có tối đa một khoản PAYING. Khi báo đã thanh toán, store cho biết thanh toán bằng cách
nào, và flight service lưu điều đó trên extra purchase ở metadata.payment_method — wallet,
corporate_wallet, bank_transfer, momo, p2c2p, vnpay hoặc stripe.
Object UserRequest
Mọi số tiền đều tính theo currency của yêu cầu. Cả user_requests và admin_user_requests đều trả
về mọi trường bên dưới.
| Trường | Kiểu | Ý nghĩa | Khi nào có giá trị |
|---|---|---|---|
code | String | Mã tham chiếu dễ đọc, FUR + 8 chữ số, không trùng. Dùng trên giao diện và khi hỗ trợ khách. | Khi tạo. null với yêu cầu tạo trước khi có mã. |
status | UserRequestStatus | Thêm AWAITING_PERSONAL_PAYMENT. | Xem Luồng trạng thái. |
amount | Decimal | Tổng price của các khoản phí. | Khi admin phát hành báo giá. |
fees mới | [UserRequestFee] | Các khoản phí đã báo giá. | Khi admin phát hành báo giá. Rỗng trước đó. |
corporate_amount | Decimal | Phần công ty trả: tổng corporate_amount của các khoản phí. | Khi xác nhận. null trước đó. |
personal_amount | Decimal | amount - corporate_amount: phần người yêu cầu trả. Được tính ra, không gửi lên. | Bằng amount cho đến khi xác nhận. null khi không có amount. |
paid_personal_amount mới | Decimal | Phần personal_amount người yêu cầu đã thanh toán. | 0 khi phát hành; bằng personal_amount khi đã thanh toán. |
extra_purchase_id đã bỏ | String | Được thay bằng paid và extra_purchase_ids của từng khoản phí. | Chọn trường này giờ sẽ bị lỗi query. |
Object UserRequestFee
| Trường | Kiểu | Ý nghĩa |
|---|---|---|
id | String | Định danh khoản phí. Gửi lên confirm_user_request và add_passenger_fee. |
fee_type | FeeType | BAGGAGE, SEAT hoặc OTHER. |
leg | FlightLeg | DEPARTURE hoặc RETURN. |
baggage | Baggage | Hành lý, khi fee_type là BAGGAGE. |
seat | Seat | Ghế, khi fee_type là SEAT. |
other_fee | OtherFee | Khoản phí, khi fee_type là OTHER. |
currency | String | Đơn vị tiền của yêu cầu; price và corporate_amount tính theo đơn vị này. |
price | Decimal | Giá đầy đủ của khoản phí. |
corporate_amount | Decimal | Phần công ty trả, chọn khi xác nhận. Người yêu cầu trả price - corporate_amount. null trước khi xác nhận. |
paid | Boolean | true khi add_passenger_fee đã áp dụng khoản phí và thu phần công ty trả. |
extra_purchase_ids | [String] | Khoản thanh toán của công ty cho khoản phí này, từ add_passenger_fee. |
Endpoint và xác thực
Mọi thao tác trong mục này là GraphQL query hoặc mutation trên endpoint /graphql/v1 của flight
service. Tên trường và tham số viết theo snake_case, còn giá trị enum viết hoa.
| Header | Bắt buộc | Mô tả |
|---|---|---|
Authorization | Có | Session token của người dùng. Flight service chuyển nguyên token này sang store service để lấy profile của người gọi. |
| Thao tác | Ai được gọi |
|---|---|
user_requests, confirm_user_request, begin_personal_payment, decline_user_request, cancel_user_request, booking_request_progress | Mọi người dùng đã đăng nhập; bốn mutation chỉ dùng được với yêu cầu do chính người gọi tạo |
admin_user_requests, process_user_request, add_passenger_fee | Người dùng có role ADMIN |
Thiếu token hoặc token không hợp lệ sẽ trả về UNAUTHENTICATED, sai role trả về UNAUTHORIZED —
xem Lỗi middleware.
APIs
| Thao tác | Loại | Ai | Dùng để |
|---|---|---|---|
user_requests | Query | Người yêu cầu | Liệt kê các yêu cầu của chính người gọi |
confirm_user_request | Mutation | Người yêu cầu | Chấp nhận báo giá và chọn phần công ty trả cho từng khoản |
begin_personal_payment mới | Mutation | Người yêu cầu | Bắt đầu thanh toán personal_amount |
decline_user_request | Mutation | Người yêu cầu | Từ chối báo giá |
cancel_user_request | Mutation | Người yêu cầu | Rút lại yêu cầu trước khi có báo giá |
admin_user_requests | Query | Admin | Liệt kê yêu cầu, tìm theo code, lọc theo claimed |
process_user_request | Mutation | Admin | Tiếp nhận, báo giá, hoàn tất hoặc từ chối yêu cầu |
add_passenger_fee | Mutation | Admin | Áp dụng một khoản phí của yêu cầu đã hoàn tất cho hành khách |
booking_request_progress | Query | Mọi người dùng đã đăng nhập | Xem những ai còn phải phê duyệt một yêu cầu CONFIRMED |
Thay đổi breaking và triển khai
Thay đổi này mới chỉ có trên staging; prod vẫn chạy luồng cũ. Hãy phát hành phần FE cùng lúc
với bản backend, không phát hành trước. begin_personal_payment còn cần bản phát hành tương ứng của
store service.
| Thay đổi | Cái gì bị hỏng | Cần làm gì |
|---|---|---|
process_user_request nhận fees; bỏ amount khỏi input | Gửi amount làm lỗi request | Gửi các khoản phí; amount được tính từ chúng |
confirm_user_request nhận fees; bỏ corporate_amount khỏi input | Gửi corporate_amount làm lỗi request | Gửi corporate_amount của từng khoản |
Trạng thái mới AWAITING_PERSONAL_PAYMENT | Trạng thái lạ trong nhãn và bộ lọc | Hiển thị là "chờ bạn thanh toán", kèm nút thanh toán |
Bỏ extra_purchase_id khỏi UserRequest | Chọn trường này làm lỗi cả request | Dùng paid của từng khoản phí |
add_passenger_fee cần user_request_fee_id cùng user_request_id | Áp dụng yêu cầu mà thiếu nó sẽ trả về user_request_fee_id_required | Áp dụng từng khoản phí bằng một lần gọi riêng |
claimed nghĩa là mọi khoản phí đã thanh toán | Danh sách admin giữ yêu cầu cho đến khi mọi khoản được áp dụng | Không cần làm gì, nếu danh sách luôn gửi claimed: false |
Bỏ personal_pay khỏi UserRequest và ProcessUserRequestInput | Chọn hoặc gửi trường này làm lỗi cả request | Bỏ đi; hiển thị corporate_amount và personal_amount thay thế |
Bỏ AdminUserRequestsSortInput | Biến query khai báo bằng kiểu đó sẽ lỗi | Khai báo bằng UserRequestsSortInput |
ISSUED không còn nghĩa là "chờ phê duyệt" | Nhãn trạng thái hiển thị sai | Đổi nhãn thành "chờ bạn xác nhận" |
Các yêu cầu này có amount nhưng không có fees. Vẫn xác nhận được — không gửi fees, và công ty
trả toàn bộ amount — nhưng add_passenger_fee không áp dụng được, vì nó cần id của khoản phí.
ISSUED cũCác yêu cầu đang chờ phê duyệt từ trước bản phát hành được lưu với trạng thái ISSUED. Cho đến khi
backend chuyển chúng sang CONFIRMED, FE sẽ hiển thị chúng như đang chờ người yêu cầu, kèm nút xác
nhận lẽ ra không nên có.
Còn cần bổ sung
- Các phương thức thanh toán của store khác
pay_with_walletcho phần cá nhân. Flight service ghi nhận được mọi phương thức, nhưng trang này chỉ mô tả đường thanh toán bằng ví, và chưa phương thức nào được chạy thử trọn luồng qua store. - Base URL và đường dẫn api-gateway mà FE dùng để gọi
/graphql/v1của flight service. create_user_request— input và quy tắc kiểm tra chưa có trang riêng. Response cócode.- Các request Bruno tương ứng trong
api-tests/backend/flight-service/. Phần lớn các thao tác này thay đổi dữ liệu, cònadd_passenger_feevà thanh toán phần cá nhân có chuyển tiền, nên cần một tài khoản và booking staging riêng trước khi chạy an toàn.