Trang này dành cho người làm kỹ thuật cho tiệm. Chủ tiệm không cần đọc.
onduty.js là một tệp IIFE khoảng 16 KB (gzip), không phụ thuộc thư viện nào của trang, vẽ toàn bộ giao diện trong một Shadow DOM tại #onduty-root. CSS của trang không lọt vào widget và ngược lại. Tệp có defer nên không chặn tải trang; trên Next.js dùng strategy="lazyOnload".
Khi nạp, widget đọc data-tenant trên chính thẻ script, gọi GET /api/t/{tenant} để lấy cấu hình công khai, rồi vẽ nút. Cấu hình chỉ trả về cho các origin đã khai của tiệm (CORS), origin lạ nhận 403.
window.OnDuty.open(); // mở menu
window.OnDuty.close(); // đóng
window.OnDuty.openChat(); // mở thẳng trợ lý
window.OnDuty.openBooking(); // mở thẳng đặt lịch (tiệm có bật đặt lịch)
<div data-onduty-booking></div>
Có thẻ này ở đâu, OnDuty vẽ khung liên hệ (chip Zalo và gọi, tab chọn giờ, tab tin nhắn) vào đó, trong Shadow DOM riêng. window.OnDuty.openBooking() và mục "Đặt lịch" của nút góc phải cuộn tới khung này thay vì mở bản thu nhỏ. ?msg= điền sẵn ô tin nhắn, #need hoặc #form mở tab tin nhắn, #dat-lich cuộn tới khung. Trang chỉ cần khung mà không cần nút: thêm data-launcher="off" vào thẻ script. Trang có sẵn: https://onduty.goecomaz.com/b/{tenant}.
Dùng khi trang đã có nút riêng (thanh nút đáy trên điện thoại) và muốn nút đó mở OnDuty.
#onduty-root { bottom: 80px; } /* nhường chỗ cho thanh cookie */
Ẩn dưới một độ rộng màn hình là một thiết lập trong cấu hình tiệm (launcher.hideBelow), không cần CSS.
Thêm ?onduty-dry=1 vào URL trang. Mọi lượt gửi số trong trang đó được đánh dấu thử, không tới Telegram, form hay email.
| Điểm cuối | Việc nó làm |
|---|---|
GET /api/t/{tenant} |
Cấu hình công khai của tiệm: màu, nút, mục menu, lời chào. Không chứa hồ sơ, luật, nơi nhận số. |
POST /api/chat |
Thân { tenant, messages: [{role, content}], page?: {url, title, text} }. Trả { reply, offline }. Quá giới hạn trả offline: true với HTTP 200. |
POST /api/lead |
Thân { tenant, phone, name?, note?, turns?, page?, dry? }. Gửi số tới nơi nhận của tiệm. |
GET /api/booking/{tenant}/slots |
Giờ còn trống, gộp theo ngày (giờ Việt Nam): { ok, calendar, durationMin, days: { "2026-09-08": ["2026-09-08T11:00:00.000Z", ...] } }. Cache 60 giây. |
POST /api/booking/{tenant} |
Thân { slot, phone, name?, note?, page?, dry? }. Kiểm lại lịch trống ngay trước khi ghi; giờ vừa bị giữ trả 409 kèm reason: "slot-taken". |
Cả ba chỉ nhận request từ origin đã khai. Có ô honeypot hp trong hai request POST, bot điền vào là bị bỏ.
script-src ... https://onduty.goecomaz.com;
connect-src ... https://onduty.goecomaz.com;
Không có SDK npm, không có iframe, không có webhook cho tiệm (số đi thẳng tới Telegram, form hoặc email đã chọn). Cần gì khác, nhắn GoEcomaZ.