Mục lục
Mục lục
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.
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.
POST /auth/register
Body:
{
"email": "[email protected]",
"password": "P@ssw0rd123",
"name": "Nguyen Van A"
}
{
"status": 201,
"message": "User registered successfully",
"data": {
"user": {
"userId": "u123",
"email": "[email protected]",
"name": "Nguyen Van A"
},
"token": "...",
"refreshToken": "..."
},
"pagination": null
}
{
"status": 400,
"message": "Email already exists",
"data": null,
"pagination": null
}
POST /auth/login
Body:
{
"email": "[email protected]",
"password": "P@ssw0rd123"
}
{
"status": 200,
"message": "Login successful",
"data": {
"user": {
"userId": "u123",
"email": "[email protected]",
"name": "Nguyen Van A"
},
"token": "...",
"refreshToken": "..."
},
"pagination": null
}
{
"status": 401,
"message": "Invalid credentials",
"data": null,
"pagination": null
}
POST /auth/refresh-token
Body:
{ "refreshToken": "..." }
{
"status": 200,
"message": "Token refreshed",
"data": {
"token": "...",
"refreshToken": "..."
},
"pagination": null
}
{
"status": 401,
"message": "Invalid or expired refresh token",
"data": null,
"pagination": null
}
GET /auth/validate?token=…
Response:
{
"status": 200,
"message": "Email validated",
"data": null,
"pagination": null
}
{
"status": 400,
"message": "Invalid or expired token",
"data": null,
"pagination": null
}
POST /auth/reset-password
Body:
{ "email": "[email protected]" }
{
"status": 200,
"message": "Password reset email sent",
"data": null,
"pagination": null
}
{
"status": 404,
"message": "Email not found",
"data": null,
"pagination": null
}
POST /auth/logout
Header: Authorization: Bearer
Response:
{
"status": 200,
"message": "Logged out successfully",
"data": null,
"pagination": null
}
{
"status": 401,
"message": "Invalid token",
"data": null,
"pagination": null
}
POST /shops
Body:
{
"name": "Shop A",
"description": "Shop bán hàng của A",
"address": "123 Đường ABC",
"industries": ["Thời trang"]
}
{
"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
}
{
"status": 400,
"message": "Shop name already taken",
"data": null,
"pagination": null
}
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"]
}
{
"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
}
{
"status": 404,
"message": "Shop not found",
"data": null,
"pagination": null
}
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
}
{
"status": 404,
"message": "Shop not found",
"data": null,
"pagination": null
}
POST /shops/{shopId}/verify
Body:
{
"registrationNumber": "123456789",
"businessAddress": "789 Đường KLM",
"productDescription": "Áo quần thời trang",
"operatingHours": "9:00 - 17:00"
}
{
"status": 200,
"message": "Verification request submitted",
"data": null,
"pagination": null
}
{
"status": 400,
"message": "Missing required information",
"data": null,
"pagination": null
}
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
}
{
"status": 403,
"message": "Shop has products, cannot delete",
"data": null,
"pagination": null
}
GET /industries
Response:
{
"status": 200,
"message": "Industry list fetched successfully",
"data": [{ "id": "i1", "title": "Thời trang" }],
"pagination": null
}
POST /industries/request
Body:
{ "title": "Ngành mới" }
{
"status": 200,
"message": "Industry request submitted",
"data": null,
"pagination": null
}
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"]
}
]
}
{
"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": "Category not found" }
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
}
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
}
POST /tags
Body:
{
"title": "Mùa hè",
"description": "Sản phẩm phù hợp mùa hè",
"industry": "Thời trang"
}
{
"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
}
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
}
}
POST /categories/{categoryId}/products
Body:
{ "productId": "p101" }
{
"status": 200,
"message": "Product added to category",
"data": null,
"pagination": null
}
{
"status": 404,
"message": "Category not found",
"data": null,
"pagination": null
}
POST /categories
Body:
{ "title": "Áo thun", "description": "Danh mục áo thun", "shopId": "s456" }
{
"status": 201,
"message": "Category created successfully",
"data": {
"categoryId": "cat789",
"title": "Áo thun",
"description": "Danh mục áo thun",
"shopId": "s456"
},
"pagination": null
}
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"]
}
{
"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
}
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
}
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
}
}
POST /products/{productId}/reviews
Body:
{
"rating": 5,
"comment": "Sản phẩm tuyệt vời!",
"images": ["https://example.com/review1.jpg"]
}
{
"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
}
{
"status": 404,
"message": "Product not found",
"data": null,
"pagination": null
}
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
}
}
POST /products/{productId}/comments
Body:
{ "comment": "Đẹp quá!", "images": ["https://example.com/comment1.jpg"] }
{
"status": 201,
"message": "Comment created successfully",
"data": {
"commentId": "c505",
"comment": "Đẹp quá!",
"images": ["https://example.com/comment1.jpg"]
},
"pagination": null
}
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
}
}
POST /user-behaviors
Body:
{
"userId": "u123",
"productId": "p101",
"action": "VIEW" // VIEW | CLICK | ADD_TO_CART | PURCHASE
}
{
"status": 201,
"message": "User behavior tracked successfully",
"data": {
"userId": "u123",
"productId": "p101",
"action": "VIEW" // VIEW | CLICK | ADD_TO_CART | PURCHASE
},
"pagination": null
}
POST /carts
Body:
{ "productId": "p101", "quantity": 2, "variantId": "v404" }
{
"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
}
{
"status": 400,
"message": "Product out of stock",
"data": null,
"pagination": null
}
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
}
POST /carts/{cartId}/vouchers
Body:
{ "voucherCode": "SUMMER2025" }
{
"status": 200,
"message": "Voucher applied",
"data": {
"discount": 50000,
"voucherCode": "SUMMER2025"
},
"pagination": null
}
{
"status": 400,
"message": "Voucher not valid",
"data": null,
"pagination": null
}
POST /checkout
Body:
{
"cartId": "c505",
"addressId": "a606",
"paymentMethod": "ewallet",
"voucherCode": "SUMMER2025"
}
{
"status": 201,
"message": "Checkout created successfully",
"data": {
"orderId": "o707",
"sessionId": "s808"
},
"pagination": null
}
{
"status": 400,
"message": "Cart is empty",
"data": null,
"pagination": null
}
GET /checkout/{sessionId}/status
Response:
{
"status": 200,
"message": "Session status fetched successfully",
"data": {
"status": "COMPLETED"
},
"pagination": null
}
POST /checkout/{sessionId}/mock-status
Body:
{ "status": "COMPLETED" }
{
"status": 200,
"message": "Session status updated",
"data": null,
"pagination": null
}
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
}
}
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
}
{
"status": 404,
"message": "Order not found",
"data": null,
"pagination": null
}
POST /orders/{orderId}/mock-status
Body:
{ "status": "packed" }
{
"status": 200,
"message": "Order status updated",
"data": {
"orderId": "o707",
"status": "packed"
},
"pagination": null
}
POST /orders/{orderId}/confirm
Response:
{
"status": 200,
"message": "Order confirmed as delivered",
"data": {
"orderId": "o707",
"status": "delivered"
},
"pagination": null
}
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"
}
{
"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
}
{
"status": 400,
"message": "Voucher code already exists",
"data": null,
"pagination": null
}
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
}
}
POST /users/{userId}/addresses
Body:
{ "address": "456 Đường XYZ", "tag": "Công ty" }
{
"status": 201,
"message": "Address created successfully",
"data": {
"addressId": "a707",
"address": "456 Đường XYZ",
"tag": "Công ty"
},
"pagination": null
}
PUT /users/{userId}/addresses/{addressId}
Body:
{ "address": "789 Đường ABC", "tag": "Nhà riêng" }
{
"status": 200,
"message": "Address updated successfully",
"data": {
"addressId": "a707",
"address": "789 Đường ABC",
"tag": "Nhà riêng"
},
"pagination": null
}
POST /users/{userId}/addresses/{addressId}/default
Response:
{
"status": 200,
"message": "Address set as default",
"data": null,
"pagination": null
}
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
}
PUT /users/{userId}
Body:
{
"name": "Nguyen Van B",
"avatar": "https://example.com/avatar2.jpg",
"age": 26
}
{
"status": 200,
"message": "Profile updated successfully",
"data": {
"userId": "u123",
"name": "Nguyen Van B",
"avatar": "https://example.com/avatar2.jpg",
"age": 26
},
"pagination": null
}
POST /auth/reset-password
Body:
{ "email": "[email protected]" }
{
"status": 200,
"message": "Password reset email sent",
"data": null,
"pagination": null
}
{
"status": 400,
"message": "Email not found",
"data": null,
"pagination": null
}
PUT /auth/update-password
Body:
{ "token": "abc123xyz", "newPassword": "NewP@ssw0rd" }
{
"status": 200,
"message": "Password updated successfully",
"data": null,
"pagination": null
}
{
"status": 400,
"message": "Invalid or expired token",
"data": null,
"pagination": null
}
// 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()`]
}
// 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])
}
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.
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.
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).
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).
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.
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).
Đá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.
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.
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.
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
}
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.
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.
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.
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.
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…).
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ử.