Chuyển tới nội dung chính

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_request nhận fees thay cho amount; amount giờ 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_request nhận phần công ty trả của từng khoản (fees: [{ id, corporate_amount }]) thay cho một corporate_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_PAYMENT và thao tác mới begin_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_fee nhận user_request_fee_id cùng user_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_id khỏi UserRequest. Mỗi khoản phí có paid và extra_purchase_ids; yêu cầu có paid_personal_amount.
  • claimed của admin_user_requests giờ 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ĩaAi thao tác tiếp
PENDINGĐã tạo, chưa có admin tiếp nhậnAdmin tiếp nhận; người yêu cầu có thể hủy
IN_CHARGEAdmin đ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
ISSUEDAdmin đã báo giá các khoản phí; chờ người yêu cầuNgườ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ánNgười yêu cầu thanh toán — xem Người yêu cầu thanh toán thế nào
CONFIRMEDNgười yêu cầu đã chấp nhận, và phần của họ (nếu có) đã thanh toán; chờ phê duyệtNgườ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
RESOLVEDHoàn tấtAdmin áp dụng từng khoản phí bằng add_passenger_fee
REJECTEDBị admin hoặc người phê duyệt từ chốiKhông ai (trạng thái cuối)
CANCELEDNgườ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)
DECLINEDNgườ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ềnNằm ở đâuCách tính
amountYêu cầuTổng price của mọi khoản phí, có khi admin phát hành báo giá
corporate_amountTừ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_amountYêu cầuTổng corporate_amount của các khoản phí
personal_amountYêu cầuamount - 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ầnAi trảKhi nàoQua đâu
personal_amountNgười yêu cầu, từ ví của họSau khi xác nhận, trước khi phê duyệtNgười yêu cầu thanh toán thế nào
corporate_amount của từng khoảnCông tyKhi 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ọiServiceDùng để
1begin_personal_paymentFlightTrả về extra purchase cần thanh toán: amount là personal_amount tính bằng VND, status là PAYING.
2book_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.
3Phần còn lại của Payment FlowStorebegin_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ườngKiểuÝ nghĩaKhi nào có giá trị
codeStringMã 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ã.
statusUserRequestStatusThêm AWAITING_PERSONAL_PAYMENT.Xem Luồng trạng thái.
amountDecimalTổ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_amountDecimalPhầ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_amountDecimalamount - 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ớiDecimalPhầ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ườngKiểuÝ nghĩa
idStringĐịnh danh khoản phí. Gửi lên confirm_user_request và add_passenger_fee.
fee_typeFeeTypeBAGGAGE, SEAT hoặc OTHER.
legFlightLegDEPARTURE hoặc RETURN.
baggageBaggageHành lý, khi fee_type là BAGGAGE.
seatSeatGhế, khi fee_type là SEAT.
other_feeOtherFeeKhoản phí, khi fee_type là OTHER.
currencyStringĐơn vị tiền của yêu cầu; price và corporate_amount tính theo đơn vị này.
priceDecimalGiá đầy đủ của khoản phí.
corporate_amountDecimalPhầ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.
paidBooleantrue 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.

HeaderBắt buộcMô tả
AuthorizationCó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ácAi được gọi
user_requests, confirm_user_request, begin_personal_payment, decline_user_request, cancel_user_request, booking_request_progressMọ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_feeNgườ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ácLoạiAiDùng để
user_requestsQueryNgười yêu cầuLiệt kê các yêu cầu của chính người gọi
confirm_user_requestMutationNgười yêu cầuChấp nhận báo giá và chọn phần công ty trả cho từng khoản
begin_personal_payment mớiMutationNgười yêu cầuBắt đầu thanh toán personal_amount
decline_user_requestMutationNgười yêu cầuTừ chối báo giá
cancel_user_requestMutationNgười yêu cầuRút lại yêu cầu trước khi có báo giá
admin_user_requestsQueryAdminLiệt kê yêu cầu, tìm theo code, lọc theo claimed
process_user_requestMutationAdminTiếp nhận, báo giá, hoàn tất hoặc từ chối yêu cầu
add_passenger_feeMutationAdminÁ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_progressQueryMọi người dùng đã đăng nhậpXem 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 đổiCái gì bị hỏngCần làm gì
process_user_request nhận fees; bỏ amount khỏi inputGửi amount làm lỗi requestGửi các khoản phí; amount được tính từ chúng
confirm_user_request nhận fees; bỏ corporate_amount khỏi inputGửi corporate_amount làm lỗi requestGửi corporate_amount của từng khoản
Trạng thái mới AWAITING_PERSONAL_PAYMENTTrạng thái lạ trong nhãn và bộ lọcHiển thị là "chờ bạn thanh toán", kèm nút thanh toán
Bỏ extra_purchase_id khỏi UserRequestChọn trường này làm lỗi cả requestDù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ánDanh sách admin giữ yêu cầu cho đến khi mọi khoản được áp dụngKhông cần làm gì, nếu danh sách luôn gửi claimed: false
Bỏ personal_pay khỏi UserRequest và ProcessUserRequestInputChọn hoặc gửi trường này làm lỗi cả requestBỏ đi; hiển thị corporate_amount và personal_amount thay thế
Bỏ AdminUserRequestsSortInputBiến query khai báo bằng kiểu đó sẽ lỗiKhai 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"
Yêu cầu báo giá trước khi có khoản phí

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í.

Yêu cầu 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_wallet cho 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/v1 củ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òn add_passenger_fee và 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.