Checkout Process
Relevant source files
The following files were used as context for generating this wiki page:- app/(shop)/cart/checkout/page.tsx
- features/checkout/hooks/use-checkout.ts
- features/checkout/services/checkout.service.ts
- features/checkout/components/checkout-content.tsx
- features/checkout/types/checkout.types.ts
- features/checkout/services/checkout.service.spec.ts
- e2e/checkout-cod.spec.ts
- e2e/checkout-sepay.spec.ts
- app/(shop)/cart/success/page.tsx
The Checkout Process is a critical feature module that enables users to finalize their purchases. It orchestrates the user's journey from the cart to a successful order placement. The process involves selecting a shipping address, choosing a payment method, reviewing the order summary, and submitting the order. The system supports two primary payment flows: Cash on Delivery (COD) and an online payment method called SePay, which utilizes QR codes.
The architecture follows a feature-based organization, encapsulating all related logic, components, and services within the features/checkout directory. This modular design separates concerns, with React components for the UI, a custom hook for state management and client-side logic, and a dedicated service layer for handling business rules and API interactions.
Architecture Overview
The checkout feature is structured into three main layers: UI Components, a Logic Hook, and a Service Layer. This separation ensures that UI is decoupled from business logic, and business logic is decoupled from direct API communication.
This diagram shows the high-level dependency flow within the checkout feature.
Component Breakdown
The user interface for the checkout process is composed of several specialized React components. The CheckoutContent component acts as the main orchestrator.
| Component | File | Description |
|---|---|---|
CheckoutPage | app/(shop)/cart/checkout/page.tsx | The main Next.js page route, wrapped in a ProtectedRoute to ensure the user is authenticated. It renders the CheckoutContent. |
CheckoutContent | features/checkout/components/checkout-content.tsx | Orchestrates the entire checkout UI. It fetches necessary data (cart, addresses) and handles loading, empty, and validation states before rendering the main layout. |
AddressSelector | features/checkout/components/address-selector.tsx | Renders a list of the user's saved shipping addresses and allows them to select one for the order. |
PaymentMethodSelector | features/checkout/components/payment-method-selector.tsx | Allows the user to choose between available payment methods, specifically COD and SEPAY. |
OrderSummary | features/checkout/components/order-summary.tsx | Displays the items currently in the user's cart. |
CheckoutActions | features/checkout/components/checkout-actions.tsx | A sticky sidebar component showing the order total and the final "Đặt hàng" (Place Order) button. |
PaymentWaitingDialog | features/payments/components/payment-waiting-dialog.tsx | A dialog that appears during the SePay flow, displaying a QR code and waiting for payment confirmation. |
Sources: app/(shop)/cart/checkout/page.tsx:13, features/checkout/components/checkout-content.tsx:23, features/checkout/components/checkout-actions.tsx:18
State Management and Logic (useCheckout hook)
The core client-side logic and state management for the checkout process are centralized in the useCheckout custom hook. This hook is responsible for managing user selections, handling the submission process, and orchestrating the different payment flows.
Sources: features/checkout/hooks/use-checkout.ts
State
The hook manages the following state, defined by the CheckoutState interface:
| Field | Type | Description |
|---|---|---|
selectedAddressId | string | The ID of the shipping address selected by the user. |
selectedPayment | PaymentMethod | The payment method selected by the user (COD or SEPAY). Defaults to COD. |
isSubmitting | boolean | A flag to indicate if the checkout process is currently in progress, used to disable buttons and show loading states. |
Sources: features/checkout/types/checkout.types.ts:9-14, features/checkout/hooks/use-checkout.ts:41-45
Key Functions
The useCheckout hook exposes several handler functions to the CheckoutContent component:
handleCheckout(): The primary function triggered when the user clicks "Place Order". It performs validation, then calls the appropriateCheckoutServicemethod based on theselectedPayment.handlePaymentSuccess(): A callback for the SePay flow. It is triggered when the payment is confirmed, clears the cart, and redirects the user to the success page.handlePaymentTimeout(): A callback for the SePay flow if the payment is not completed within the time limit.handlePaymentError(): A callback to handle any errors during the SePay payment process.
Sources: features/checkout/hooks/use-checkout.ts:68-146
Service Layer (CheckoutService)
The CheckoutService class encapsulates all business logic related to the checkout process, acting as an intermediary between the useCheckout hook and the backend API services (ordersApi, paymentsApi).
This diagram illustrates the structure of the CheckoutService.
Validation
Before an order can be placed, the validateCheckoutRequest method is called to ensure the data is valid.
| Validation Rule | Error Message |
|---|---|
selectedAddressId is missing. | "Vui lòng chọn địa chỉ giao hàng" (Please select a shipping address) |
| Cart is empty or has no items. | "Giỏ hàng trống" (Cart is empty) |
| Cart items have invalid product data (e.g., missing price). | "Giỏ hàng chứa sản phẩm không hợp lệ. Vui lòng làm mới trang." (Cart contains invalid products. Please refresh the page.) |
| Cart total is zero or negative. | "Tổng giá trị đơn hàng không hợp lệ" (Total order value is invalid) |
Sources: features/checkout/services/checkout.service.ts:60-82, features/checkout/services/checkout.service.spec.ts:75-144
Checkout Processing
processCodCheckout(addressId, cart): For COD orders, this method formats the cart items and callsordersApi.create()to create a new order in the backend. It does not interact with the payment API. It returns theorderId.
Sources: features/checkout/services/checkout.service.ts:87-98processSepayCheckout(addressId, cart): For SePay orders, this method follows a two-step process:- It first calls
ordersApi.create()to create the order, just like the COD flow. - It then uses the returned
orderIdto callpaymentsApi.process(), which initiates the payment and returns apaymentIdand aqrCode.
Sources: features/checkout/services/checkout.service.ts:103-123
- It first calls
Data Flow and API Interaction
The checkout process involves a sequence of interactions between the user, the frontend application, and the backend API. The flow differs significantly based on the chosen payment method.
Cash on Delivery (COD) Flow
The COD flow is straightforward: the user places the order, the system creates the order, and the user is redirected to a success page.
This sequence diagram shows the COD checkout flow.
SePay (QR Code) Flow
The SePay flow is more interactive. After placing the order, the user is shown a dialog with a QR code and must complete the payment externally. The frontend polls for payment status updates.
This sequence diagram shows the SePay checkout flow.
Post-Checkout
After a successful checkout for either payment method, the user is redirected to the /cart/success page. This page displays a confirmation message, order details, and clears the cart from the local state using useCartStore.clearCart().
Sources: app/(shop)/cart/success/page.tsx:56, features/checkout/hooks/use-checkout.ts:98,131