Giáo trình này viết cho người ngoài: bạn dựng hệ của chính bạn, bằng tài khoản của bạn. Không có chỗ nào bắt bạn xin quyền của tôi. Mọi thông tin riêng (webhook, tài khoản ngân hàng, mã tài khoản Cloudflare) đều để trống dạng
<…>cho bạn tự điền.Hệ gốc đang chạy thật với 4 trang bán hàng, tiền về tài khoản mỗi ngày. Chỗ nào có dấu ⚠️ là những lần tôi đã trả giá - đọc kỹ vào, đó là phần đắt nhất trong đây.
Tài liệu này trộn hai loại kiến thức, và bạn có quyền biết mình đang đọc loại nào:
🔵 ĐÃ CHẠY THẬT - tôi tự tay làm, có ngày tháng, có lần hỏng và cách chữa. Phần lớn tài liệu nằm ở mức này. Cứ làm theo.
🟡 CHƯA THỬ - TÌM HIỂU THÊM - tôi chưa gặp tình huống này bao giờ. Nội dung tra từ tài liệu chính thức, ghi rõ nguồn để bạn tự kiểm. Đừng coi đó là kinh nghiệm của tôi.
Không dán nhãn thì mặc định là 🔵.
Tôi tách ra vậy vì tài liệu kỹ thuật mà nói như đúng rồi ở đúng chỗ tác giả chưa từng làm thì khó chịu lắm - bạn làm theo, hỏng, mà chả hiểu hỏng vì đâu. Chỗ nào tôi không biết thì tôi nói là không biết. Đổi lại, chỗ nào tôi bảo đã chạy thật thì bạn tin được.
Bạn bán một sản phẩm. Khách thấy quảng cáo, bấm vào trang, điền đơn. Xong xuôi rồi thì bình thường nó diễn ra thế này:
Cái hệ này tôi làm ra để bỏ hết đoạn chép tay đó đi. Khách bấm một phát là:
Khách bấm ĐẶT HÀNG
│
├─► Lưu vào cơ sở dữ liệu của bạn, sinh MÃ ĐƠN duy nhất (vd CMC7)
├─► Ghi một dòng vào Google Sheet ──► người đóng gói mở Sheet là gói được ngay
├─► Gửi email cho khách (kèm mã QR chuyển khoản ĐÚNG SỐ TIỀN, ĐÚNG MÃ ĐƠN)
└─► Đẩy khách sang trang cảm ơn
Không ai phải gõ lại gì hết. Tiền hạ tầng thì 0đ, tới lúc đông thật mới phải lo (xem 1.1).
Tôi trả lời thẳng, vì cái này với bạn quan trọng hơn một cái bảng so sánh cho oai.
Lý do duy nhất: tôi có trợ lý AI dựng hộ.
Không có Claude Code thì tôi xài nền tảng đóng gói từ lâu rồi, chả đắn đo một giây. Tôi không phải dân lập trình, không kiến thức, không kinh nghiệm, và cái worker trong tài liệu này bảo tôi tự ngồi viết thì chịu chết.
Mà tôi cũng không định đi học. Tôi làm những việc có lợi cho chuyện chia sẻ giá trị của mình, còn có trợ lý AI rồi thì học lập trình nó tụt xuống hàng thứ yếu, không còn là cái cửa bắt buộc phải qua nữa.
Cái này tôi tin chắc, và nó định hình luôn cách tôi viết cả tài liệu này.
Muốn xài trợ lý AI ra kết quả, bạn buộc phải có hai thứ:
- Biết mình muốn gì, chi tiết vào. Không phải kiểu "làm cho tôi cái trang bán hàng", mà là biết cái trang đó phải gỡ nỗi lăn tăn nào của khách, ở chỗ nào, bằng câu gì.
- Kiểm được trợ lý - nhìn vào thứ nó vừa làm ra là biết đúng ý mình hay chưa.
Thiếu thứ nhất, bạn nhận về một cái trang chung chung, đúng kỹ thuật mà chả bán được hàng. Thiếu thứ hai còn nguy hơn: trợ lý làm sai mà bạn không biết, cứ tưởng xong rồi, y như cái vụ đơn hàng đổ sai cột mà hệ thống vẫn báo thành công ở mục 4.
Hai thứ này không ai làm hộ bạn được, kể cả AI. Nhưng nó cũng chả bắt bạn phải biết code: thứ nhất ra từ chuyện bạn bán hàng thật nên hiểu khách vướng ở đâu, thứ hai chỉ cần bạn chịu mở ra xem thay vì tin lời.
Nên tài liệu này chỗ nào cũng có bước nghiệm thu, nói rõ kiểm bằng cách gì, nhìn vào đâu. Không phải cẩn thận thừa đâu - cái khâu kiểm đó là phần bạn buộc phải tự giữ, không giao cho ai được, kể cả AI.
Và đó cũng là lý do bộ này không bán cho bạn một cuốn PDF, mà bán một bộ hướng dẫn để trợ lý trên máy bạn dẫn bạn làm. Chứ nếu bạn phải tự gõ từng dòng thì tôi cũng chả phải người giúp được bạn, tôi có gõ được dòng nào đâu.
🟡 CHƯA THỬ. Tôi chưa từng xài Ladipage, Sapo, Haravan hay bất kỳ nền tảng nào, nên tôi không so sánh được, và cũng chả định giả vờ so sánh. Ai bảo bạn nền tảng X không làm được việc Y mà chưa mở ra xem thử thì cứ nghi ngờ đi, kể cả người đó là tôi.
Tôi chỉ nói được về hệ của mình. Mấy thứ dưới đây là cái tôi tự kiểm được, không phải chê ai:
| Thứ | 🔵 Hệ này |
|---|---|
| Chi phí hạ tầng | 0đ, kể cả khi có nhiều trang. Chỉ tốn tiền tên miền |
| Mã QR | mang mã đơn riêng + số tiền, số tiền lấy từ máy chủ nên khách không sửa được |
| Sửa nội dung | sửa file rồi đẩy lên, cỡ 20 giây |
| Dữ liệu đơn | nằm trong cơ sở dữ liệu của bạn, muốn làm gì thì làm |
Còn nền tảng đóng gói có làm được từng ấy không, giá bao nhiêu, thì bạn tự tra. Tra xong thấy nó đủ dùng cho bạn thì cứ dùng nó, đừng vì trót mua tài liệu này mà cố. Tôi thà bạn chọn đúng còn hơn bạn xài đồ của tôi rồi thấy phí thời gian.
Nói trước cho đỡ mất thời gian của nhau:
┌──────────────────────────────────────────────────────────────────┐
│ [1] TRANG (Cloudflare Pages) │
│ index.html - trang bán hàng, có form đặt đơn │
│ cam-on-cod.html - trang cảm ơn cho đơn ship COD │
│ cam-on-qr.html - trang cảm ơn cho đơn chuyển khoản │
└────────────────────────────┬─────────────────────────────────────┘
│ khách bấm gửi → POST /api/order
┌────────────────────────────▼─────────────────────────────────────┐
│ [2] MÁY CHỦ NHỎ (_worker.js - Cloudflare Worker) │
│ • kiểm tra dữ liệu (tên, SĐT, email, địa chỉ) │
│ • cấp số đơn, sinh mã CK (vd CMC7) │
│ • ghi vào [3], rồi bắn sang [4] │
│ • phục vụ ảnh QR ở /qr/CMC7.png │
└──────┬────────────────────────────────────┬──────────────────────┘
│ │
┌──────▼───────────────────┐ ┌────────────▼─────────────────────┐
│ [3] CƠ SỞ DỮ LIỆU (D1) │ │ [4] MAKE.COM (tự động hoá) │
│ bảng orders │ │ Webhook → Router → 3 nhánh: │
│ bảng counters │ │ • ghi Google Sheet │
│ bảng products │ │ • email "đã nhận đơn" │
│ │ │ • email "mời chuyển khoản" │
└──────────────────────────┘ └──────────────────────────────────┘
Thuật ngữ, giải thích một lần rồi thôi:
Luật 1: Mọi sale page dùng CHUNG một scenario Make + một Google Sheet. Trang thứ hai, thứ mười cũng bắn vào đúng cái webhook đó, vì chúng đều là đơn hàng: cùng loại dữ liệu, cùng đích đến. Tách ra là bạn ngồi bảo trì 10 bản sao của cùng một luồng.
Luật 2: Việc KHÁC LOẠI thì phải có scenario riêng. Trang tặng quà miễn phí (lead page) không phải đơn hàng → webhook riêng, scenario riêng.
⚠️ Tôi từng nhét một sự kiện lạ vào webhook đơn hàng (18/07/2026). Payload không khớp nhánh nào → Make báo "Validation failed" → Make tự tắt scenario → đơn hàng ngừng chảy. Một lỗi bên luồng quà tặng mà làm chết luồng tiền thì không chấp nhận được.
Ranh giới nằm ở loại việc, chứ không phải cứ thấy sự kiện mới là tách.
Làm hết phần này trước khi gõ dòng lệnh đầu tiên. Mất khoảng 60–90 phút nếu chưa có gì.
| Dịch vụ | Dùng làm gì | Bản miễn phí đủ tới đâu |
|---|---|---|
| Cloudflare | chứa trang + máy chủ nhỏ + cơ sở dữ liệu | 100.000 lượt gọi/ngày, 5 triệu dòng đọc D1/ngày - thừa sức cho vài nghìn đơn/tháng |
| Make.com | nối webhook → Sheet → email | 1.000 thao tác/tháng. Mỗi đơn tốn ~3 thao tác ⇒ khoảng 330 đơn/tháng. Vượt thì nâng gói |
| Google (Gmail + Sheets) | gửi email cho khách + bảng đơn | miễn phí |
| Tên miền | địa chỉ trang, vd sanpham.tenban.com |
⚠️ thứ duy nhất tốn tiền. Giá tuỳ đuôi - xem 1.4 |
| Meta Business (tuỳ chọn) | Pixel để chạy quảng cáo sau này | miễn phí |
| Google Analytics 4 (tuỳ chọn) | đo lưu lượng | miễn phí |
⚠️ Mua tên miền rồi trỏ nameserver về Cloudflare. Không làm bước này thì phần gắn tên miền ở 3.7 sẽ vướng. Mua ở đâu cũng được, miễn đổi được nameserver.
Bắt buộc:
winget install OpenJS.NodeJS.LTS (Windows 10/11 có sẵn), hoặc tải bộ cài .msi ở
nodejs.org rồi bấm Next → Next → Finish. Cài xong khởi động lại terminal/app một lần.
⛔ Đừng tải bản .zip giải nén tay vào một thư mục — kiểu đó không tự vào PATH, buổi sau lại
"không tìm thấy node", rất phiền (nhất là khi làm cùng trợ lý AI: phiên sau nó không thấy node).npm i -g wrangler
⚠️ Nếu npm báo đã bỏ qua install script của esbuild/workerd thì kệ nó - vô hại, wrangler
vẫn chạy. (Đừng thêm --ignore-scripts: đó là mẹo cũ cho kiểu cài giải nén, giờ không cần.)wrangler login
Nó mở trình duyệt cho bạn bấm đồng ý. Token lưu lại, không phải đăng nhập lại.pip install pillow opencv-python.Nếu bạn làm cùng trợ lý AI - cả hai thứ dưới đây đều BẮT BUỘC, không phải "nên có":
Nhìn danh sách 10 bước triển khai thì tưởng toàn dòng lệnh. Không phải. Hai bước khó nhất lại là thao tác chuột thuần, không có lệnh nào thay thế:
| Bước | Làm ở đâu | Có lệnh thay thế không |
|---|---|---|
| Bước 1 - dựng scenario Make | canvas Make.com | ❌ không. Kéo thả module, đặt bộ lọc, gõ nội dung email |
| Bước 10 - gắn tên miền | dashboard Cloudflare (phần DNS) | ❌ không. Token của wrangler không có quyền DNS |
| Sửa tiêu đề / định dạng Google Sheet | Google Sheets | ❌ không |
Thiếu tiện ích này thì trợ lý mù hoàn toàn ở ba chỗ đó: không nhìn thấy màn hình, không biết bạn đang đứng ở đâu, chỉ đọc hướng dẫn chay cho bạn nghe. Mà đây đúng là ba chỗ người mới dễ lạc nhất.
Có nó thì trợ lý nhìn được màn hình, chỉ đúng nút, và tự kiểm lại kết quả - quan trọng nhất là cái cuối: nó đọc lại được cái bạn vừa bấm có ăn không.
Canvas của Make là ảnh vector, không phải trang web bình thường:
Sửa chữ trong ô nội dung email của Make:
< của thẻ, hoặc ăn
lẹm chữ bên cạnh. Cách chắc: double-click vào một TỪ để neo đúng biên từ, rồi Shift+← /
Shift+→ dịch từng ký tự cho khít.Google Sheets: gõ tiếng Việt có dấu qua trình duyệt bị rơi dấu → gõ tiêu đề cột bằng chữ không dấu. (Lạ là gõ trong Make thì dấu vẫn đúng - nên đừng suy từ chỗ này sang chỗ kia.)
Dashboard Cloudflare: có bước kiểm tra bot khoảng 5 giây lúc vào, cứ chờ. Và ở phần DNS phải chọn Type = CNAME TRƯỚC rồi mới điền Name + Target - đổi Type sau làm ô Target render lại, mất chữ vừa gõ.
Mở Chrome đúng tài khoản mà vẫn báo mất kết nối: thủ phạm hay gặp là tiện ích tự cập nhật ngay dưới chân ứng dụng đang chạy. Cách chữa: khởi động lại ứng dụng Claude - không phải khởi động lại Chrome. Nghe ngược đời nhưng đúng.
Trong tài liệu này bạn sẽ thấy ví dụ dạng sanpham.tenban.com. Đó chỉ là ví dụ. Hệ thống này
không quan tâm đuôi tên miền là gì - .vn, .com, .net, .store, .shop đều chạy như nhau.
Chỗ nào trong tài liệu ghi đuôi cụ thể, bạn cứ thay bằng tên miền của bạn.
Đây mới là thứ quyết định, không phải cái đuôi. Cloudflare Pages chỉ gắn được tên miền riêng khi nameserver (máy chủ tên miền - nơi quyết định "tên miền này trỏ đi đâu") của tên miền đó đã trỏ về Cloudflare.
Cách làm, một lần duy nhất cho cả tên miền:
⚠️ Kiểm được điều này TRƯỚC KHI MUA, đặc biệt với tên miền .vn: hỏi thẳng nơi bán -
"tôi có tự đổi nameserver sang Cloudflare được không?" Phần lớn nhà đăng ký cho phép, nhưng đây là
thứ phải hỏi trước thay vì mua xong mới phát hiện vướng. Chưa hỏi được thì đừng đoán, cũng đừng
tin tôi - hỏi nơi bán.
Tên miền phụ (subdomain) là phần đứng trước, ví dụ sanpham trong sanpham.tenban.com.
Vì sao: một tên miền đẻ ra bao nhiêu tên miền phụ cũng được, hoàn toàn miễn phí. Nghĩa là trang bán hàng thứ hai, thứ năm của bạn không tốn thêm đồng tên miền nào - chỉ cần thêm một bản ghi DNS. Đây là chỗ tiết kiệm lớn mà nhiều người bỏ lỡ vì quen nghĩ "một trang một tên miền".
Đặt tên miền phụ dễ nhớ, đọc lên là biết bán gì: chaico, kemtay, bbcream.
Muốn dùng thẳng tên miền gốc (
tenban.com, không có phần đứng trước) thì vẫn được, nhưng theo chuẩn DNS thì gốc không đặt được bản ghi CNAME. Cloudflare có cơ chế riêng xử lý việc đó nên nó chạy - miễn là DNS đang ở Cloudflare. Một lý do nữa để làm xong bước nameserver ở trên. Dù vậy tôi vẫn khuyên để dành tên miền gốc cho một trang giới thiệu chung, đừng gán cho một sản phẩm cụ thể - gán rồi mà sau này ngừng bán món đó thì tên miền đẹp nhất của bạn trỏ vào chỗ chết. (Nói cho công bằng: tôi cũng chưa dựng trang giới thiệu chung đó. Đây là khuyên, không phải khoe.)
Giá chênh nhau rất nhiều theo đuôi: đuôi quốc tế (.com, .net, .store) thường rẻ hơn hẳn
đuôi .vn. Và gần như đuôi nào cũng có giá năm đầu rẻ, năm sau tăng - hãy xem giá gia hạn chứ
đừng chỉ xem giá lần mua đầu.
⚠️ Mọi con số giá trong tài liệu này chỉ để bạn hình dung mức chi. Kiểm giá thật tại nơi bán vào đúng lúc bạn mua.
🟡 CHƯA THỬ. Tôi xài đúng một ngân hàng (Techcombank) cho cả 4 trang và nó chạy ngon. Chưa thử ngân hàng nào khác bao giờ, cũng chưa gặp ca hỏng nào ở khâu này. Phần dưới là tôi tra tài liệu, không phải kinh nghiệm.
Cả luồng chuyển khoản của hệ này dựa vào VietQR - chuẩn mã QR chuyển khoản của Napas, dùng chung cho các ngân hàng tham gia dịch vụ Napas 247. Ngân hàng không nằm trong danh sách đó thì mã QR không tạo được, và bạn mất toàn bộ phần tự động của luồng chuyển khoản.
Tính tới các công bố của Napas, mạng lưới này đã mở rộng tới khoảng 40 ngân hàng, gồm gần như mọi ngân hàng phổ thông ở Việt Nam: Vietcombank, VietinBank, BIDV, Agribank, Techcombank, MB, ACB, Sacombank, VPBank, TPBank, VIB, MSB, HDBank, SHB, OCB, Eximbank, SeABank… ⚠️ Danh sách này thay đổi theo thời gian (Napas thêm ngân hàng nhiều đợt), nên đừng học thuộc - hãy tự kiểm.
Đây là phần đáng tin nhất trong mục này, vì bạn tự chứng minh chứ không tin lời ai:
vietqr.io, tạo thử một mã QR với ngân hàng + số tài khoản của bạn, số tiền để 2.000đ.⚠️ Quét bằng app ngân hàng, đừng quét bằng camera thường. Camera đọc ra một chuỗi ký tự loằng ngoằng và bạn không kết luận được gì.
Hai đường, cả hai tôi đều chưa thử:
Tên chủ tài khoản trong cấu hình phải VIẾT HOA, KHÔNG DẤU. Có dấu là mã QR sinh ra sai và app ngân hàng đọc không đúng tên.
🟡 PHẦN HẠN MỨC: CHƯA THỬ. Tôi dùng một tài khoản Gmail thường, lượng đơn của tôi chưa bao giờ chạm tới hạn mức nào nên chưa từng thấy hệ thống bị chặn gửi. Phần hạn mức dưới đây là tra tài liệu. 🔵 PHẦN THƯ VÀO RÁC: đã thử, kể cả cách chống được khuyên nhiều nhất - xem cuối mục.
Theo tài liệu công bố, Gmail miễn phí gửi khoảng 500 thư/ngày; tài khoản Google Workspace (bản trả phí cho doanh nghiệp) khoảng 2.000 thư/ngày.
⭐ Nhưng đây gần như chắc chắn không phải chỗ bạn tắc trước. Làm phép tính: bản Make miễn phí chịu được khoảng 330 đơn/tháng ≈ 11 đơn/ngày, mỗi đơn tối đa 2 thư ⇒ khoảng 22 thư/ngày. Còn xa mức 500. Trần thật của hệ này là Make, không phải Gmail - nếu phải nâng cấp thứ gì thì nâng Make trước.
⚠️ Có một con số dễ gây hiểu nhầm: tài liệu hay nhắc mức 100 thư/ngày cho trường hợp gửi qua SMTP. Make gửi qua kết nối Gmail chứ không phải SMTP thủ công, nên tôi nghĩ mức đó không áp - nhưng tôi không chắc. Nếu bạn định chạy trên 100 đơn/ngày thì đừng dựa vào phỏng đoán của tôi, hãy kiểm bằng tài liệu Google hoặc hỏi hỗ trợ của Make.
Đây là rủi ro thật hơn hạn mức, và nó im lặng: Make vẫn báo gửi thành công, Sheet vẫn đúng, chỉ là khách không thấy thư.
🔵 Hai việc tôi chắc chắn nên làm:
Lời khuyên bạn sẽ nghe ở khắp nơi: dùng email theo tên miền riêng (ban@tenban.com) và cấu hình
SPF · DKIM · DMARC - ba bản ghi chứng minh với hộp thư nhận rằng thư đúng là do bạn gửi - thì thư
sẽ vào hộp thư chính.
Tôi làm đúng như vậy rồi. Email theo tên miền riêng, xác thực đầy đủ. Gửi thử. Thư vẫn vào thư rác.
⇒ Kết luận của tôi, nói đúng mức thôi chứ không nói quá: xác thực có thể giảm rủi ro thật, nhưng chẳng đảm bảo được gì cả. Nó là điều kiện cần, không phải điều kiện đủ. Ai bảo bạn cứ cấu hình ba bản ghi đó vào là hết rơi vào rác thì người đó đang bán cho bạn thứ gì đó.
🟡 Một cách giải thích tôi thấy hợp lý nhưng chưa tự kiểm chứng được: tên miền mới gửi thư thì chưa có "lịch sử" nào để hộp thư nhận đánh giá, và cái lịch sử đó mới là thứ quyết định chính - xác thực chỉ chứng minh bạn là bạn, không chứng minh thư của bạn đáng đọc. Nếu đúng vậy thì nó sẽ tự tốt dần lên khi có người mở thư của bạn đều đặn. Tôi chưa chạy đủ lâu để nói chắc.
Vậy có nên làm không? Nếu bạn muốn nhìn chuyên nghiệp hơn thì cứ làm, không hại gì. Nhưng đừng làm với kỳ vọng nó giải quyết chuyện vào rác - làm xong mà thư vẫn vào rác thì đừng ngạc nhiên như tôi.
Vì không có nút nào bấm một cái là xong, tôi xử lý theo hướng khác - thiết kế để khách vẫn nhận được thư kể cả khi nó rơi vào rác:
Đây là chỗ tôi thấy nhiều người (và cả trợ lý AI) làm sai một cách rất tự nhiên: điền email của chính mình vào form, đơn chạy, thư về, mở ra thấy đẹp → kết luận "xong".
Thư tự gửi cho chính mình không chứng minh được hai thứ quan trọng nhất:
| Muốn kiểm | Tự gửi cho mình | Vì sao hỏng |
|---|---|---|
| Có vào thư rác không | ❌ vô nghĩa | Thư bạn gửi cho chính mình gần như không bao giờ bị ném vào rác hay tab Quảng cáo. Bạn sẽ luôn thấy nó nằm ngoan trong hộp thư chính - và tưởng mọi khách cũng vậy |
| Tên người gửi hiện ra sao | ❌ vô nghĩa | Hộp thư hiển thị "tôi" thay vì tên bạn đã đặt. Đúng cái tên bạn cần kiểm thì nó lại giấu đi |
⇒ Test đủ phải hai lượt:
⚠️ Chọn email lượt 2 cho đúng: một hộp thư KHÔNG có bạn trong danh bạ. Hộp thư nhận thường hiển
thị tên trong danh bạ của nó, không phải tên thật trên thư - nên gửi sang địa chỉ phụ mà bạn đã
lưu nhau từ lâu thì lại thấy tên cũ, và bạn kết luận sai lần nữa. Nhờ một người bạn nhận hộ là chắc
nhất. Muốn chắc tuyệt đối thì mở thư → "Hiển thị bản gốc" → đọc dòng From:.
⚠️ Và làm lại lượt 2 sau mỗi lần đổi thứ gì đó dính tới email - đổi tên người gửi, đổi địa chỉ gửi, sửa nội dung thư.
🟡 Hệ này không quan tâm bạn gửi bằng địa chỉ nào - công cụ tự động hoá chỉ cần kết nối được tới hộp thư đó. Nhưng cách kết nối cụ thể thì tuỳ nhà cung cấp email của bạn, mỗi nơi một kiểu, và tôi không đủ kinh nghiệm với các nhà cung cấp khác nhau để hướng dẫn từng ca.
Tới đây bạn có đủ tài khoản, công cụ, tên miền. Toàn bộ phần còn lại của tài liệu là dạy bạn nhận đơn. Mà nhận đơn thì phải có người bấm nút đã.
Thứ quyết định người ta bấm hay không là: họ có tin bạn không.
Cái đó không nằm trong số tài khoản, không nằm trong tên miền, và tôi không dựng hộ bạn được. Nhưng tôi chỉ được cho bạn nó gồm những gì và moi ở đâu ra.
Dân làm nội dung gọi bộ này là E-E-A-T (bốn trụ cột chất lượng nội dung của Google). Nghe hàn lâm nhưng bóc ra thì rất đời:
| Trụ | Tiếng người | Câu khách thầm hỏi |
|---|---|---|
| Experience - trải nghiệm thật | bạn đã tự dùng chưa | "Ông này có xài không hay chỉ đi bán?" |
| Expertise - chuyên môn | bạn biết gì mà nói | "Có biết gì không hay nghe hơi nồi chõ?" |
| Authoritativeness - có gì chứng minh | ai đã mua, ở đâu ra | "Có thật là bán được không?" |
| Trustworthiness - dám thừa nhận giới hạn | bạn giấu gì không | "Sao toàn lời khen thế?" |
⭐ Trụ thứ tư là trụ mạnh nhất, và cũng là trụ ai cũng bỏ. Trang bán hàng nào cũng khen sản phẩm từ đầu tới cuối, đọc riết thành nhàm và chả ai tin. Một câu kiểu "món này không làm được X đâu" hoặc "người đang bị Y thì đừng mua" làm mọi câu còn lại tự nhiên đáng tin hơn hẳn. Bạn mất một ít khách không hợp, đổi lại khách hợp thì tin bạn thật.
Trong bộ này có sẵn HO-SO-NGUOI-BAN.md. Chép vào thư mục dự án rồi điền một lần.
Vì sao phải là file:
Cái cuối quan trọng hơn nghe tưởng. Trợ lý AI rất sẵn lòng viết cho bạn "với hơn 10 năm kinh nghiệm trong ngành" dù bạn mới bán tháng đầu. Nó không cố lừa ai, nó chỉ đang lấp chỗ trống bằng thứ nghe xuôi tai. Nhưng thứ đó in lên trang là bạn phải đỡ khi khách hỏi vặn, chứ không phải nó.
⇒ Bộ này đã dặn trợ lý: ô nào trống thì hỏi bạn, không được tự điền. Bạn cứ để trống thoải mái, đừng bịa cho đầy. Một dòng thật đáng giá hơn mười dòng nghe kêu.
Vẫn có, chỉ là không nằm ở chỗ bạn tưởng. So hai câu:
❌ "Với nhiều năm kinh nghiệm, chúng tôi tự hào mang đến sản phẩm chất lượng..." ✅ "Tôi mới bán món này từ tháng 3. Nhưng tôi tự dùng nó một năm rồi, và lý do tôi nhập về bán là vì mẹ tôi dùng thấy đỡ."
Câu dưới thật hơn, cụ thể hơn, và bán tốt hơn - dù nghe "yếu" hơn. Câu trên thì ai cũng viết được, viết xong chẳng ai tin, mà lỡ có người hỏi "nhiều năm là mấy năm" thì bạn kẹt.
Nhóm hàng này ảnh hưởng trực tiếp tới sức khoẻ và tiền bạc người mua. Viết ẩu là hại người thật, chứ không phải chỉ mất uy tín. Google gọi nhóm này là YMYL (Your Money or Your Life) và soi rất kỹ, nhưng kể cả không có Google thì đây vẫn là chuyện nên làm cho đàng hoàng.
Luật tối thiểu, có đủ trong mục 7 của HO-SO-NGUOI-BAN.md:
⚠️ Rà mấy điểm này TRƯỚC khi viết, đừng viết xong mới rà. Mấy lỗi kiểu này nằm ngay trong cách dựng câu chứ không phải chỗ nào cắt phăng ra được - viết xong mới sửa là gần như phải viết lại.
Trợ lý cũng đã được dặn từ chối viết mấy câu hứa hẹn kiểu đó. Bạn vẫn ép được nếu muốn, nhưng nó sẽ nói cho bạn biết vì sao không nên trước đã.
⭐ Bảng này KHÔNG phải bài tập về nhà. Trợ lý sẽ hỏi bạn từng dòng rồi tự ghi lại - bạn không cần chép ra hay điền vào đâu cả. Để đây là để bạn biết trước sẽ bị hỏi những gì, và thấy dòng nào cần đi làm thật (mở tài khoản ngân hàng, mua tên miền) thì chuẩn bị sớm cho khỏi chờ.
── Về sản phẩm ────────────────────────────────────────────────
□ Tên sản phẩm (đúng như muốn hiện trên đơn): ____________________
□ Bảng giá theo số lượng: 1 = ______đ 2 = ______đ 3 = ______đ
□ Ảnh sản phẩm (3–6 tấm, đã cắt gọn): ____________________
□ Nội dung bán hàng: câu chuyện, cam kết, hỏi–đáp
── Tiền nong ──────────────────────────────────────────────────
□ Mã ngân hàng theo chuẩn VietQR (vd TCB, VCB, MB): __________
□ Số tài khoản: __________
□ Tên chủ tài khoản - VIẾT HOA KHÔNG DẤU: __________
(⚠️ có dấu là mã QR sinh ra sai, ngân hàng không đọc được)
── Thương hiệu ────────────────────────────────────────────────
□ File logo gốc, nền trong suốt, tối thiểu 1000×1000: ____________
□ Tên miền đã mua (đuôi gì cũng được): ____________________
□ Tên miền phụ định dùng cho trang này: ______.____________
□ Nameserver đã trỏ về Cloudflare chưa? □ rồi □ chưa (xem 1.4)
□ Địa chỉ Zalo nhóm cộng đồng (nếu có, không thì để trống)
── Kỹ thuật (tự chọn) ─────────────────────────────────────────
□ CK_PREFIX - tiền tố mã đơn, 2–4 chữ HOA không dấu: __________
□ ADMIN_TOKEN - chuỗi ngẫu nhiên dài để mở trang quản trị: ______
□ Địa chỉ Gmail dùng để gửi email cho khách: __________
CK_PREFIX - lỗi im lặng nguy hiểm nhất cả hệMỗi trang tự đếm đơn từ 1. Hai trang cùng tiền tố CMC là cùng đẻ ra mã CMC1 → lúc bạn dán
sao kê để đối soát, hệ thống lật nhầm đơn của trang kia. Không báo lỗi gì hết. Bạn chỉ biết lúc
khách gọi hỏi hàng đâu.
⇒ Giữ một cuốn sổ tiền tố. Mỗi lần dựng trang mới, tra sổ, chọn tiền tố chưa ai dùng, ghi lại ngay. Sổ của tôi:
| Tiền tố | Sản phẩm |
|---|---|
CMC |
Chải Mạch Cơ (trang gốc) |
CMB |
Chải Mạch Cơ (trang kể góc khác) |
KDT |
Kem dưỡng tay |
BBC |
BB Cream |
Gmail có HAI cái tên tách rời nhau:
Đổi cái thứ nhất KHÔNG kéo theo cái thứ hai. Tệ hơn: nếu ô đó đang ở chế độ "lấy tên từ Tài khoản Google" thì màn hình cài đặt hiện tên mới ngay lập tức - nhìn tưởng xong - nhưng tên thật in trên thư gửi đi thì không đổi. Khách nhận được email đề tên cũ.
Cách duy nhất ăn chắc: bấm "chỉnh sửa thông tin" → chọn ô radio nhập tự do → GÕ TAY tên → Lưu.
⚠️ Và đừng tự kiểm bằng cách gửi thư cho chính mình - Gmail hiển thị tên trong danh bạ của
bạn, không phải tên trên thư. Bạn sẽ thấy tên cũ dù thư đã đúng. Muốn chắc: mở thư → "Hiển thị bản
gốc" → đọc dòng From:.
Phần này không có thao tác nào cả. Nhưng bỏ qua thì lúc hỏng bạn chả biết hỏng ở đâu.
Đơn COD (thu tiền khi giao):
khách điền form → POST /api/order
→ worker kiểm dữ liệu (tên ≥2 ký tự, SĐT ≥8 chữ số, email đúng dạng, địa chỉ ≥5 ký tự)
→ counters cấp số thứ tự → ma_ck = "CMC" + số
→ ghi bảng orders
→ bắn Make với su_kien = "cod-moi"
├─ nhánh Google Sheet: thêm một dòng
└─ nhánh Gmail "đã nhận đơn": gửi khách
→ trả kết quả về trình duyệt → chuyển sang /cam-on-cod
Đơn chuyển khoản: giống hệt, chỉ khác su_kien = "qr-cho-tien", và:
su_kien = "qr-da-tra" → lúc này mới ghi SheetBa giá trị su_kien (cod-moi / qr-cho-tien / qr-da-tra) là thứ Make dựa vào để rẽ nhánh.
⚠️ Sai một chữ là đơn rơi vào hư không, mà Make vẫn báo thành công.
Vì ai cũng bấm được mà chả cần chuyển đồng nào. Bạn đóng hàng gửi đi xong mới biết.
Ở hệ này, đơn chuyển khoản chỉ lật sang "đã thanh toán" khi bạn dán sao kê ngân hàng vào trang
quản trị. Hệ thống dò mã CMC7 trong nội dung chuyển khoản, soi lại số tiền, khớp thì lật.
⚠️ Đơn lệch tiền thì nó KHÔNG tự lật, mà giữ lại cho bạn xem. Vì lật một đơn là kéo theo gửi email cho khách với ghi vào Sheet - lật nhầm rồi ngồi dọn còn mệt hơn bấm tay một nhát.
Cái này tôi trả giá hôm 16/07/2026: khách báo mã QR trong email hiện ngon trên máy tính, mà vỡ trên Gmail điện thoại.
Mò ra thì vietqr không chặn gì cả, nhưng (1) ảnh trả về không kèm Cache-Control, (2) đường link
nhồi đầy tham số &…&… → Gmail lọc HTML trên di động khác với trên web. Đây là chỗ kinh điển làm
thẻ <img> chết trên app mà vẫn sống ngon trên trình duyệt.
Cách bịt: cho worker tự phục vụ ảnh ở GET /qr/<MÃ>.png. Đường link sạch, không tham số, cache một
năm, chả phụ thuộc ai.
⚠️ Số tiền lấy từ cơ sở dữ liệu theo mã đơn, tuyệt đối không cho truyền qua đường link. Cho truyền thì ai cũng dựng được mã QR mang tên bạn với số tiền tuỳ thích. Bốn trường hợp phải trả 404: đơn không tồn tại · đơn COD · sai tiền tố · sai đuôi file.
| Bảng | Chứa gì | Vì sao tách |
|---|---|---|
orders |
từng đơn hàng | dữ liệu chính |
counters |
mỗi sản phẩm một dòng last_no |
để mã đơn đếm riêng theo sản phẩm dù dùng chung kho. Không có nó thì trang thứ hai mở hàng từ số 57 vì trang khác đã ăn mất 56 đơn, nhìn rất kỳ |
products |
danh mục sản phẩm | để trang quản trị tổng biết đang có những sản phẩm nào mà không phải gõ cứng |
Một nguyên tắc nhớ được thì đỡ khối lỗi về sau:
Thứ gì không đổi theo từng đơn thì đừng lưu theo từng đơn.
Ví dụ cái link nhóm Zalo: đó là cấu hình, không phải dữ liệu đơn. Nhét nó vào cơ sở dữ liệu thì hôm bạn đổi link, mọi đơn cũ vẫn ôm cái link chết.
Từ đây là thao tác thật. Làm đúng thứ tự.
don-hang) → Save.
→ Make cho bạn một đường link dạng https://hook.<vùng>.make.com/<chuỗi ngẫu nhiên>.
Chép đường link này lại - lát nữa dán vào _worker.js.| Nhánh | Module | Bộ lọc (filter) |
|---|---|---|
| 1 | Google Sheets → Add a Row | su_kien ≠ qr-cho-tien |
| 2 | Gmail → Send an email ("đã nhận đơn") | su_kien ≠ qr-cho-tien |
| 3 | Gmail → Send an email ("đơn đã tạo, mời chuyển khoản" + ảnh QR) | su_kien = qr-cho-tien |
⚠️ Đặt bộ lọc: bấm CHUỘT PHẢI vào đường nối → Set up filter. Hover hay click trái đều vô ích, canvas của Make là ảnh vector. ⚠️ Mở một module: double-click, không phải click.
Tạo Google Sheet với hàng tiêu đề gồm 15 cột. Gõ tiêu đề bằng chữ không dấu:
event · name · phone · email · pay_note · product · price · status · created_time
So thu tu · Ho va dem · Ten · Dia chi · Hinh thuc dat hang · Ghi chu
⚠️ Gõ tiếng Việt có dấu qua trình duyệt hay bị rơi dấu - dùng ASCII cho chắc.
⭐ Trong module Google Sheets, bật Use column headers as IDs of the columns = Yes.
Đây là bài học trả giá 22/07/2026: mặc định là No, nghĩa là Make map theo VỊ TRÍ cột
(A, B, C…). Hôm tôi sắp lại thứ tự cột cho dễ nhìn thì mọi đơn mới đổ vào sai cột - mà Make
vẫn báo Success, Sheet vẫn có dòng mới. Chỉ đọc kỹ mới thấy tên khách nằm ở ô trạng thái.
Bật Yes rồi thì sắp lại cột thoải mái; đổi lại ĐỪNG SỬA CHỮ Ở HÀNG 1.
Map dữ liệu. Mẹo nhanh: click vào ô rồi gõ thẳng {{N.ten_khoa}} - Make tự dựng thành
chip. Nhanh hơn nhiều so với bấm chọn từng biến ở bảng bên phải, và map được cả những khoá
Make chưa "học".
Viết nội dung 2 email hoàn toàn bằng BIẾN, đừng gõ cứng tên sản phẩm.
⚠️ Trả giá 16/07/2026: khách đầu tiên của trang Kem tay nhận email ghi "Cảm ơn bạn đã đặt
Combo Chải Mạch Cơ". "Dùng chung Make" nghĩa là dùng chung cả lời văn - cái gì gõ cứng
cho sản phẩm này sẽ đọc sai cho sản phẩm kia. Lỗi im lặng hoàn toàn: Make báo Success, Sheet đúng,
chỉ mỗi khách đọc email là thấy sai.
⇒ Tên sản phẩm dùng {{N.product}}, lời mời nhóm dùng {{N.nhom_cong_dong}}, ảnh QR dùng
{{N.qr_url}}.
Bật scenario sang Active. ⚠️ Save module ≠ Save scenario - lưu từng module xong vẫn phải bấm nút đĩa mềm (Ctrl+S) để lưu cả scenario. Quên cái sau là mất trắng công vừa làm.
Đây là chỗ dễ hiểu nhầm nhất, mà hiểu nhầm thì hỏng ngầm về sau.
Bạn lập một thư mục dự án duy nhất. Mọi trang bán hàng sau này nằm bên trong nó:
sale-page-cua-toi/ ← MỘT thư mục cho cả công việc bán hàng của bạn
GIAO-TRINH.md
HO-SO-NGUOI-BAN.md ← dùng chung cho MỌI trang
TIEN-DO.md ← tiến độ + sổ tiền tố, dùng chung
.claude/ ← trợ lý đọc
|
san-pham-1/ ← trang bán hàng thứ nhất
wrangler.toml
dist/
san-pham-2/ ← trang thứ hai, dựng sau
wrangler.toml
dist/
quan-tri/ ← trang đối soát, dựng MỘT lần dùng chung
wrangler.toml
dist/
Vì sao phải gom một chỗ - bốn thứ dùng chung:
| Thứ | Vì sao chung |
|---|---|
HO-SO-NGUOI-BAN.md |
vẫn là bạn bán, đâu đổi người |
TIEN-DO.md (có sổ tiền tố) |
⭐ xem cảnh báo ngay dưới |
| Cơ sở dữ liệu D1 | dán sao kê một lần là đối soát được mọi sản phẩm |
| Trang quản trị | một cái ví, một chỗ xem |
⛔ Sai lầm phải tránh: mỗi trang bán hàng một thư mục dự án riêng. Lúc đó mỗi thư mục có
TIEN-DO.mdriêng → sổ tiền tố bị chia đôi → bạn đặt trùngCK_PREFIXlúc nào không hay. Mà trùng tiền tố là lỗi im lặng nguy hiểm nhất cả hệ: hai trang cùng đẻ ra mãABC1, dán sao kê lật nhầm đơn của trang kia, không báo lỗi gì, tới lúc khách gọi hỏi hàng đâu mới biết.Trợ lý cũng mất luôn trí nhớ về mấy trang cũ, vì nó chỉ đọc thư mục đang mở.
Trong thư mục dự án đó, mỗi trang bán hàng một thư mục con riêng, ngay từ trang đầu:
san-pham-1/
wrangler.toml
dist/
index.html ← trang bán hàng
cam-on-cod.html
cam-on-qr.html
_worker.js ← máy chủ nhỏ
images/
⚠️ _worker.js phải nằm TRONG thư mục dist/, không phải cạnh wrangler.toml. Để sai chỗ là
Cloudflare không nhận ra máy chủ nhỏ: trang tĩnh vẫn hiện bình thường nhưng bấm đặt hàng thì không
có gì xảy ra, và thông báo lỗi chẳng nói gì về nguyên nhân.
⚠️ Gốc thư mục deploy PHẢI tên index.html. Cloudflare Pages lấy đúng cái tên đó làm trang gốc.
Đặt tên khác (sale-page.html, landing.html) là vào / không ra trang - người tôi hỗ trợ đã
dính đúng lỗi này. Trang con thì tên gì cũng được; Pages phục vụ ở đường dẫn không đuôi
(/cam-on-qr), trong mã cứ trỏ như vậy.
wrangler.toml:
name = "<ten-project>"
compatibility_date = "2024-09-23"
pages_build_output_dir = "./<ten-trang>-dist"
[[d1_databases]]
binding = "DB"
database_name = "<ten-db>"
database_id = "<điền sau khi tạo ở bước 3>"
wrangler d1 create <ten-db> # → chép database_id in ra vào wrangler.toml
Tạo bảng - đúng schema này, worker phụ thuộc vào nó:
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
san_pham TEXT, -- slug sản phẩm, để lọc đơn theo trang
san_pham_ten TEXT,
so_thu_tu INTEGER, -- số đơn riêng của sản phẩm này
ma_ck TEXT, -- CMC7 - KHOÁ THẬT để đối soát
ten TEXT, sdt TEXT, email TEXT, diachi TEXT,
so_luong TEXT, so_tien INTEGER,
hinh_thuc TEXT, -- cod | chuyen-khoan
trang_thai TEXT, -- moi | cho-tien | da-thanh-toan
nguon TEXT, -- UTM / fbclid bắt được lúc khách vào
nguoi_gt TEXT, -- mã người giới thiệu (affiliate)
meta TEXT,
created_at TEXT, paid_at TEXT,
ghi_chu TEXT DEFAULT ''
);
CREATE TABLE counters (
san_pham TEXT PRIMARY KEY,
last_no INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE products (
slug TEXT PRIMARY KEY,
ten TEXT, ck_prefix TEXT, created_at TEXT
);
Chạy:
wrangler d1 execute <ten-db> --remote --file=schema.sql
_worker.jsToàn bộ phần cần sửa nằm gọn ở đầu file. Phần dưới (nhận đơn, đối soát, phục vụ QR) để nguyên.
// ═══ KHỐI CẤU HÌNH RIÊNG CỦA TRANG NÀY ═══
const SAN_PHAM = "<Tên sản phẩm hiện trên đơn>";
const SAN_PHAM_SLUG = "<ten-khong-dau-gach-ngang>"; // khoá đếm số đơn
const CK_PREFIX = "<VD: CMC>"; // ⚠️ tra sổ, đừng trùng
const PRICE = { "1": 1000000, "2": 2000000 }; // bảng giá theo số lượng
const SITE_URL = "https://<tên miền đầy đủ của trang>"; // email cần link tuyệt đối
const BANK = { code:"<TCB>", acc:"<số tk>", name:"<TEN VIET HOA KHONG DAU>", display:"<Techcombank>" };
const NHOM_CONG_DONG = ""; // để rỗng = trang chưa có nhóm, câu tự biến mất khỏi email
const SHEET_WEBHOOK = "https://hook.<vùng>.make.com/<của bạn ở Bước 1>";
const ADMIN_TOKEN = "<chuỗi ngẫu nhiên dài>";
⚠️ Ba thứ bắt buộc khác nhau giữa các trang: CK_PREFIX, SAN_PHAM_SLUG, ADMIN_TOKEN.
Trùng ADMIN_TOKEN nghĩa là ai biết khoá trang này mở được trang quản trị của trang kia.
index.htmlPhần kỹ thuật của form thì giữ nguyên. Phần nội dung bán hàng là việc của bạn - nằm ngoài phạm vi tài liệu này (xem khung 7 bước kể chuyện).
Ba thứ kỹ thuật dễ hỏng, để ý:
⚠️ Nút CTA phải có font-family: inherit. Thẻ <button> không tự kế thừa font của body -
đó là mặc định của trình duyệt, không phải lỗi của bạn. Thiếu dòng này là nút rơi về Arial trong khi
cả trang là Segoe UI; chữ Đ / Ậ / đ của Arial vẽ khác hẳn → trông y hệt lỗi font tiếng Việt,
ngay trên nút to nhất và quan trọng nhất của trang. Cả 3 trang của tôi đều dính.
.btn { font-family: inherit; }
⛔ Đừng "sửa" bằng cách thêm button vào nhóm input, select, textarea. Nhìn thì hợp lý nhưng
bộ chọn đó có độ ưu tiên cao hơn .btn và nằm sau trong file → nó cướp luôn cỡ chữ, viền, đệm và
xoá cả nền gradient của nút. Tôi đã thử và phải gỡ ra trong cùng buổi.
⚠️ Trang cảm ơn KHÔNG hiện số đơn nội bộ cho khách. Đó là số thứ tự của bạn; khách chẳng làm gì
được với nó, hiện ra chỉ khiến họ tưởng phải ghi nhớ.
Nhưng ⚠️ đừng xoá nhầm dòng Nội dung: <mã CK> ở trang cảm ơn QR - nhìn giống nhau nhưng đó là
thứ khách bắt buộc ghi khi chuyển khoản. Mất nó thì tiền về không biết của ai.
⚠️ Đừng sửa file HTML tiếng Việt bằng perl -0pi -e. Perl in cảnh báo Wide character rồi mã
hoá UTF-8 lần thứ hai - toàn bộ tiếng Việt trong file thành nút bá» khóa…. Dùng Python với
io.open(..., encoding='utf-8').
Gắn Pixel + GA4 vào cả ba file (index.html, cam-on-cod.html, cam-on-qr.html) ngay từ lúc
lên sóng.
Vì sao không hoãn được: pixel không hồi tố. Tệp khách để retarget (nhắm lại người đã ghé trang) chỉ tính từ ngày bạn gắn. Hoãn hai tháng là mất trắng hai tháng người đã vào trang - đúng nhóm rẻ nhất khi bật quảng cáo. Meta cũng cần dữ liệu để học; bật quảng cáo trên pixel trắng thì giai đoạn học vừa lâu vừa đắt. Đây là quyết định tôi từng hoãn rồi phải đảo ngược.
| Trang | Sự kiện |
|---|---|
index.html |
PageView + ViewContent → lúc bấm gửi: InitiateCheckout + GA4 begin_checkout |
| hai trang cảm ơn | PageView + Purchase + GA4 purchase (transaction_id = mã CK) |
⚠️ Giá trong sự kiện phải lấy từ PRICE[form.soluong.value], đừng gõ cứng - đổi giá là số liệu
sai ngay.
⚠️ Purchase bắn lúc khách đặt, chưa phải lúc tiền về (áp cho cả COD lẫn chuyển khoản). Số
Purchase sẽ cao hơn doanh thu thật. Đây là lựa chọn có chủ ý - quan trọng là giữ nhất quán ở
mọi trang thì số liệu giữa các trang mới so sánh được với nhau.
Nghiệm thu bằng request thật, đừng chỉ tìm thấy chữ fbq trong file. Mở Console trình duyệt:
performance.getEntriesByType('resource').map(e => e.name)
.filter(n => n.includes('facebook.com/tr'))
.map(n => (n.match(/ev=([^&]+)/) || [])[1])
Thiếu là link dán ra Zalo/Messenger chỉ có chữ trơn, tab trình duyệt trống trơn - trông nghiệp dư ngay lập tức.
Dùng file logo gốc, đừng cắt từ ảnh poster. ⚠️ Bẫy hay gặp: logo nhiều thương hiệu là chữ trắng trên nền trong suốt - dán thẳng lên tab trình duyệt nền sáng là mất hút. Cách xử: cắt tròn lấy phần huy hiệu (ở cỡ 16px thì dòng chữ cong bên dưới vô nghĩa) rồi đặt lên đĩa màu đậm để tương phản trên cả tab sáng lẫn tối.
Cần xuất: favicon.ico (16/32/48) · favicon-512.png · apple-touch-icon.png · một ảnh chia sẻ
og-*.jpg cỡ 1200×630.
Khai vào <head>: canonical, icon, apple-touch-icon, og:url, og:site_name, og:locale,
og:image + og:image:secure_url + :type + :width + :height + :alt,
twitter:card=summary_large_image.
⚠️ Đường link trong
og:imagephải TUYỆT ĐỐI. Thiếuog:image:width/heightthì Zalo hay dựng khung ảnh nhỏ thay vì ảnh lớn.
Nghiệm thu bằng vân tay file, đừng nhìn mắt - so md5 của favicon.ico ở máy với bản trên
mạng. Trình duyệt cache favicon rất lì, nhìn tab không đáng tin.
wrangler pages dev --port 8788 --binding LOCAL=1
Cờ LOCAL=1 chốt một điều kiện trong worker chặn không bắn sang Make ⇒ nghịch thoải mái, không
đổ rác vào Sheet và không gửi email thật.
⚠️ Đừng test bằng python -m http.server - nó không phục vụ đường dẫn không đuôi, bấm gửi xong
ra 404 ở /cam-on-cod, bạn tưởng trang hỏng trong khi trang không sao.
Phải thử đủ bốn tình huống:
/cam-on-cod./cam-on-qr, mã QR mang đúng mã <PREFIX><số>, ảnh tải được thật./cam-on-qr không qua form → không vỡ trang, giấu QR, hiện lời nhắn liên hệ.wrangler pages project create <ten-project> --production-branch main # ⚠️ PHẢI làm trước
wrangler pages deploy --project-name <ten-project> --branch main
Bỏ dòng đầu là deploy báo project "<ten>" does not exist.
⚠️ Ngay sau deploy, đường nào đụng cơ sở dữ liệu sẽ trả lỗi 522 trong vài chục giây rồi mới chạy. Trang tĩnh thì lên ngay. Đừng vội tưởng binding hỏng - chờ ~20 giây rồi gọi lại.
⚠️ Cloudflare lan truyền trễ. Bản trên .pages.dev và bản trên tên miền riêng có thể lệch nhau
vài chục giây. Edge còn cache cả bản 404 cũ. Luôn thêm ?v=<số ngẫu nhiên> để phá cache trước
khi kết luận là hỏng.
chaico)<ten-project>.pages.dev⚠️ Dashboard Cloudflare có bước kiểm tra bot khoảng 5 giây lúc vào, cứ chờ. ⚠️ Nếu bạn để trợ lý AI thao tác: token của wrangler không có quyền DNS, và ô Target hay bị bộ lọc quyền chặn gõ. Thực tế chạy được: trợ lý set Type + Name, bạn gõ Target và bấm Save.
Phần 3 mới dựng chỗ nhận đơn. Nhưng đơn chuyển khoản nằm im ở trạng thái chờ tiền cho tới khi có người xác nhận tiền về. Nếu bước đó vẫn làm tay thì bạn chưa tiết kiệm được gì.
Trang quản trị (/don-hang?key=<ADMIN_TOKEN>) là một project riêng, dùng chung cơ sở dữ liệu với
mọi sale page. Dựng một lần, mọi trang sau hưởng.
Vì bạn sẽ có nhiều sale page, nhưng chỉ có một cái ví. Sao kê ngân hàng tải về là một file chung cho tất cả sản phẩm. Mỗi trang một trang quản trị riêng thì bạn phải dán cùng một đoạn sao kê vào 4 chỗ, mà mỗi chỗ lại chỉ nhặt ra mã của nó.
Gộp lại thì dán một lần, hệ thống tự tra tiền tố của mọi sản phẩm trong bảng products rồi lật hết.
Bạn tải sao kê từ app ngân hàng, bôi đen, dán vào ô. Hệ thống:
products (CMC, KDT, BBC…).<tiền tố><số> - không phân biệt hoa thường, và nuốt số 0 thừa
(cmc007 vẫn ra CMC7). Khách gõ nội dung chuyển khoản rất tuỳ hứng, phải chịu được.orders, rồi phán một trong sáu kết luận:| Kết luận | Khi nào | Hệ thống làm gì |
|---|---|---|
da-lat |
mã đúng, số tiền khớp | lật sang đã thanh toán, bắn qr-da-tra → ghi Sheet + gửi email |
lech-tien |
mã đúng, dòng có số tiền nhưng không phải số của đơn | ⭐ GIỮ LẠI, không lật |
da-roi |
đơn đã thanh toán từ trước | bỏ qua |
khong-phai-ck |
đơn này là COD | bỏ qua |
khong-thay |
không có mã đó trong hệ thống | báo để bạn tự tra |
khong-thay |
đơn đã bị huỷ (trong thùng rác) | ⭐ bỏ qua - dán trúng mã cũng không lật lại |
Luật 1: Lệch tiền thì KHÔNG tự lật, giữ lại cho người xem. Lật một đơn đâu phải chỉ đổi một chữ trong cơ sở dữ liệu. Nó gửi email cho khách và ghi một dòng vào Sheet cho người ta đóng gói. Lật nhầm rồi ngồi dọn còn mệt hơn bấm tay một nhát nhiều.
Có một chỗ tinh: hệ thống chỉ kêu lệch khi dòng đó có chứa một con số tiền nào đó mà không khớp. Còn đoạn bạn dán không có cột tiền (khối app xuất ra kiểu vậy) thì nó vẫn lật theo mã, nhưng ghi rõ "đã lật theo mã, đoạn dán không có cột tiền để đối chiếu" - tức là nó khai luôn với bạn là nó đang tin vào cái gì.
Luật 2: Xoá đơn là xoá MỀM.
Đơn thì đã lỡ bắn sang Make và ghi vào Sheet rồi. Xoá trắng khỏi cơ sở dữ liệu là mất dấu đối
soát, sau này lệch tiền không lần lại được. Nên "xoá" ở đây chỉ là đặt trang_thai = da-huy: đơn
biến khỏi bảng, khỏi thống kê, đối soát cũng bỏ qua nó, nhưng nó vẫn nằm đó.
Muốn xoá hẳn thì phải qua hai bước, huỷ trước rồi mới xoá, và chốt chặn nằm ở máy chủ: chỉ đơn
đang da-huy mới xoá được. Không có đường nào xoá vĩnh viễn bằng một cú bấm nhầm.
⚠️ Huỷ đơn ở đây KHÔNG gỡ dòng đã ghi trên Google Sheet, cũng KHÔNG rút lại email đã gửi. Hai thứ đó phải dọn tay. Hệ thống không giấu bạn chuyện này, nhưng bạn phải nhớ.
Luật 3: Mã truy cập nằm trên thanh địa chỉ, hiểu đúng mức bảo vệ của nó.
/don-hang?key=… chống được người lạ mò trúng, không chống được người mà bạn đã đưa link. Cái
link đó còn nằm trong lịch sử duyệt web nữa. Nên: đặt chuỗi dài vào, đừng dán vào nhóm chat, và
đừng xài chung một ADMIN_TOKEN cho mọi thứ.
Y hệt một sale page, chỉ khác: không có form đặt hàng, và wrangler.toml trỏ vào cùng
database_id với các sale page.
name = "donhang-tong"
pages_build_output_dir = "./dash-dist"
[[d1_databases]]
binding = "DB"
database_name = "<ten-db>"
database_id = "<CÙNG id với các sale page>"
Bốn đường API của nó:
| Đường | Việc |
|---|---|
POST /api/doi-soat |
dán sao kê, lật hàng loạt |
POST /api/danh-dau |
lật / gỡ tay một đơn |
POST /api/xoa-don |
{viec: huy | khoi-phuc | xoa-han} |
GET /don-hang?key= |
bảng đơn + bộ lọc + thùng rác |
⚠️ Mọi đường đều kiểm key ở MÁY CHỦ, không phải chỉ giấu nút trên giao diện. Giấu nút mà không
kiểm ở máy chủ thì ai mở Console cũng gọi được.
⚠️ Khi lật đơn, payload bắn sang Make phải dựng lại từ đơn đã lưu và giữ đúng tên khoá như
lúc sale page bắn đi. Thông tin ngân hàng lấy từ cột meta (đã chụp lại lúc đặt hàng) - không lấy
từ cấu hình hiện tại. Vì sao: hôm bạn đổi số tài khoản, đơn cũ vẫn phải hiện đúng số tài khoản mà
khách đã chuyển vào.
Chưa làm hết danh sách này thì chưa được chạy quảng cáo.
□ Đặt một đơn COD thật bằng số điện thoại của bạn, điền EMAIL CỦA CHÍNH BẠN
□ sang đúng trang cảm ơn
□ Google Sheet có dòng mới, ĐÚNG CỘT (đọc kỹ, đừng chỉ đếm số dòng)
□ email tới hộp thư, ĐỌC TỪNG CHỮ: tên sản phẩm đúng, không sót ô trống nào
□ ⭐ ĐẶT THÊM MỘT ĐƠN NỮA, điền một EMAIL KHÁC - không có bạn trong danh bạ
(lượt tự gửi cho mình KHÔNG kết luận được 2 ô dưới đây, xem mục 1.6)
□ thư nằm ở hộp thư chính, hay rơi vào tab Quảng cáo / thư rác? ← kiểm TRÊN ĐIỆN THOẠI
□ tên người gửi hiện đúng chưa (chắc ăn: mở "Hiển thị bản gốc", đọc dòng From:)
□ Đặt một đơn CHUYỂN KHOẢN thật
□ email "mời chuyển khoản" tới
□ ⭐ MỞ EMAIL RA XEM ẢNH QR CÓ HIỆN KHÔNG - trên ĐIỆN THOẠI, không chỉ máy tính
□ QUÉT THẬT mã QR bằng ứng dụng ngân hàng: đúng ngân hàng, đúng số tài khoản,
đúng số tiền, đúng mã đơn trong nội dung
□ Dán sao kê thật vào trang quản trị → đơn lật đúng
□ Chỉ SAU KHI làm hết các bước trên: xoá đơn test + xoá dòng trong bảng counters
→ khách thật đầu tiên nhận mã <PREFIX>1
Đây là bẫy tôi trả giá 20/07/2026 và nó phá đúng cái bước nghiệm thu quan trọng nhất.
Đường /qr/<mã>.png tra đơn trong cơ sở dữ liệu rồi mới dựng ảnh. Xoá đơn đi là đường đó trả
404, và email đã gửi trước đó sẽ hiện ảnh vỡ - vì Gmail chỉ tải ảnh lúc người ta MỞ thư,
không phải lúc gửi. Bạn dọn dữ liệu xong, mở thư ra thấy ảnh gãy, tưởng sản phẩm mình hỏng.
⇒ Thứ tự đúng: đặt đơn test → MỞ EMAIL xem ảnh QR → rồi mới xoá đơn.
⚠️ Và đừng để cache đánh lừa lúc chẩn đoán: đường link sạch vẫn có thể trả 200 từ cache biên
trong khi máy chủ đã 404. Thêm ?v=<số> để phá cache rồi mới kết luận.
phone trên Google Sheet mà mất định dạng Plain text
thì 0824352686 hiện thành 824352686. Gọi khách là sai số. Sau mỗi lần sắp lại cột: bôi cột
phone → Format → Number → Plain text. ⚠️ Đổi định dạng không tự khôi phục số 0 đã mất -
phải gõ tay lại.={J1:J3,H1:H3,A1:A3,…}) → copy → dán chỉ giá trị đè lên chính nó → copy lần nữa → dán vào
A1 → xoá vùng tạm. ⚠️ Bám đúng số hàng đang có dữ liệu (1:3), đừng dùng A:A - dán cả nghìn ô
rỗng làm Make nối dòng mới xuống tận cuối bảng.Shift+← / Shift+→ dịch từng
ký tự. Và Make CHÈN THÊM biến chứ không đè lên vùng đang chọn → chèn xong phải bôi đen phần
chữ cũ rồi bấm Delete.qr-cho-tien) cho khỏi đẻ rác.Trang đầu ngốn cỡ một ngày. Trang thứ hai chỉ còn một buổi, mà phần lớn thời gian là ngồi viết nội dung bán hàng chứ không phải kỹ thuật.
cp -r <trang-cu>/<trang-cu>-dist <trang-moi>/<trang-moi>-dist
rm -rf <trang-moi>/<trang-moi>-dist/.wrangler # trạng thái của trang cũ
Rồi:
_worker.js: SAN_PHAM, SAN_PHAM_SLUG, CK_PREFIX (tra sổ!),
PRICE, SITE_URL, ADMIN_TOKEN.wrangler.toml: name + pages_build_output_dir mới. Dùng chung database_id nếu bạn muốn
một kho đơn duy nhất (khuyến nghị - đối soát một lần cho mọi sản phẩm).index.html.su_kien là tự chảy vào đúng
nhánh.
Ngoại lệ duy nhất: payload có trường MỚI mà email cần dùng → vào Webhook bấm
"Detect new values" rồi bắn một request thật cho Make học.productSheet có số cột cố định. Đẻ thêm khoá payload thì Sheet không có cột nhận → người đóng gói không biết gói cái gì, dù dữ liệu vẫn nằm trong cơ sở dữ liệu.
Cách rẻ nhất - nhét thuộc tính vào ngay trường product đang chảy sẵn:
product: SAN_PHAM + " x" + o.soluong + (o.mui ? " (" + o.mui + ")" : "")
// → "Kem dưỡng tay Gongskin x3 (Wood x2, Blackberry x1)"
Đọc một cột là biết gói mấy cái, loại nào. Không phải sửa gì bên Make.
⚠️ Và luôn kiểm lại ở máy chủ, đừng tin mỗi trình duyệt: tổng số món theo mùi phải khớp soluong;
giá trị lạ ngoài danh sách cho phép thì bỏ.
Tài liệu này là hướng dẫn kỹ thuật, không phải nội dung sức khoẻ - nhưng nó dạy người khác dựng trang bán hàng, nên có ba điểm phải nói rõ:
Số liệu trong tài liệu (hạn mức miễn phí Cloudflare/Make, giá tên miền) đúng tại 22/07/2026 - hãy kiểm lại trên trang chính thức trước khi dựa vào để ra quyết định.