API sàn TMDT: Đặc tả & PRD đầy đủ nghiệp vụ

Mục lục
Mục lục
1. Lời mở đầu…
Hello các bạn, lại là mình đây. Mùa làm đồ án tốt nghiệp lại tới, và shop bán hàng, thương mại điện tử lại được các coder tương lai lựa chọn làm đề tài chính cho sản phẩm tốt nghiệp của mình/nhóm mình vì tính phổ biến, dễ hiểu nghiệp vụ và thường gặp trong các bài tập lớn. Tuy nhiên, nếu để làm ở mức nhỏ, đơn giản thì ai cũng làm được, mức độ cạnh tranh rất cao cũng như không đủ wow trong mắt các giáo viên cũng như nhà tuyển dụng. Vì vậy để giúp các bạn phân tích một phần nhỏ về các tính năng của shop bán hàng, thương mại điện tử, mình sẽ viết một số API cho các bạn tham khảo một cách đơn giản nhất có thể.
Lưu ý: Không cần thiết phải thực hiện tất cả các tính năng, API trong này, tuy nhiên nếu hoàn thiện hết, thì chắc chắn bạn sẽ có một sản phẩm tốt nghiệp đẹp. Và sẵn sàng đem cho các nhà tuyển dụng với offer mức lương khủng bố hehe.
2. Hướng dẫn sử dụng
- Tất cả các API (trừ đăng ký, đăng nhập, xác thực email, refresh token, reset password) đều yêu cầu xác thực Bearer Token.
- Các trường hợp lỗi trả về
{ "error": "..." }
với status code phù hợp. - Super admin có quyền duyệt ngành hàng, xác minh shop, tạo voucher toàn sàn.
- Shop owner chỉ tạo được voucher riêng shop, danh mục riêng, sản phẩm riêng.
- Các API mock cho phép đổi trạng thái đơn hàng, session thanh toán để test frontend.
3. Endpoints & Chức năng
3.1. Authentication & User
Đăng ký tài khoản
POST /auth/register
- Body:
{
"email": "[email protected]",
"password": "P@ssw0rd123",
"name": "Nguyen Van A"
}
- Response:
{
"status": 201,
"message": "User registered successfully",
"data": {
"user": {
"userId": "u123",
"email": "[email protected]",
"name": "Nguyen Van A"
},
"token": "...",
"refreshToken": "..."
},
"pagination": null
}
- Error:
{
"status": 400,
"message": "Email already exists",
"data": null,
"pagination": null
}
Đăng nhập
POST /auth/login
- Body:
{
"email": "[email protected]",
"password": "P@ssw0rd123"
}
- Response:
{
"status": 200,
"message": "Login successful",
"data": {
"user": {
"userId": "u123",
"email": "[email protected]",
"name": "Nguyen Van A"
},
"token": "...",
"refreshToken": "..."
},
"pagination": null
}
- Error:
{
"status": 401,
"message": "Invalid credentials",
"data": null,
"pagination": null
}
Refresh token
POST /auth/refresh-token
- Body:
{ "refreshToken": "..." }
- Response:
{
"status": 200,
"message": "Token refreshed",
"data": {
"token": "...",
"refreshToken": "..."
},
"pagination": null
}
- Error:
{
"status": 401,
"message": "Invalid or expired refresh token",
"data": null,
"pagination": null
}
Xác thực email
GET /auth/validate?token=...
- Response:
{
"status": 200,
"message": "Email validated",
"data": null,
"pagination": null
}
- Error:
{
"status": 400,
"message": "Invalid or expired token",
"data": null,
"pagination": null
}
Đặt lại mật khẩu
POST /auth/reset-password
- Body:
{ "email": "[email protected]" }
- Response:
{
"status": 200,
"message": "Password reset email sent",
"data": null,
"pagination": null
}
- Error:
{
"status": 404,
"message": "Email not found",
"data": null,
"pagination": null
}
Đăng xuất
POST /auth/logout
- Header:
Authorization: Bearer <token>
- Response:
{
"status": 200,
"message": "Logged out successfully",
"data": null,
"pagination": null
}
- Error:
{
"status": 401,
"message": "Invalid token",
"data": null,
"pagination": null
}
3.2. Shop Management
Tạo shop
POST /shops
- Body:
{
"name": "Shop A",
"description": "Shop bán hàng của A",
"address": "123 Đường ABC",
"industries": ["Thời trang"]
}
- Response:
{
"status": 201,
"message": "Shop created successfully",
"data": {
"shopId": "s456",
"name": "Shop A",
"description": "Shop bán hàng của A",
"address": "123 Đường ABC",
"industries": ["Thời trang"],
"verified": false,
"metadata": {
"totalProducts": 0,
"realProducts": 0,
"followers": 0,
"joinedAt": "2024-06-01"
}
},
"pagination": null
}
- Error:
{
"status": 400,
"message": "Shop name already taken",
"data": null,
"pagination": null
}
Chỉnh sửa shop
PUT /shops/{shopId}
- Body:
{
"name": "Shop A Updated",
"description": "Shop bán hàng của A Updated",
"address": "456 Đường XYZ",
"industries": ["Thời trang", "Phụ kiện"]
}
- Response:
{
"status": 200,
"message": "Shop updated successfully",
"data": {
"shopId": "s456",
"name": "Shop A Updated",
"description": "Shop bán hàng của A Updated",
"address": "456 Đường XYZ",
"industries": ["Thời trang", "Phụ kiện"],
"verified": false,
"metadata": {
"totalProducts": 10,
"realProducts": 8,
"followers": 1234,
"joinedAt": "2024-01-01"
}
},
"pagination": null
}
- Error:
{
"status": 404,
"message": "Shop not found",
"data": null,
"pagination": null
}
Lấy thông tin shop
GET /shops/{shopId}
- Response:
{
"status": 200,
"message": "Shop info fetched successfully",
"data": {
"shopId": "s456",
"name": "Shop A",
"description": "Shop bán hàng của A",
"address": "123 Đường ABC",
"industries": ["Thời trang"],
"verified": false,
"products": [ ... ],
"outOfStockProducts": [ ... ],
"categories": [ ... ],
"vouchers": [ ... ],
"metadata": {
"totalProducts": 100,
"realProducts": 80,
"followers": 1234,
"joinedAt": "2024-01-01"
}
},
"pagination": null
}
- Error:
{
"status": 404,
"message": "Shop not found",
"data": null,
"pagination": null
}
Xác minh shop
POST /shops/{shopId}/verify
- Body:
{
"registrationNumber": "123456789",
"businessAddress": "789 Đường KLM",
"productDescription": "Áo quần thời trang",
"operatingHours": "9:00 - 17:00"
}
- Response:
{
"status": 200,
"message": "Verification request submitted",
"data": null,
"pagination": null
}
- Error:
{
"status": 400,
"message": "Missing required information",
"data": null,
"pagination": null
}
Xóa shop
DELETE /shops/{shopId}
- Response:
{
"status": 200,
"message": "Shop deleted successfully",
"data": {
"shopId": "s456",
"name": "Shop A Updated",
"description": "Shop bán hàng của A Updated",
"address": "456 Đường XYZ",
"industries": ["Thời trang", "Phụ kiện"]
},
"pagination": null
}
- Error:
{
"status": 403,
"message": "Shop has products, cannot delete",
"data": null,
"pagination": null
}
Lấy list ngành hàng
GET /industries
- Response:
{
"status": 200,
"message": "Industry list fetched successfully",
"data": [{ "id": "i1", "title": "Thời trang" }],
"pagination": null
}
Yêu cầu thêm ngành hàng mới
POST /industries/request
- Body:
{ "title": "Ngành mới" }
- Response:
{
"status": 200,
"message": "Industry request submitted",
"data": null,
"pagination": null
}
3.3. Product Management
Tạo sản phẩm
POST /products
- Body:
{
"name": "Áo thun trắng",
"description": "Áo thun cotton mềm mại",
"price": 150000,
"images": ["https://example.com/image1.jpg"],
"tags": ["thời trang", "áo thun"],
"categoryId": "cat789",
"attributes": [{ "title": "Chất liệu", "description": "Cotton" }],
"variants": [
{
"sku": "TSHIRT-WHITE-L",
"attributes": { "color": "trắng", "size": "L" },
"price": 155000,
"stock": 20,
"images": ["https://example.com/variant1.jpg"]
},
{
"sku": "TSHIRT-WHITE-M",
"attributes": { "color": "trắng", "size": "M" },
"price": 150000,
"stock": 10,
"images": ["https://example.com/variant2.jpg"]
}
]
}
- Response:
{
"status": 201,
"message": "Product created successfully",
"data": {
"productId": "p101",
"name": "Áo thun trắng",
"description": "Áo thun cotton mềm mại",
"price": 150000,
"images": ["https://example.com/image1.jpg"],
"tags": ["thời trang", "áo thun"],
"categoryId": "cat789",
"attributes": [{ "title": "Chất liệu", "description": "Cotton" }],
"variants": [
{
"variantId": "v404",
"sku": "TSHIRT-WHITE-L",
"attributes": { "color": "trắng", "size": "L" },
"price": 155000,
"stock": 20,
"images": ["https://example.com/variant1.jpg"]
},
{
"variantId": "v405",
"sku": "TSHIRT-WHITE-M",
"attributes": { "color": "trắng", "size": "M" },
"price": 150000,
"stock": 10,
"images": ["https://example.com/variant2.jpg"]
}
]
},
"pagination": null
}
- Error:
{ "error": "Category not found" }
Chỉnh sửa sản phẩm
PUT /products/{productId}
- Body: như tạo sản phẩm
- Response:
{
"status": 200,
"message": "Product updated successfully",
"data": {
"productId": "p101",
"name": "Áo thun trắng Updated",
"description": "Áo thun cotton mềm mại Updated",
"price": 155000,
"images": ["https://example.com/image1.jpg"],
"tags": ["thời trang", "áo thun"],
"categoryId": "cat789",
"attributes": [{ "title": "Chất liệu", "description": "Cotton" }],
"variants": [
{
"variantId": "v404",
"sku": "TSHIRT-WHITE-L",
"attributes": { "color": "trắng", "size": "L" },
"price": 155000,
"stock": 20,
"images": ["https://example.com/variant1.jpg"]
},
{
"variantId": "v405",
"sku": "TSHIRT-WHITE-M",
"attributes": { "color": "trắng", "size": "M" },
"price": 150000,
"stock": 10,
"images": ["https://example.com/variant2.jpg"]
}
]
},
"pagination": null
}
Xóa sản phẩm
DELETE /products/{productId}
- Response:
{
"status": 200,
"message": "Product deleted successfully",
"data": {
"productId": "p101",
"name": "Áo thun trắng Updated",
"description": "Áo thun cotton mềm mại Updated"
},
"pagination": null
}
Tạo tag
POST /tags
- Body:
{
"title": "Mùa hè",
"description": "Sản phẩm phù hợp mùa hè",
"industry": "Thời trang"
}
- Response:
{
"status": 201,
"message": "Tag created successfully",
"data": {
"tagId": "t202",
"title": "Mùa hè",
"description": "Sản phẩm phù hợp mùa hè",
"industry": "Thời trang"
},
"pagination": null
}
Gợi ý tag
GET /tags?industry=...
Example:
GET /tags?industry=Mùa
- Response:
{
"status": 200,
"message": "Tag list fetched successfully",
"data": [
{ "id": "t202", "title": "Mùa hè" },
{ "id": "t203", "title": "Mùa đông" },
{ "id": "t204", "title": "Mùa xuân" },
{ "id": "t205", "title": "Mùa thu" },
{ "id": "t206", "title": "Mùa lễ hội" }
],
"pagination": {
"total": 10,
"page": 1,
"limit": 5,
"canNext": true,
"canPrev": false
}
}
Nhóm sản phẩm theo danh mục
POST /categories/{categoryId}/products
- Body:
{ "productId": "p101" }
- Response:
{
"status": 200,
"message": "Product added to category",
"data": null,
"pagination": null
}
- Error:
{
"status": 404,
"message": "Category not found",
"data": null,
"pagination": null
}
Tạo danh mục
POST /categories
- Body:
{ "title": "Áo thun", "description": "Danh mục áo thun", "shopId": "s456" }
- Response:
{
"status": 201,
"message": "Category created successfully",
"data": {
"categoryId": "cat789",
"title": "Áo thun",
"description": "Danh mục áo thun",
"shopId": "s456"
},
"pagination": null
}
Biến thể sản phẩm (Product Variants)
POST /products/{productId}/variants
- Body:
{
"sku": "TSHIRT-WHITE-L",
"attributes": { "color": "trắng", "size": "L" },
"price": 155000,
"stock": 20,
"images": ["https://example.com/variant1.jpg"]
}
- Response:
{
"status": 201,
"message": "Variant created successfully",
"data": {
"variantId": "v404",
"sku": "TSHIRT-WHITE-L",
"attributes": { "color": "trắng", "size": "L" },
"price": 155000,
"stock": 20,
"images": ["https://example.com/variant1.jpg"]
},
"pagination": null
}
Lấy danh sách biến thể
GET /products/{productId}/variants
- Response:
{
"status": 200,
"message": "Variant list fetched successfully",
"data": [
{
"variantId": "v404",
"sku": "TSHIRT-WHITE-L",
"attributes": { "color": "trắng", "size": "L" },
"price": 155000,
"stock": 20,
"images": ["https://example.com/variant1.jpg"]
}
],
"pagination": null
}
Sản phẩm liên quan
-
GET /products/{productId}/related?type=attribute|tag|userBehavior
-
Response:
{
"status": 200,
"message": "Related products fetched successfully",
"data": [
{ "productId": "p101", "name": "Áo thun trắng" },
{ "productId": "p102", "name": "Áo thun đen" },
{ "productId": "p103", "name": "Áo thun xanh" },
{ "productId": "p104", "name": "Áo thun vàng" },
{ "productId": "p105", "name": "Áo thun đỏ" }
],
"pagination": {
"total": 10,
"page": 1,
"limit": 5,
"canNext": true,
"canPrev": false
}
}
Đánh giá sản phẩm
POST /products/{productId}/reviews
- Body:
{
"rating": 5,
"comment": "Sản phẩm tuyệt vời!",
"images": ["https://example.com/review1.jpg"]
}
- Response:
{
"status": 201,
"message": "Review created successfully",
"data": {
"reviewId": "r303",
"rating": 5,
"comment": "Sản phẩm tuyệt vời!",
"images": ["https://example.com/review1.jpg"]
},
"pagination": null
}
- Error:
{
"status": 404,
"message": "Product not found",
"data": null,
"pagination": null
}
Lấy danh sách đánh giá
GET /products/{productId}/reviews
- Response:
{
"status": 200,
"message": "Review list fetched successfully",
"data": [
{ "reviewId": "r303", "rating": 5, "comment": "Tuyệt vời!" },
{ "reviewId": "r304", "rating": 4, "comment": "Tuyệt vời!" },
{ "reviewId": "r305", "rating": 5, "comment": "Tuyệt vời!" },
{ "reviewId": "r306", "rating": 4, "comment": "Tuyệt vời!" },
{ "reviewId": "r307", "rating": 5, "comment": "Tuyệt vời!" }
],
"pagination": {
"total": 50,
"page": 1,
"limit": 5,
"canNext": true,
"canPrev": false
}
}
Comment sản phẩm
POST /products/{productId}/comments
- Body:
{ "comment": "Đẹp quá!", "images": ["https://example.com/comment1.jpg"] }
- Response:
{
"status": 201,
"message": "Comment created successfully",
"data": {
"commentId": "c505",
"comment": "Đẹp quá!",
"images": ["https://example.com/comment1.jpg"]
},
"pagination": null
}
Lấy danh sách comment
GET /products/{productId}/comments
- Response:
{
"status": 200,
"message": "Comment list fetched successfully",
"data": [
{ "commentId": "c506", "comment": "Đẹp quá!" },
{ "commentId": "c507", "comment": "Đẹp quá!" },
{ "commentId": "c508", "comment": "Đẹp quá!" },
{ "commentId": "c509", "comment": "Đẹp quá!" },
{ "commentId": "c510", "comment": "Đẹp quá!" }
],
"pagination": {
"total": 10,
"page": 2,
"limit": 5,
"canNext": true,
"canPrev": true
}
}
Ghi nhận hành vi người dùng (User Behavior Tracking)
POST /user-behaviors
- Body:
{
"userId": "u123",
"productId": "p101",
"action": "VIEW" // VIEW | CLICK | ADD_TO_CART | PURCHASE
}
- Response:
{
"status": 201,
"message": "User behavior tracked successfully",
"data": {
"userId": "u123",
"productId": "p101",
"action": "VIEW" // VIEW | CLICK | ADD_TO_CART | PURCHASE
},
"pagination": null
}
3.4. Cart & Checkout
Thêm vào giỏ hàng
POST /carts
- Body:
{ "productId": "p101", "quantity": 2, "variantId": "v404" }
- Response:
{
"status": 201,
"message": "Added to cart",
"data": {
"cartProducts": [
{
"shopId": "s456",
"products": [
{
"productId": "p101",
"variantId": "v404",
"quantity": 2,
"price": 155000,
"inStock": 20,
"inCarts": 10
}
],
"shopVoucher": { "code": "SHOPSALE", "discount": 20000 }
}
],
"globalVouchers": [{ "code": "SUMMER2025", "discount": 50000 }],
"shippingVouchers": [{ "code": "FREESHIP", "discount": 30000 }],
"total": 310000,
"finalTotal": 260000
},
"pagination": null
}
- Error:
{
"status": 400,
"message": "Product out of stock",
"data": null,
"pagination": null
}
Xem giỏ hàng
GET /carts/{cartId}
- Response:
{
"status": 200,
"message": "Cart fetched successfully",
"data": {
"items": [
{
"shopId": "s456",
"products": [
{
"productId": "p101",
"variantId": "v404",
"quantity": 2,
"price": 155000,
"inStock": 20,
"inCarts": 10
}
],
"shopVoucher": { "code": "SHOPSALE", "discount": 20000 }
}
],
"globalVouchers": [{ "code": "SUMMER2025", "discount": 50000 }],
"shippingVouchers": [{ "code": "FREESHIP", "discount": 30000 }],
"total": 310000,
"finalTotal": 260000
},
"pagination": null
}
Áp dụng mã giảm giá
POST /carts/{cartId}/vouchers
- Body:
{ "voucherCode": "SUMMER2025" }
- Response:
{
"status": 200,
"message": "Voucher applied",
"data": {
"discount": 50000,
"voucherCode": "SUMMER2025"
},
"pagination": null
}
- Error:
{
"status": 400,
"message": "Voucher not valid",
"data": null,
"pagination": null
}
Thanh toán (Checkout)
POST /checkout
- Body:
{
"cartId": "c505",
"addressId": "a606",
"paymentMethod": "ewallet",
"voucherCode": "SUMMER2025"
}
- Response:
{
"status": 201,
"message": "Checkout created successfully",
"data": {
"orderId": "o707",
"sessionId": "s808"
},
"pagination": null
}
- Error:
{
"status": 400,
"message": "Cart is empty",
"data": null,
"pagination": null
}
Kiểm tra trạng thái thanh toán
GET /checkout/{sessionId}/status
- Response:
{
"status": 200,
"message": "Session status fetched successfully",
"data": {
"status": "COMPLETED"
},
"pagination": null
}
API mock đổi trạng thái session
POST /checkout/{sessionId}/mock-status
- Body:
{ "status": "COMPLETED" }
- Response:
{
"status": 200,
"message": "Session status updated",
"data": null,
"pagination": null
}
3.5. Order Management
Lấy danh sách đơn hàng
GET /orders?status=CONFIRMED|PACKED|SHIPPING|DELIVERED|CANCELLED
- Response:
{
"status": 200,
"message": "Order list fetched successfully",
"data": [
{ "orderId": "o707", "status": "CONFIRMED" },
{ "orderId": "o708", "status": "PACKED" },
{ "orderId": "o709", "status": "SHIPPING" },
{ "orderId": "o710", "status": "DELIVERED" },
{ "orderId": "o711", "status": "CANCELLED" }
],
"pagination": {
"total": 50,
"page": 2,
"limit": 5,
"canNext": true,
"canPrev": true
}
}
Lấy chi tiết đơn hàng
GET /orders/{orderId}
- Response:
{
"status": 200,
"message": "Order info fetched successfully",
"data": {
"orderId": "o707",
"status": "shipping",
"total": 310000,
"finalTotal": 260000,
"paymentMethod": "ewallet",
"voucherCode": "SUMMER2025",
"address": "123 Đường ABC",
"shops": [
{
"shopId": "s456",
"shopName": "Shop A",
"products": [
{
"productId": "p101",
"productName": "Áo thun trắng",
"variantId": "v404",
"price": 155000,
"status": "packed",
"quantity": 2
}
],
"shopStatus": "packed"
},
{
"shopId": "s457",
"shopName": "Shop B",
"products": [
{
"productId": "p102",
"productName": "Áo thun trắng",
"variantId": "v405",
"price": 155000,
"status": "packed",
"quantity": 1
}
],
"shopStatus": "shipping"
}
],
"createdAt": "2024-01-01"
},
"pagination": null
}
- Error:
{
"status": 404,
"message": "Order not found",
"data": null,
"pagination": null
}
Cập nhật trạng thái đơn hàng (mock)
POST /orders/{orderId}/mock-status
- Body:
{ "status": "packed" }
- Response:
{
"status": 200,
"message": "Order status updated",
"data": {
"orderId": "o707",
"status": "packed"
},
"pagination": null
}
Xác nhận đã nhận hàng
POST /orders/{orderId}/confirm
- Response:
{
"status": 200,
"message": "Order confirmed as delivered",
"data": {
"orderId": "o707",
"status": "delivered"
},
"pagination": null
}
3.6. Voucher & Promotion
Tạo mã giảm giá (shop/super admin)
POST /vouchers
- Body:
{
"title": "Giảm giá mùa hè",
"type": "SHIPPING|PRICE|PERCENTAGE",
"conditions": {
"minAmount": 300000,
"paymentMethods": ["CASH", "EWALLET", "BANK_CARD", "TRANSFER_TO_PLATFORM"]
},
"discount": 50000,
"shopId": "s456"
}
- Response:
{
"status": 201,
"message": "Voucher created successfully",
"data": {
"voucherId": "v909",
"code": "SUMMER2025",
"title": "Giảm giá mùa hè",
"type": "SHIPPING|PRICE|PERCENTAGE",
"conditions": {
"minAmount": 300000,
"paymentMethods": ["CASH", "EWALLET", "BANK_CARD", "TRANSFER_TO_PLATFORM"]
},
"discount": 50000,
"shopId": "s456" | ["s456", "s457"] | null // null as global voucher
},
"pagination": null
}
- Error:
{
"status": 400,
"message": "Voucher code already exists",
"data": null,
"pagination": null
}
Lấy danh sách voucher
GET /vouchers?shopId=...
- Response:
{
"status": 200,
"message": "Voucher list fetched successfully",
"data": [
{ "voucherId": "v909", "code": "SUMMER2025", "discount": 50000 },
{ "voucherId": "v910", "code": "SUMMER2026", "discount": 50000 }
],
"pagination": {
"total": 2,
"page": 1,
"limit": 5,
"canNext": true,
"canPrev": false
}
}
3.7. Address & Profile
Thêm địa chỉ
POST /users/{userId}/addresses
- Body:
{ "address": "456 Đường XYZ", "tag": "Công ty" }
- Response:
{
"status": 201,
"message": "Address created successfully",
"data": {
"addressId": "a707",
"address": "456 Đường XYZ",
"tag": "Công ty"
},
"pagination": null
}
Sửa địa chỉ
PUT /users/{userId}/addresses/{addressId}
- Body:
{ "address": "789 Đường ABC", "tag": "Nhà riêng" }
- Response:
{
"status": 200,
"message": "Address updated successfully",
"data": {
"addressId": "a707",
"address": "789 Đường ABC",
"tag": "Nhà riêng"
},
"pagination": null
}
Đặt mặc định
POST /users/{userId}/addresses/{addressId}/default
- Response:
{
"status": 200,
"message": "Address set as default",
"data": null,
"pagination": null
}
Lấy profile
GET /users/{userId}
- Response:
{
"status": 200,
"message": "Profile fetched successfully",
"data": {
"userId": "u123",
"name": "Nguyen Van A",
"email": "[email protected]",
"avatar": "https://example.com/avatar.jpg",
"age": 25,
"walletLinks": ["momo", "zalopay"],
"cardLinks": ["visa", "mastercard"],
"addresses": [{ "id": "a606", "address": "123 Đường ABC", "tag": "Nhà" }]
},
"pagination": null
}
Cập nhật profile
PUT /users/{userId}
- Body:
{
"name": "Nguyen Van B",
"avatar": "https://example.com/avatar2.jpg",
"age": 26
}
- Response:
{
"status": 200,
"message": "Profile updated successfully",
"data": {
"userId": "u123",
"name": "Nguyen Van B",
"avatar": "https://example.com/avatar2.jpg",
"age": 26
},
"pagination": null
}
Đổi mật khẩu/gửi lại mail
POST /auth/reset-password
- Body:
{ "email": "[email protected]" }
- Response:
{
"status": 200,
"message": "Password reset email sent",
"data": null,
"pagination": null
}
- Error:
{
"status": 400,
"message": "Email not found",
"data": null,
"pagination": null
}
Cập nhật mật khẩu
PUT /auth/update-password
- Body:
{ "token": "abc123xyz", "newPassword": "NewP@ssw0rd" }
- Response:
{
"status": 200,
"message": "Password updated successfully",
"data": null,
"pagination": null
}
- Error:
{
"status": 400,
"message": "Invalid or expired token",
"data": null,
"pagination": null
}
4. Database
DBML (Database Markup Language)
// Enums
enum UserRole {
USER
SHOP_OWNER
ADMIN
}
enum StatusType {
PENDING
APPROVED
REJECTED
}
enum OrderStatus {
CONFIRMED
PACKED
SHIPPING
DELIVERED
CANCELLED
}
enum PaymentStatus {
PENDING
COMPLETED
FAILED
}
enum PaymentMethod {
CASH
EWALLET
BANK_CARD
TRANSFER_TO_PLATFORM
}
enum VoucherType {
SHIPPING
PRICE
PERCENTAGE
}
enum UserBehaviorAction {
VIEW
CLICK
ADD_TO_CART
PURCHASE
}
// Users & Authentication
Table users {
id String [pk]
email String [unique]
password String
name String
avatar String
age Int
role UserRole [default: 'USER']
verified Boolean [default: false]
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table addresses {
id String [pk]
userId String [ref: > users.id]
address String
tag String
isDefault Boolean [default: false]
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
// Shop Management
Table industries {
id String [pk]
title String
status StatusType [default: 'PENDING']
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table shops {
id String [pk]
name String
description String
address String
verified Boolean [default: false]
userId String [ref: > users.id]
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table shop_industries {
shopId String [ref: > shops.id]
industryId String [ref: > industries.id]
indexes {
(shopId, industryId) [pk]
}
}
Table verification_requests {
id String [pk]
shopId String [ref: > shops.id]
registrationNumber String
businessAddress String
productDescription String
operatingHours String
status StatusType [default: 'PENDING']
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
// Product Management
Table categories {
id String [pk]
title String
description String
shopId String [ref: > shops.id]
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table tags {
id String [pk]
title String
description String
industry String
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table products {
id String [pk]
name String
description String
price Decimal
shopId String [ref: > shops.id]
categoryId String [ref: > categories.id]
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table product_images {
id String [pk]
productId String [ref: > products.id]
url String
createdAt DateTime [default: `now()`]
}
Table product_attributes {
id String [pk]
productId String [ref: > products.id]
title String
description String
createdAt DateTime [default: `now()`]
}
Table product_tags {
productId String [ref: > products.id]
tagId String [ref: > tags.id]
indexes {
(productId, tagId) [pk]
}
}
Table variants {
id String [pk]
productId String [ref: > products.id]
sku String [unique]
price Decimal
stock Int
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table variant_attributes {
id String [pk]
variantId String [ref: > variants.id]
key String
value String
indexes {
(variantId, key) [unique]
}
}
Table variant_images {
id String [pk]
variantId String [ref: > variants.id]
url String
createdAt DateTime [default: `now()`]
}
// Reviews & Comments
Table reviews {
id String [pk]
productId String [ref: > products.id]
userId String [ref: > users.id]
rating Int
comment String
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table review_images {
id String [pk]
reviewId String [ref: > reviews.id]
url String
createdAt DateTime [default: `now()`]
}
Table comments {
id String [pk]
productId String [ref: > products.id]
userId String [ref: > users.id]
comment String
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table comment_images {
id String [pk]
commentId String [ref: > comments.id]
url String
createdAt DateTime [default: `now()`]
}
// Cart & Checkout
Table carts {
id String [pk]
userId String [ref: > users.id]
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table cart_items {
id String [pk]
cartId String [ref: > carts.id]
productId String [ref: > products.id]
variantId String [ref: > variants.id]
quantity Int
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table cart_vouchers {
cartId String [ref: > carts.id]
voucherId String [ref: > vouchers.id]
indexes {
(cartId, voucherId) [pk]
}
}
// Orders
Table orders {
id String [pk]
userId String [ref: > users.id]
addressId String [ref: > addresses.id]
status OrderStatus [default: 'CONFIRMED']
total Decimal
finalTotal Decimal
paymentMethod PaymentMethod
voucherCode String
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table order_items {
id String [pk]
orderId String [ref: > orders.id]
productId String [ref: > products.id]
variantId String [ref: > variants.id]
shopId String [ref: > shops.id]
quantity Int
price Decimal
status OrderStatus [default: 'CONFIRMED']
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table payment_sessions {
id String [pk]
orderId String [ref: > orders.id]
status PaymentStatus [default: 'PENDING']
method PaymentMethod
amount Decimal
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
// Vouchers & Promotions
Table vouchers {
id String [pk]
code String [unique]
title String
type VoucherType
discount Decimal
minAmount Decimal
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
expiresAt DateTime
}
Table voucher_payment_methods {
voucherId String [ref: > vouchers.id]
paymentMethod PaymentMethod
indexes {
(voucherId, paymentMethod) [pk]
}
}
Table shop_vouchers {
voucherId String [ref: > vouchers.id]
shopId String [ref: > shops.id]
indexes {
(voucherId, shopId) [pk]
}
}
// User wallet and payment info
Table user_wallets {
id String [pk]
userId String [ref: > users.id]
provider String // momo, zalopay
linked Boolean [default: false]
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
Table user_cards {
id String [pk]
userId String [ref: > users.id]
type String // visa, mastercard
last4 String
createdAt DateTime [default: `now()`]
updatedAt DateTime [updatedAt]
}
// Refresh tokens
Table refresh_tokens {
id String [pk]
userId String [ref: > users.id]
token String [unique]
expiresAt DateTime
createdAt DateTime [default: `now()`]
}
Table user_behaviors {
id String [pk]
userId String [ref: > users.id]
productId String [ref: > products.id]
action UserBehaviorAction
createdAt DateTime [default: `now()`]
}
Prisma schema
// This is your Prisma schema file
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// Enums
enum UserRole {
USER
SHOP_OWNER
ADMIN
}
enum StatusType {
PENDING
APPROVED
REJECTED
}
enum OrderStatus {
CONFIRMED
PACKED
SHIPPING
DELIVERED
CANCELLED
}
enum PaymentStatus {
PENDING
COMPLETED
FAILED
}
enum PaymentMethod {
CASH
EWALLET
BANK_CARD
TRANSFER_TO_PLATFORM
}
enum VoucherType {
SHIPPING
PRICE
PERCENTAGE
}
enum UserBehaviorAction {
VIEW
CLICK
ADD_TO_CART
PURCHASE
}
// Users & Authentication
model User {
id String @id @default(uuid())
email String @unique
password String
name String
avatar String?
age Int?
role UserRole @default(USER)
verified Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
addresses Address[]
shops Shop[]
reviews Review[]
comments Comment[]
carts Cart[]
orders Order[]
wallets UserWallet[]
cards UserCard[]
refreshTokens RefreshToken[]
}
model Address {
id String @id @default(uuid())
userId String
address String
tag String
isDefault Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
orders Order[]
}
// Shop Management
model Industry {
id String @id @default(uuid())
title String
status StatusType @default(PENDING)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
shops ShopIndustry[]
}
model Shop {
id String @id @default(uuid())
name String
description String
address String
verified Boolean @default(false)
userId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
industries ShopIndustry[]
categories Category[]
products Product[]
verificationRequests VerificationRequest[]
vouchers ShopVoucher[]
orderItems OrderItem[]
}
model ShopIndustry {
shopId String
industryId String
shop Shop @relation(fields: [shopId], references: [id])
industry Industry @relation(fields: [industryId], references: [id])
@@id([shopId, industryId])
}
model VerificationRequest {
id String @id @default(uuid())
shopId String
registrationNumber String
businessAddress String
productDescription String
operatingHours String
status StatusType @default(PENDING)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
shop Shop @relation(fields: [shopId], references: [id])
}
// Product Management
model Category {
id String @id @default(uuid())
title String
description String
shopId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
shop Shop @relation(fields: [shopId], references: [id])
products Product[]
}
model Tag {
id String @id @default(uuid())
title String
description String
industry String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
products ProductTag[]
}
model Product {
id String @id @default(uuid())
name String
description String
price Decimal
shopId String
categoryId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
shop Shop @relation(fields: [shopId], references: [id])
category Category @relation(fields: [categoryId], references: [id])
images ProductImage[]
attributes ProductAttribute[]
tags ProductTag[]
variants Variant[]
reviews Review[]
comments Comment[]
cartItems CartItem[]
orderItems OrderItem[]
}
model ProductImage {
id String @id @default(uuid())
productId String
url String
createdAt DateTime @default(now())
product Product @relation(fields: [productId], references: [id])
}
model ProductAttribute {
id String @id @default(uuid())
productId String
title String
description String
createdAt DateTime @default(now())
product Product @relation(fields: [productId], references: [id])
}
model ProductTag {
productId String
tagId String
product Product @relation(fields: [productId], references: [id])
tag Tag @relation(fields: [tagId], references: [id])
@@id([productId, tagId])
}
model Variant {
id String @id @default(uuid())
productId String
sku String @unique
price Decimal
stock Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
product Product @relation(fields: [productId], references: [id])
attributes VariantAttribute[]
images VariantImage[]
cartItems CartItem[]
orderItems OrderItem[]
}
model VariantAttribute {
id String @id @default(uuid())
variantId String
key String
value String
variant Variant @relation(fields: [variantId], references: [id])
@@unique([variantId, key])
}
model VariantImage {
id String @id @default(uuid())
variantId String
url String
createdAt DateTime @default(now())
variant Variant @relation(fields: [variantId], references: [id])
}
// Reviews & Comments
model Review {
id String @id @default(uuid())
productId String
userId String
rating Int
comment String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
product Product @relation(fields: [productId], references: [id])
user User @relation(fields: [userId], references: [id])
images ReviewImage[]
}
model ReviewImage {
id String @id @default(uuid())
reviewId String
url String
createdAt DateTime @default(now())
review Review @relation(fields: [reviewId], references: [id])
}
model Comment {
id String @id @default(uuid())
productId String
userId String
comment String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
product Product @relation(fields: [productId], references: [id])
user User @relation(fields: [userId], references: [id])
images CommentImage[]
}
model CommentImage {
id String @id @default(uuid())
commentId String
url String
createdAt DateTime @default(now())
comment Comment @relation(fields: [commentId], references: [id])
}
// Cart & Checkout
model Cart {
id String @id @default(uuid())
userId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
items CartItem[]
vouchers CartVoucher[]
}
model CartItem {
id String @id @default(uuid())
cartId String
productId String
variantId String
quantity Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
cart Cart @relation(fields: [cartId], references: [id])
product Product @relation(fields: [productId], references: [id])
variant Variant @relation(fields: [variantId], references: [id])
}
model CartVoucher {
cartId String
voucherId String
cart Cart @relation(fields: [cartId], references: [id])
voucher Voucher @relation(fields: [voucherId], references: [id])
@@id([cartId, voucherId])
}
// Orders
model Order {
id String @id @default(uuid())
userId String
addressId String
status OrderStatus @default(CONFIRMED)
total Decimal
finalTotal Decimal
paymentMethod PaymentMethod
voucherCode String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
address Address @relation(fields: [addressId], references: [id])
items OrderItem[]
paymentSessions PaymentSession[]
}
model OrderItem {
id String @id @default(uuid())
orderId String
productId String
variantId String
shopId String
quantity Int
price Decimal
status OrderStatus @default(CONFIRMED)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
order Order @relation(fields: [orderId], references: [id])
product Product @relation(fields: [productId], references: [id])
variant Variant @relation(fields: [variantId], references: [id])
shop Shop @relation(fields: [shopId], references: [id])
}
model PaymentSession {
id String @id @default(uuid())
orderId String
status PaymentStatus @default(PENDING)
method PaymentMethod
amount Decimal
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
order Order @relation(fields: [orderId], references: [id])
}
// Vouchers & Promotions
model Voucher {
id String @id @default(uuid())
code String @unique
title String
type VoucherType
discount Decimal
minAmount Decimal
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
expiresAt DateTime
paymentMethods VoucherPaymentMethod[]
shops ShopVoucher[]
carts CartVoucher[]
}
model VoucherPaymentMethod {
voucherId String
paymentMethod PaymentMethod
voucher Voucher @relation(fields: [voucherId], references: [id])
@@id([voucherId, paymentMethod])
}
model ShopVoucher {
voucherId String
shopId String
voucher Voucher @relation(fields: [voucherId], references: [id])
shop Shop @relation(fields: [shopId], references: [id])
@@id([voucherId, shopId])
}
// User wallet and payment info
model UserWallet {
id String @id @default(uuid())
userId String
provider WalletProvider
linked Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
}
model UserCard {
id String @id @default(uuid())
userId String
type CardType
last4 String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
}
// Refresh tokens
model RefreshToken {
id String @id @default(uuid())
userId String
token String @unique
expiresAt DateTime
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
}
model UserBehavior {
id String @id @default(uuid())
userId String
productId String
action UserBehaviorAction
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
product Product @relation(fields: [productId], references: [id])
}
5. Luồng nghiệp vụ chính (Business Flow)
1. Đăng ký, xác thực, đăng nhập tài khoản
- User đăng ký tài khoản với email, password, tên.
- Gửi email xác thực: User nhận link xác thực qua email, click để xác thực tài khoản.
- Đăng nhập: Sau khi xác thực, user đăng nhập lấy access token, refresh token.
2. Quản lý Shop
- Tạo shop: User tạo shop với tên, mô tả, địa chỉ, chọn ngành hàng từ list có sẵn.
- Chỉnh sửa shop: Sửa tên, mô tả, địa chỉ, cập nhật ngành hàng.
- Xác minh shop: Gửi yêu cầu xác minh với các thông tin: số đăng ký kinh doanh, địa chỉ đăng ký, mô tả mặt hàng, thời gian làm việc. Nhân viên sàn sẽ kiểm chứng và cập nhật trạng thái verified.
- Quản lý metadata shop: Shop có các thông tin: tổng số sản phẩm, số sản phẩm thực (trừ hết hàng), số người theo dõi, ngày tham gia, voucher riêng, danh mục tự tạo, trạng thái verified.
3. Quản lý Sản phẩm, Tag, Danh mục
- Tạo sản phẩm: Shop owner tạo sản phẩm với tên, mô tả (markdown), giá, ảnh (list), thuộc tính (tự tạo), tag (gợi ý từ tag đã có), danh mục (folder).
- Chỉnh sửa/xóa sản phẩm: Sửa/xóa thông tin sản phẩm.
- Quản lý biến thể (variant): Tạo các loại sản phẩm con với thuộc tính riêng (tên, ảnh, giá, tồn kho, đã bán).
- Gán sản phẩm vào danh mục: Gom nhóm sản phẩm theo danh mục tự tạo.
- Quản lý tag: Tạo tag mới (title, desc, ngành hàng), gợi ý tag theo ngành hàng.
- Sản phẩm liên quan: Gợi ý dựa trên thuộc tính, tag, thói quen user (tracking).
4. Quản lý Giỏ hàng & Voucher
- Thêm vào giỏ hàng: User thêm sản phẩm (theo variant) vào giỏ, nhóm theo shop.
- Xem giỏ hàng: Lấy thông tin sản phẩm trong giỏ, voucher áp dụng, tổng tiền, tồn kho, số người đã cho vào giỏ.
- Áp dụng voucher: Áp dụng mã giảm giá riêng shop, mã giảm giá vận chuyển, mã giảm giá tiền.
- Tạo voucher: Shop owner/super admin tạo voucher với tiêu đề, loại (vận chuyển, giá), điều kiện (minAmount, paymentMethods).
5. Thanh toán (Checkout)
- Quản lý địa chỉ: Thêm, sửa, xóa, chọn địa chỉ nhận hàng, đặt mặc định, gán tag (nhà riêng, công sở,…).
- Chọn phương thức thanh toán: Tiền mặt, ví điện tử, thẻ ngân hàng, chuyển khoản vào sàn.
- Tạo đơn hàng: Gom nhóm đơn theo shop, tính tổng tiền, tổng tiền cuối cùng (sau voucher, thuộc tính).
- Mở session thanh toán: Với các phương thức không phải COD, tạo sessionId (live 10 phút), frontend polling check status, có API mock đổi trạng thái session.
6. Quản lý Đơn hàng
- Lấy danh sách đơn hàng: Gom nhóm theo trạng thái (đã xác nhận, đã đóng gói, đang vận chuyển, đã nhận hàng), đã hủy.
- Chi tiết đơn hàng: Trạng thái đơn, sản phẩm trong đơn (gom nhóm theo shop), trạng thái từng sản phẩm, từng shop.
- Cập nhật trạng thái đơn hàng: Có API mock đổi trạng thái, xác nhận đã nhận hàng (nếu không confirm sau 10 ngày, tự động xác nhận).
7. Đánh giá & Bình luận sản phẩm
- Đánh giá sản phẩm: User đánh giá (số sao, mô tả, ảnh).
- Bình luận sản phẩm: User comment (mô tả, ảnh).
- Lấy danh sách đánh giá, comment: Hỗ trợ phân trang.
8. Quản lý Profile User
- Thông tin cá nhân: Tên, tuổi, ảnh, email.
- Liên kết ví điện tử, thẻ thanh toán: Thêm/xóa liên kết.
- Quản lý địa chỉ nhận hàng: List, thêm, sửa, xóa, tag địa chỉ.
- Đổi mật khẩu, cấp lại mật khẩu qua email: Gửi mail reset, cập nhật mật khẩu mới.
9. Quản trị Super Admin
- Duyệt ngành hàng mới: Xem và duyệt các yêu cầu thêm ngành hàng.
- Xác minh shop: Duyệt yêu cầu xác minh shop.
- Tạo voucher toàn sàn: Tạo mã giảm giá áp dụng toàn hệ thống.
6. Ghi chú triển khai (Implementation Notes)
1. Chuẩn hóa API & Response
- Tất cả API trả về dữ liệu đúng nghiệp vụ, gom nhóm theo shop khi cần.
- Hỗ trợ phân trang, lọc, tìm kiếm cho các list (sản phẩm, đơn hàng, đánh giá, comment…).
- Response mẫu:
{ status, message, data, pagination }
hoặc{ error }
với status code phù hợp.
Với API có phân trang, response sẽ có thêm pagination và data là array
:
{
"status": 200,
"message": "Success",
"data": [
{
"id": "o707",
"status": "CONFIRMED",
"totalAmount": 100000
}
],
"pagination": {
"total": 10,
"page": 1,
"limit": 1,
"canNext": true,
"canPrev": true
}
}
Với API không có phân trang, response sẽ có thêm data là object
hoặc null
:
{
"status": 200,
"message": "Success",
"data": null,
"pagination": null
}
2. Phân quyền & Bảo mật
- Tách quyền rõ ràng: super admin, shop owner, user.
- Các API cần xác thực: Yêu cầu Bearer Token, kiểm tra quyền truy cập.
- Bảo mật thông tin cá nhân, token, dữ liệu nhạy cảm.
3. Email & Xác thực
- Tích hợp email service: Gửi mail xác thực, reset password, thông báo trạng thái shop.
- Link xác thực email: Có token, hết hạn sau thời gian nhất định.
4. Thanh toán & Session
- Tích hợp payment gateway: Hỗ trợ nhiều phương thức thanh toán.
- Session thanh toán: Tạo sessionId, timeout 10 phút, API polling check status, API mock đổi trạng thái cho test frontend.
5. Xử lý nghiệp vụ đặc thù
- Voucher: Áp dụng đúng điều kiện (minAmount, paymentMethods, loại voucher), phân biệt voucher toàn sàn và voucher shop.
- Đơn hàng: Gom nhóm theo shop, trạng thái từng sản phẩm, từng shop, từng đơn.
- Sản phẩm liên quan: Gợi ý dựa trên thuộc tính, tag, tracking hành vi user.
6. Tài liệu hóa & Test
- Tài liệu hóa rõ ràng từng endpoint: Request, response, status code, error, phân quyền.
- Mock API: Hỗ trợ đổi trạng thái đơn hàng, session thanh toán để test frontend.
- Test case: Đảm bảo các flow chính đều có test, bao gồm edge case (hết hàng, voucher không hợp lệ, session timeout…).
7. Khả năng mở rộng & maintain
- Thiết kế schema, API dễ mở rộng: Thêm ngành hàng, phương thức thanh toán, loại voucher mới.
- Tối ưu truy vấn, index cho các bảng lớn (sản phẩm, đơn hàng, user…).
- Đảm bảo code clean, dễ maintain, dễ kiểm thử.