https://shop-api.unicode.vn
Nhiều endpoint yêu cầu xác thực JWT. Thêm token vào header Authorization:
Authorization: Bearer <jwt_token_của_bạn>
POST /auth/register
Tạo tài khoản người dùng mới.
Body của Request:
{
"fullName": "string",
"email": "string",
"password": "string",
"phone": "string"
}
Quy Tắc Kiểm Tra:
fullName: Ít nhất 2 ký tựemail: Định dạng email hợp lệpassword: Tối thiểu 6 ký tựphone: Định dạng số điện thoại hợp lệ (ví dụ: +84123456789)
Response:
{
"accessToken": "string",
"refreshToken": "string",
"user": {
"id": "string",
"fullName": "string",
"email": "string",
"phone": "string"
}
}
POST /auth/login
Đăng nhập với tài khoản đã có.
Body của Request:
{
"email": "string",
"password": "string"
}
Response:
{
"accessToken": "string",
"refreshToken": "string",
"user": {
"id": "string",
"fullName": "string",
"email": "string",
"phone": "string"
}
}
POST /auth/refresh
Làm mới token khi access token hết hạn.
Body của Request:
{
"refreshToken": "string"
}
Response:
{
"accessToken": "string",
"refreshToken": "string",
"user": {
"id": "string",
"fullName": "string",
"email": "string",
"phone": "string"
}
}
Response khi token không hợp lệ hoặc đã bị thu hồi:
{
"message": "Token không hợp lệ"
}
GET /auth/google/url
Trả về URL để frontend redirect user đến trang đăng nhập Google.
Response:
{
"url": "https://accounts.google.com/o/oauth2/v2/auth?..."
}
GET /auth/google/callback?code=...
Backend xử lý xác thực Google, tạo user nếu chưa có, sinh accessToken và refreshToken (JWT của hệ thống). Sau đó backend sẽ redirect về frontend với các token này qua URL:
/auth/google/success?accessToken=...&refreshToken=...
GET /auth/google/success?accessToken=...&refreshToken=...
Frontend gọi endpoint này để lấy thông tin user và xác thực token.
Response:
{
"user": {
"id": "string",
"fullName": "string",
"email": "string",
"phone": "string"
},
"accessToken": "string",
"refreshToken": "string"
}
Lưu ý:
POST /auth/logout
Đăng xuất và vô hiệu hóa token hiện tại.
Yêu Cầu Xác Thực: Có
Response:
{
"message": "Đăng xuất thành công"
}
Response khi token không hợp lệ:
{
"message": "Token không hợp lệ"
}
PATCH /profile/change-password
Thay đổi mật khẩu của người dùng hiện tại.
Yêu Cầu Xác Thực: Có
Body của Request:
{
"currentPassword": "string",
"newPassword": "string",
"confirmPassword": "string"
}
Quy Tắc Kiểm Tra:
currentPassword: Bắt buộc, mật khẩu hiện tạinewPassword: Tối thiểu 6 ký tựconfirmPassword: Phải giống với newPasswordResponse khi thành công:
{
"message": "Đổi mật khẩu thành công"
}
Response khi sai mật khẩu hiện tại:
{
"message": "Lỗi kiểm tra dữ liệu",
"errors": {
"currentPassword": "Mật khẩu hiện tại không đúng"
}
}
GET /countries
Lấy danh sách tất cả các quốc gia có sẵn.
Yêu Cầu Xác Thực: Không
Response:
{
"countries": [
{
"name": "string",
"code": "string"
}
]
}
GET /countries/:code
Lấy thông tin chi tiết của một quốc gia, bao gồm danh sách các tỉnh/thành phố.
Yêu Cầu Xác Thực: Không
Response:
{
"country": {
"name": "string",
"code": "string",
"states": ["string"]
}
}
GET /addresses
Lấy danh sách địa chỉ của người dùng hiện tại.
Yêu Cầu Xác Thực: Có
Response:
{
"addresses": [
{
"id": "string",
"name": "string",
"country": "string",
"address": "string",
"mobileNumber": "string",
"alternativeMobileNumber": "string",
"pincode": "string",
"city": "string",
"state": "string",
"isDefault": boolean
}
]
}
POST /addresses
Thêm một địa chỉ mới cho người dùng.
Yêu Cầu Xác Thực: Có
Body của Request:
{
"name": "string",
"country": "string",
"address": "string",
"mobileNumber": "string",
"alternativeMobileNumber": "string", // không bắt buộc
"pincode": "string",
"city": "string",
"state": "string",
"isDefault": boolean // không bắt buộc
}
Quy Tắc Kiểm Tra:
name: Ít nhất 2 ký tựcountry: Mã quốc gia hợp lệaddress: Ít nhất 5 ký tựmobileNumber: Định dạng số điện thoại hợp lệpincode: Ít nhất 4 ký tựcity: Không được để trốngstate: Phải là tỉnh/thành phố hợp lệ của quốc gia đã chọn
Response:
{
"address": {
"id": "string",
"name": "string",
"country": "string",
"address": "string",
"mobileNumber": "string",
"alternativeMobileNumber": "string",
"pincode": "string",
"city": "string",
"state": "string",
"isDefault": boolean
}
}
PUT /addresses/:addressId
Cập nhật thông tin của một địa chỉ.
Yêu Cầu Xác Thực: Có
Body của Request: Giống như khi thêm địa chỉ mới
Response: Giống như khi thêm địa chỉ mới
DELETE /addresses/:addressId
Xóa một địa chỉ.
Yêu Cầu Xác Thực: Có
Response:
{
"message": "Đã xóa địa chỉ thành công"
}
PATCH /addresses/:addressId/default
Đặt một địa chỉ làm địa chỉ mặc định.
Yêu Cầu Xác Thực: Có
Response:
{
"address": {
"id": "string",
"name": "string",
"country": "string",
"address": "string",
"mobileNumber": "string",
"alternativeMobileNumber": "string",
"pincode": "string",
"city": "string",
"state": "string",
"isDefault": true
}
}
GET /profile
Lấy thông tin hồ sơ của người dùng hiện tại.
Yêu Cầu Xác Thực: Có
Response:
{
"user": {
"id": "string",
"fullName": "string",
"email": "string",
"phone": "string",
"birthDate": "string (YYYY-MM-DD)"
}
}
PATCH /profile
Cập nhật thông tin hồ sơ của người dùng hiện tại.
Yêu Cầu Xác Thực: Có
Body của Request:
{
"fullName": "string", // không bắt buộc
"phone": "string", // không bắt buộc
"birthDate": "string" // không bắt buộc, định dạng: YYYY-MM-DD
}
Quy Tắc Kiểm Tra:
fullName: Ít nhất 2 ký tự (nếu có)phone: Định dạng số điện thoại hợp lệ (nếu có)birthDate: Ngày hợp lệ theo định dạng YYYY-MM-DD (nếu có)
Response:
{
"user": {
"id": "string",
"fullName": "string",
"email": "string",
"phone": "string",
"birthDate": "string (YYYY-MM-DD)"
}
}
GET /shopping-cart
Yêu cầu xác thực. Trả về danh sách sản phẩm trong giỏ hàng của user hiện tại.
Response:
{
"items": [
{
"productId": "string",
"name": "string",
"image": "string",
"avgRating": 4.5,
"reviewCount": 12,
"priceDiscount": 90000,
"price": 100000,
"brand": "string",
"warranty": "2 năm",
"shippingFee": 15000,
"quantity": 2,
"total": 180000
}
],
"total": 180000
}
POST /shopping-cart
Yêu cầu xác thực. Thêm sản phẩm vào giỏ hàng của user hiện tại.
Body của Request:
{
"productId": "string",
"quantity": 2
}
Response khi thành công:
{
"message": "Đã thêm vào giỏ hàng"
}
DELETE /shopping-cart
Yêu cầu xác thực. Xóa toàn bộ sản phẩm trong giỏ hàng của user.
Response:
{
"message": "Đã xóa toàn bộ giỏ hàng"
}
DELETE /shopping-cart/:productId
Yêu cầu xác thực. Xóa một sản phẩm khỏi giỏ hàng theo productId.
Response:
{
"message": "Đã xóa sản phẩm khỏi giỏ hàng"
}
PATCH /shopping-cart/:productId
Yêu cầu xác thực. Cập nhật số lượng sản phẩm trong giỏ hàng theo productId.
Body:
{
"quantity": 2
}
Response:
{
"message": "Đã cập nhật số lượng sản phẩm"
}
GET /cart-suggest/you-might-also-like
Yêu cầu xác thực. Trả về danh sách sản phẩm liên quan đến các sản phẩm trong giỏ hàng (cùng danh mục hoặc thương hiệu, loại trừ sản phẩm đã có trong giỏ).
Response:
{
"products": [
{
"_id": "string",
"name": "string",
"image": "string",
"price": 100000,
"priceDiscount": 90000,
"brand": "string",
"warranty": "2 năm",
"shippingFee": 15000,
"avgRating": 4.5,
"reviewCount": 12,
"category": "categoryId"
}
]
}
GET /cart-suggest/recommended
Trả về danh sách sản phẩm nổi bật, bán chạy, đánh giá cao nhất hệ thống.
Response:
{
"products": [
{
"_id": "string",
"name": "string",
"image": "string",
"price": 100000,
"priceDiscount": 90000,
"brand": "string",
"warranty": "2 năm",
"shippingFee": 15000,
"avgRating": 4.8,
"reviewCount": 120,
"category": "categoryId"
}
]
}
POST /orders
Yêu cầu xác thực. Đặt hàng từ giỏ hàng hiện tại của user.
Body của Request:
{
"addressId": "id địa chỉ nhận hàng (tùy chọn)"
}
Lưu ý:
addressId, hệ thống sẽ kiểm tra địa chỉ này có
thuộc user không và sử dụng địa chỉ đó.
addressId, hệ thống sẽ tự động lấy địa chỉ
mặc định của user.
Response khi thành công:
{
"message": "Đặt hàng thành công",
"order": {
"_id": "string",
"userId": "string",
"items": [
{
"productId": "string",
"name": "string",
"image": "string",
"price": 100000,
"priceDiscount": 90000,
"quantity": 2
}
],
"total": 180000,
"address": "Địa chỉ nhận hàng (theo addressId hoặc địa chỉ mặc định)",
"status": "pending",
"createdAt": "2025-10-26T12:00:00.000Z"
}
}
GET /orders
Yêu cầu xác thực. Trả về danh sách đơn hàng của user hiện tại.
Response:
{
"orders": [
{
"_id": "string",
"userId": "string",
"items": [ ... ],
"total": 180000,
"address": "Địa chỉ nhận hàng",
"status": "pending",
"createdAt": "2025-10-26T12:00:00.000Z"
}
]
}
GET /orders/:id
Yêu cầu xác thực. Trả về chi tiết một đơn hàng theo id.
Response:
{
"order": {
"_id": "string",
"userId": "string",
"items": [ ... ],
"total": 180000,
"address": "Địa chỉ nhận hàng",
"status": "pending",
"createdAt": "2025-10-26T12:00:00.000Z",
"trackingId": "TRKABC12345"
},
"trackingId": "TRKABC12345"
}
GET /orders/tracking/:trackingId
Yêu cầu xác thực. Trả về đơn hàng của user theo mã trackingId.
Response:
{
"order": {
"_id": "string",
"trackingId": "TRKABC12345",
"status": "pending"
// ... các trường khác của đơn hàng
}
}
DELETE /orders/:id
Yêu cầu xác thực. Chỉ hủy được đơn hàng của chính user khi trạng thái là 'pending'.
Response:
{
"message": "Đã hủy đơn hàng",
"order": {
"_id": "string",
"status": "cancelled"
// ... các trường khác của đơn hàng
}
}
GET /products/:id/reviews
Trả về danh sách review, đánh giá trung bình, tổng số lượt đánh giá, chi tiết tỉ lệ từng mức sao (1-5).
Response:
{
"reviews": [
{
"rating": 5,
"title": "Sản phẩm tuyệt vời!",
"content": "Chất lượng tốt, giao hàng nhanh.",
"user": {
"userId": "abc123",
"email": "[email protected]"
}
}
],
"avgRating": 4.7,
"total": 10,
"ratingStats": [
{ "star": 1, "count": 0, "percent": 0 },
{ "star": 2, "count": 1, "percent": 10 },
{ "star": 3, "count": 2, "percent": 20 },
{ "star": 4, "count": 2, "percent": 20 },
{ "star": 5, "count": 5, "percent": 50 }
]
}
POST /products/:id/reviews
Thêm đánh giá cho sản phẩm, bao gồm đánh giá sao, tiêu đề và nội dung.
Body của Request:
{
"rating": 5,
"title": "Sản phẩm tuyệt vời!",
"content": "Chất lượng tốt, giao hàng nhanh. Sẽ mua lại."
}
Response khi thành công:
{
"message": "Đánh giá đã được thêm",
"review": {
"rating": 5,
"title": "Sản phẩm tuyệt vời!",
"content": "Chất lượng tốt, giao hàng nhanh. Sẽ mua lại."
}
}
GET /products/:id/related
Trả về danh sách sản phẩm liên quan cùng chuyên mục, loại trừ sản phẩm hiện tại. Mỗi sản phẩm gồm:
Response:
{
"products": [
{
"name": "string",
"image": "string",
"price": 100000,
"priceDiscount": 90000,
"brand": "string",
"warranty": "2 năm",
"shippingFee": "miễn phí",
"avgRating": 4.5,
"reviewCount": 12,
"code": "SP1234"
}
]
}
GET /products/:id
Trả về thông tin chi tiết của một sản phẩm, bao gồm:
gallery)code)description)Response:
{
"product": {
"name": "string",
"image": "string",
"gallery": ["string", "string"],
"code": "SP123456",
"description": "Chi tiết sản phẩm...",
"price": 100000,
"priceDiscount": 90000,
"brand": "string",
"warranty": "2 năm",
"shippingFee": "miễn phí",
"avgRating": 4.5,
"reviewCount": 12,
"category": "categoryId",
"dealToday": false,
"createdAt": "2025-10-26T12:00:00.000Z"
}
}
GET /products
Trả về danh sách sản phẩm với các trường:
Bộ lọc (query params):
category: Theo chuyên mục (categoryId)dealToday: Sản phẩm deal hôm nay (true/false)minRating: Đánh giá sao tối thiểuminPrice, maxPrice: Khoảng giádiscount: Discount tối thiểu (phần trăm, ví dụ: 10, 20)
Phân trang (query params):
limit: Số sản phẩm mỗi trang (mặc định: 20)page: Trang hiện tại (mặc định: 1)
Sắp xếp (query param sort):
price_asc: Giá từ thấp đến caoprice_desc: Giá từ cao xuống thấpreview_avg: Trung bình reviewnewest: Hàng mới nhấtResponse:
{
"products": [
{
"name": "string",
"image": "string",
"price": 100000,
"priceDiscount": 90000,
"brand": "string",
"warranty": "2 năm",
"shippingFee": "miễn phí",
"avgRating": 4.5,
"reviewCount": 12
}
],
"pagination": {
"total": 100,
"page": 1,
"limit": 20,
"totalPages": 5
}
}
GET /categories/names
Lấy danh sách tên các danh mục sản phẩm.
Yêu Cầu Xác Thực: Không
Response:
{
"categories": ["string", "string", ...]
}
{
"message": "Lỗi kiểm tra dữ liệu",
"errors": {
"tênTrường": "Thông báo lỗi cho trường này"
}
}
{
"message": "Yêu cầu xác thực"
}
{
"message": "Không tìm thấy người dùng"
}
{
"message": "Thông báo mô tả lỗi"
}
API sử dụng các mã trạng thái sau:
200: Thành công201: Đã tạo thành công400: Yêu cầu không hợp lệ401: Chưa xác thực404: Không tìm thấy500: Lỗi máy chủ