{"openapi":"3.0.3","info":{"title":"NVD Shop — Shop API","version":"2.0.0","description":"# API riêng của shop\n\nTài liệu này mô tả **toàn bộ REST API do shop tự host** (Telegram bot + web admin + PayOS).\nKhông phụ thuộc swagger bên thứ ba.\n\n## Nhóm API cần để vận hành\n\n| Nhóm | Bắt buộc? | Mục đích |\n|---|---|---|\n| **Public catalog** | Có | Bot/khách xem SP, danh mục, giá bán |\n| **Admin auth** | Có | Đăng nhập web / API key quản trị |\n| **Users + ví** | Có | Cộng/trừ số dư, quản lý khách |\n| **Orders** | Có | Lịch sử đơn, xem account đã giao |\n| **Local products + stock** | Có* | Bán kho riêng (thêm/sửa/xóa SP + account) |\n| **Supplier catalog override** | Tuỳ chọn | Ẩn/sửa giá SP lấy từ nguồn NCC |\n| **PayOS webhook + payments** | Có* | TT online → tự giao hàng / nạp ví |\n| **Settings** | Có | Tên shop, markup, hướng dẫn nạp |\n\n\\* Local stock **hoặc** nguồn NCC; PayOS nếu bán online không dùng ví tay.\n\n## Auth\n\n- **Public**: không key\n- **Admin**: header `X-API-Key: <ADMIN_API_KEY>` hoặc `Authorization: Bearer <ADMIN_API_KEY>`\n- **Web**: cookie sau `POST /api/admin/login`\n- **PayOS webhook**: public, verify HMAC checksum (server-side)\n\n## Luồng vận hành chính\n\n1. **Catalog** → `GET /api/products` (local + NCC + markup/override)\n2. **Thanh toán PayOS** → bot tạo link → khách CK → `POST /api/payos/webhook` (hoặc nút kiểm tra)\n3. **Giao hàng** → trừ kho local hoặc gọi NCC purchase → gửi account Telegram\n4. **Nạp ví** → PayOS type deposit → cộng balance → mua bằng ví\n5. **Admin** → credit user, sửa SP local, xem orders/payments","contact":{"name":"NVD Shop"}},"servers":[{"url":"https://shop-nduc9.duckdns.org","description":"Server hiện tại"},{"url":"http://localhost:3789","description":"Local dev"}],"tags":[{"name":"Health","description":"Trạng thái hệ thống"},{"name":"Catalog","description":"Sản phẩm & danh mục (public)"},{"name":"PayOS","description":"Webhook & trạng thái cổng thanh toán"},{"name":"Admin Auth","description":"Đăng nhập admin"},{"name":"Admin Dashboard","description":"Thống kê tổng quan"},{"name":"Admin Users","description":"Khách hàng & ví shop"},{"name":"Admin Orders","description":"Đơn hàng đã giao"},{"name":"Admin Payments","description":"Giao dịch PayOS"},{"name":"Admin Local Products","description":"Kho SP tự quản lý"},{"name":"Admin Supplier Overrides","description":"Chỉnh hiển thị SP nguồn NCC"},{"name":"Admin Settings","description":"Cấu hình shop"}],"paths":{"/api/health":{"get":{"tags":["Health"],"summary":"Health check","description":"Kiểm tra server sống. Dùng cho monitor / uptime.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"},"example":{"success":true,"shop":"NVD Shop","uptime":120.5,"timestamp":"2026-07-14T10:00:00.000Z"}}}}}}},"/api/products":{"get":{"tags":["Catalog"],"summary":"Danh sách sản phẩm bán","description":"Trả về catalog đã merge:\n- SP **local** (kho shop)\n- SP **nguồn NCC** (đã apply markup + override ẩn/giá/tên)\n\nBot Telegram và client public chỉ cần endpoint này + categories.","parameters":[{"name":"category","in":"query","schema":{"type":"string"},"description":"Lọc category (vd: grok, claude, local)","example":"grok"},{"name":"source","in":"query","schema":{"type":"string","enum":["local","canboso"]},"description":"Lọc nguồn: local | canboso (NCC)"},{"name":"refresh","in":"query","schema":{"type":"boolean"},"description":"Bỏ cache, load lại"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductsListResponse"}}}}}}},"/api/products/{id}":{"get":{"tags":["Catalog"],"summary":"Chi tiết 1 sản phẩm","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID SP local (loc_...) hoặc ID nguồn NCC"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductDetailResponse"}}}},"404":{"description":"Không tìm thấy"}}}},"/api/categories":{"get":{"tags":["Catalog"],"summary":"Danh mục sản phẩm","description":"Dùng cho menu bot (nhóm theo emoji/category).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoriesResponse"}}}}}}},"/api/payos/status":{"get":{"tags":["PayOS"],"summary":"Trạng thái PayOS","description":"Public — kiểm tra đã cấu hình keys + PUBLIC_BASE_URL chưa.","responses":{"200":{"description":"OK","content":{"application/json":{"example":{"success":true,"enabled":true,"publicBaseUrl":null,"webhookPath":"/api/payos/webhook"}}}}}}},"/api/payos/webhook":{"post":{"tags":["PayOS"],"summary":"Webhook nhận kết quả thanh toán PayOS","description":"**Bắt buộc** để tự động giao hàng / nạp ví.\n\nPayOS POST khi khách thanh toán thành công.\nServer verify signature → fulfill đơn (product) hoặc cộng ví (deposit).\n\nURL đăng ký: `{PUBLIC_BASE_URL}/api/payos/webhook`","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayosWebhookBody"}}}},"responses":{"200":{"description":"Đã nhận (kể cả business error đã log)"},"400":{"description":"Signature không hợp lệ"}}}},"/api/openapi.json":{"get":{"tags":["Health"],"summary":"OpenAPI JSON (spec này)","responses":{"200":{"description":"OpenAPI 3.0 document"}}}},"/api/admin/login":{"post":{"tags":["Admin Auth"],"summary":"Đăng nhập web admin (set cookie)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"password":{"type":"string","example":"admin123"}}}}}},"responses":{"200":{"description":"OK + Set-Cookie admin_session"},"401":{"description":"Sai mật khẩu"}}}},"/api/admin/logout":{"post":{"tags":["Admin Auth"],"summary":"Đăng xuất","responses":{"200":{"description":"OK"}}}},"/api/admin/me":{"get":{"tags":["Admin Auth"],"summary":"Kiểm tra session / API key","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Authenticated"},"401":{"description":"Unauthorized"}}}},"/api/admin/dashboard":{"get":{"tags":["Admin Dashboard"],"summary":"Dashboard thống kê","description":"Users, doanh thu, đơn, tồn local, PayOS paid, số dư ví NCC (nếu có).","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"OK"}}}},"/api/admin/users":{"get":{"tags":["Admin Users"],"summary":"Danh sách users","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"q","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":100}}],"responses":{"200":{"description":"OK"}}},"post":{"tags":["Admin Users"],"summary":"Tạo user (Telegram userId)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer","example":8051440849},"username":{"type":"string"},"firstName":{"type":"string"},"balance":{"type":"number","example":0},"note":{"type":"string"}}}}}},"responses":{"200":{"description":"Created"},"409":{"description":"Đã tồn tại"}}}},"/api/admin/users/{userId}":{"get":{"tags":["Admin Users"],"summary":"Chi tiết user + orders/deposits gần đây","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK"},"404":{"description":"Not found"}}},"put":{"tags":["Admin Users"],"summary":"Sửa user (username, note, ...)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Admin Users"],"summary":"Xóa user","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK"}}}},"/api/admin/users/{userId}/credit":{"post":{"tags":["Admin Users"],"summary":"Cộng tiền ví shop","description":"Bắt buộc nếu bán bằng ví (không PayOS). Có thể notify Telegram.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","example":50000},"note":{"type":"string","example":"CK NAP"}}}}}},"responses":{"200":{"description":"OK"}}}},"/api/admin/users/{userId}/debit":{"post":{"tags":["Admin Users"],"summary":"Trừ tiền ví shop","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number"},"note":{"type":"string"}}}}}},"responses":{"200":{"description":"OK"}}}},"/api/admin/users/{userId}/balance":{"put":{"tags":["Admin Users"],"summary":"Set số dư tuyệt đối","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["balance"],"properties":{"balance":{"type":"number","example":100000},"note":{"type":"string"}}}}}},"responses":{"200":{"description":"OK"}}}},"/api/admin/orders":{"get":{"tags":["Admin Orders"],"summary":"Danh sách đơn hàng","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["success","failed"]}},{"name":"userId","in":"query","schema":{"type":"integer"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"OK"}}}},"/api/admin/orders/{id}":{"get":{"tags":["Admin Orders"],"summary":"Chi tiết đơn (kèm deliveredAccounts)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"404":{"description":"Not found"}}},"delete":{"tags":["Admin Orders"],"summary":"Xóa đơn (log)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/api/admin/deposits":{"get":{"tags":["Admin Users"],"summary":"Lịch sử cộng/trừ ví","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"query","schema":{"type":"integer"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50}}],"responses":{"200":{"description":"OK"}}}},"/api/admin/deposits/{id}":{"delete":{"tags":["Admin Users"],"summary":"Xóa log giao dịch ví (không hoàn tiền)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/api/admin/payments":{"get":{"tags":["Admin Payments"],"summary":"Danh sách thanh toán PayOS","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["pending","paid","failed","cancelled"]}},{"name":"type","in":"query","schema":{"type":"string","enum":["product","deposit"]}},{"name":"userId","in":"query","schema":{"type":"integer"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50}}],"responses":{"200":{"description":"OK"}}}},"/api/admin/payments/{orderCode}":{"get":{"tags":["Admin Payments"],"summary":"Chi tiết 1 payment PayOS","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"orderCode","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK"},"404":{"description":"Not found"}}}},"/api/admin/payments/{orderCode}/retry-fulfill":{"post":{"tags":["Admin Payments"],"summary":"Retry giao hàng sau khi đã PAID","description":"Dùng khi webhook đã nhận tiền nhưng fulfill lỗi (hết kho NCC, ...).","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"orderCode","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"force":{"type":"boolean","description":"Bắt fulfill lại dù đã có fulfilledAt"}}}}}},"responses":{"200":{"description":"OK"}}}},"/api/admin/payos/confirm-webhook":{"post":{"tags":["PayOS"],"summary":"Đăng ký webhook URL với PayOS","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"baseUrl":{"type":"string","description":"Override PUBLIC_BASE_URL","example":"https://shop.example.com"}}}}}},"responses":{"200":{"description":"OK"},"400":{"description":"URL invalid / thiếu config"}}}},"/api/admin/products":{"get":{"tags":["Admin Local Products","Admin Supplier Overrides"],"summary":"Catalog admin (local + NCC raw + overrides)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"OK"}}}},"/api/admin/local-products":{"get":{"tags":["Admin Local Products"],"summary":"List SP local","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"OK"}}},"post":{"tags":["Admin Local Products"],"summary":"Tạo SP local","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["name","price"],"properties":{"name":{"type":"string","example":"Grok Super 1 tháng"},"price":{"type":"number","example":140000},"category":{"type":"string","example":"grok"},"description":{"type":"string"},"usageGuide":{"type":"string"},"active":{"type":"boolean","default":true},"displayOrder":{"type":"integer","default":0}}}}}},"responses":{"200":{"description":"Created"}}}},"/api/admin/local-products/{id}":{"get":{"tags":["Admin Local Products"],"summary":"Chi tiết SP local (+ full stock lines)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}},"put":{"tags":["Admin Local Products"],"summary":"Sửa SP local","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Admin Local Products"],"summary":"Xóa SP local + toàn bộ kho","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/api/admin/local-products/{id}/stock":{"post":{"tags":["Admin Local Products"],"summary":"Thêm stock (mỗi dòng 1 account)","description":"Format gợi ý: `user|pass` mỗi dòng. Khi bán, bot parse và gửi khách.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"lines":{"oneOf":[{"type":"string","example":"a@gmail.com|pass1\nb@gmail.com|pass2"},{"type":"array","items":{"type":"string"}}]}}}}}},"responses":{"200":{"description":"OK"}}}},"/api/admin/local-products/{id}/stock/{accountId}":{"delete":{"tags":["Admin Local Products"],"summary":"Xóa 1 dòng stock","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"accountId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/api/admin/local-products/{id}/stock/clear-sold":{"post":{"tags":["Admin Local Products"],"summary":"Xóa các account đã bán khỏi kho","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/api/admin/canboso-products/{id}":{"put":{"tags":["Admin Supplier Overrides"],"summary":"Override SP nguồn NCC (ẩn / đổi tên / giá / mô tả)","description":"Không xóa được SP gốc từ NCC (API buyer chỉ list/purchase).\nOverride lưu local: hidden, customName, customPrice, customDescription.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"hidden":{"type":"boolean"},"customName":{"type":"string"},"customPrice":{"type":"number"},"customDescription":{"type":"string"}}}}}},"responses":{"200":{"description":"OK"}}}},"/api/admin/canboso-products/{id}/override":{"get":{"tags":["Admin Supplier Overrides"],"summary":"Xem override hiện tại","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}},"delete":{"tags":["Admin Supplier Overrides"],"summary":"Xóa override → về giá/tên mặc định NCC","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/api/admin/supplier/balance":{"get":{"tags":["Admin Supplier Overrides"],"summary":"Số dư ví nguồn NCC (nếu dùng dropship)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"OK"}}}},"/api/admin/settings":{"get":{"tags":["Admin Settings"],"summary":"Đọc cấu hình shop","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"OK"}}},"put":{"tags":["Admin Settings"],"summary":"Cập nhật tên shop / markup / hướng dẫn nạp","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"shopName":{"type":"string"},"markupPercent":{"type":"number","example":10},"depositInstructions":{"type":"string"}}}}}},"responses":{"200":{"description":"OK"}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"ADMIN_API_KEY trong .env"},"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer ADMIN_API_KEY"}},"schemas":{"HealthResponse":{"type":"object","properties":{"success":{"type":"boolean"},"shop":{"type":"string"},"uptime":{"type":"number"},"timestamp":{"type":"string","format":"date-time"}}},"ShopProduct":{"type":"object","description":"Sản phẩm hiển thị cho khách (đã merge markup/override)","properties":{"id":{"type":"string","example":"loc_xxx hoặc 6a55f40b..."},"name":{"type":"string"},"category":{"type":"string","example":"grok"},"categoryLabel":{"type":"string","example":"🚀 Grok"},"description":{"type":"string"},"usageGuide":{"type":"string"},"price":{"type":"number","example":140000},"priceText":{"type":"string","example":"140.000 ₫"},"costPrice":{"type":"number","description":"Giá gốc NCC (nếu có)"},"available":{"type":"number","nullable":true,"example":25},"source":{"type":"string","enum":["local","canboso"]},"requiresCustomerEmail":{"type":"boolean"},"image":{"type":"string","nullable":true}}},"ProductsListResponse":{"type":"object","properties":{"success":{"type":"boolean"},"markupPercent":{"type":"number"},"total":{"type":"integer"},"categories":{"type":"array","items":{"type":"string"}},"products":{"type":"array","items":{"$ref":"#/components/schemas/ShopProduct"}}}},"ProductDetailResponse":{"type":"object","properties":{"success":{"type":"boolean"},"product":{"$ref":"#/components/schemas/ShopProduct"}}},"CategoriesResponse":{"type":"object","properties":{"success":{"type":"boolean"},"categories":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"count":{"type":"integer"}}}}}},"PayosWebhookBody":{"type":"object","properties":{"code":{"type":"string","example":"00"},"desc":{"type":"string"},"success":{"type":"boolean"},"data":{"type":"object","properties":{"orderCode":{"type":"integer"},"amount":{"type":"integer"},"description":{"type":"string"},"reference":{"type":"string"},"transactionDateTime":{"type":"string"},"currency":{"type":"string","example":"VND"},"paymentLinkId":{"type":"string"},"code":{"type":"string"},"desc":{"type":"string"}}},"signature":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string"}}}}}}