Nếu doanh nghiệp bạn bán hàng online, đây là bài quan trọng nhất series.
Khi purchase event được track đúng, mọi báo cáo doanh thu trong GA4 sẽ có nghĩa: doanh thu theo kênh, theo chiến dịch ads, theo từng sản phẩm. Mọi tích hợp với Google Ads, Facebook Ads, TikTok Ads cũng dựa vào event này.
Nhưng nếu track sai, và rất nhiều website ở Việt Nam đang track sai mà không biết, thì mỗi báo cáo GA4 đều chứa số liệu sai lệch. Marketing dựa vào đó để quyết ngân sách → lãng phí ngân sách vào kênh không hiệu quả.
dataLayer là gì, giải thích đơn giản
dataLayer là một JavaScript array trên trang web, đóng vai trò “người đưa tin” giữa website và GTM.
Khi website có sự kiện quan trọng (user xem sản phẩm, thêm giỏ, thanh toán), code của website push một object vào dataLayer. GTM “nghe” mọi push lên dataLayer, match với trigger đã cài, fire tag tương ứng → gửi event đến GA4.
Cấu trúc cơ bản của một push:
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: "view_item",
ecommerce: {
currency: "VND",
value: 450000,
items: [{
item_id: "SKU-12345",
item_name: "Áo thun cotton trắng size M",
price: 450000,
quantity: 1,
item_category: "Áo thun",
}],
},
});
3 phần quan trọng:
event: tên event GTM sẽ nghe (custom event trigger)ecommerce: object chứa dữ liệu chuẩn của GA4 ecommerceitems: mảng các sản phẩm liên quan (xem, thêm giỏ, mua)
Mistake phổ biến, push thiếu reset
GA4 có một quirk: nếu bạn push 2 lần liên tiếp với cùng nested object ecommerce, lần thứ 2 có thể bị bỏ qua vì giá trị giống nhau.
Best practice: push null để reset trước khi push event mới:
window.dataLayer.push({ ecommerce: null }); // reset
window.dataLayer.push({
event: "purchase",
ecommerce: { ... },
});
Bỏ dòng reset → purchase event sau đó trong cùng session có thể không gửi value → revenue trong GA4 thấp hơn thực tế.
6 ecommerce event chuẩn của GA4
Sử dụng đúng tên chuẩn này → GA4 tự kích hoạt báo cáo Monetization → Ecommerce purchases, Reports → Monetization. Đặt sai tên → mất hết báo cáo này.
| Event | Khi nào push | Phổ biến ở đâu |
|---|---|---|
view_item_list | User xem trang danh sách sản phẩm (category, search results) | Trang category, search |
view_item | User mở chi tiết 1 sản phẩm | Trang chi tiết sản phẩm |
add_to_cart | User thêm vào giỏ | Click nút Add to Cart |
begin_checkout | User bắt đầu thanh toán | Click nút Checkout |
purchase | Thanh toán thành công | Trang Thank You / sau khi server xác nhận đơn |
refund | Hoàn hàng | Khi admin xử lý refund |
Còn 4 event khác ít dùng hơn: select_item, add_payment_info, add_shipping_info, view_cart. Cài sau khi có data từ 6 cái trên.
Schema items, phần hay sai nhất
items là array. Mỗi phần tử là một sản phẩm. Field bắt buộc/khuyến nghị:
{
item_id: "SKU-12345", // BẮT BUỘC — mã sản phẩm
item_name: "Áo thun trắng", // BẮT BUỘC — tên sản phẩm
price: 450000, // KHUYẾN NGHỊ — giá đơn vị (số, không có "VND")
quantity: 1, // KHUYẾN NGHỊ — số lượng
currency: "VND", // có thể đặt ở level trên hoặc trong từng item
item_brand: "ACME", // tuỳ chọn
item_category: "Áo thun", // tuỳ chọn — phân loại
item_variant: "Size M", // tuỳ chọn — biến thể (size, màu)
discount: 50000, // tuỳ chọn — số tiền giảm
affiliation: "Shopee", // tuỳ chọn — nguồn bán
index: 0, // tuỳ chọn — vị trí trong list (cho view_item_list)
}
3 quy tắc quan trọng:
-
pricelà giá đơn vị, không phải tổng. Nếu user mua 3 áo giá 450 nghìn →price: 450000, quantity: 3. Tổngvalue(level trên) phải bằngprice * quantity. -
item_idphải nhất quán giữa các event. Cùng một sản phẩm → cùng SKU ởview_item,add_to_cart,purchase. Lệch nhau → GA4 không thể link funnel. -
currencyphải nhất quán và là mã ISO 4217:VND,USD,EUR. Không phảivndhayđồng.
Cài cho từng nền tảng
Shopify
Tin tốt: app Google & YouTube Channel của Shopify (đã giới thiệu ở bài 3) tự động push tất cả 6 event ecommerce vào dataLayer.
Verify: mở DevTools Console → gõ dataLayer → thêm sản phẩm vào giỏ → kiểm tra dataLayer array có push mới với event add_to_cart không.
Nếu app official cài đúng, bạn chỉ cần làm phần GTM: tạo Custom Event triggers cho mỗi event name (view_item, add_to_cart...) và GA4 Event tag tương ứng.
Nếu cần custom hơn (vd. push thêm parameter affiliation: "Direct" cho mọi đơn hàng) → vào Online Store → Themes → Edit code → mở theme.liquid → thêm script custom dataLayer push.
WooCommerce
Tin tốt: plugin GTM4WP (miễn phí) tự push tất cả ecommerce event vào dataLayer.
Cài plugin → Settings → tab Integration → bật Track classic ecommerce hoặc Track enhanced ecommerce (UA) hoặc Track GA4 ecommerce. Chọn cái phù hợp với phiên bản GA bạn dùng, với GA4 mới: chọn GA4 ecommerce.
Sau khi bật, không cần code gì thêm. WooCommerce events tự push lên dataLayer. Bạn chỉ làm GTM (Custom Event triggers + GA4 Event tags).
Next.js / Custom
Đây là phần phải code. Mỗi lần action xảy ra trên website, gọi function push dataLayer.
Tạo utility:
// src/lib/analytics.ts
type EcommerceItem = {
item_id: string;
item_name: string;
price: number;
quantity: number;
currency?: string;
item_brand?: string;
item_category?: string;
item_variant?: string;
};
declare global {
interface Window {
dataLayer: Record<string, unknown>[];
}
}
export function trackEvent(
eventName: string,
ecommerce: { currency: string; value: number; items: EcommerceItem[] }
) {
if (typeof window === "undefined") return;
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null }); // reset
window.dataLayer.push({
event: eventName,
ecommerce,
});
}
Sử dụng trong component:
// trang product detail
useEffect(() => {
trackEvent("view_item", {
currency: "VND",
value: product.price,
items: [{
item_id: product.sku,
item_name: product.name,
price: product.price,
quantity: 1,
item_category: product.category,
}],
});
}, [product]);
Tương tự cho add_to_cart, begin_checkout. Purchase event nên gọi từ server action sau khi confirm đơn hàng (chi tiết phần dưới).
Khi nên gọi từ client vs từ server
Client-side (trong useEffect / event handler):
view_item,view_item_list, phụ thuộc vào việc user render trang đóadd_to_cart, phụ thuộc vào click button của user
Server-side (qua sGTM, hoặc thêm event vào server response để client push):
purchase, phải confirm đơn hàng đã ghi vào DB, payment thành công. Nếu push từ client trước khi server xác nhận → có thể track double hoặc track đơn hàng failrefund, luôn là server action
purchase track sai = doanh thu GA4 sai. Đầu tư thời gian làm đúng phần này.
Push purchase đúng cách
Đây là pattern phổ biến cho Next.js + Server Action:
// server action: app/api/checkout/route.ts hoặc trong server action file
"use server";
export async function completePurchase(orderId: string) {
// 1. Confirm payment với gateway (VNPay, Stripe, MoMo...)
const payment = await confirmPayment(orderId);
if (!payment.success) return { success: false };
// 2. Lưu order vào DB
const order = await saveOrder(orderId);
// 3. Trả về response với data để client push lên dataLayer
return {
success: true,
purchaseData: {
transaction_id: order.id,
value: order.total,
currency: "VND",
tax: order.tax,
shipping: order.shipping_fee,
items: order.items.map((it) => ({
item_id: it.sku,
item_name: it.name,
price: it.unit_price,
quantity: it.qty,
})),
},
};
}
Trong client component:
const handleCheckoutComplete = async () => {
const result = await completePurchase(orderId);
if (result.success && result.purchaseData) {
trackEvent("purchase", result.purchaseData);
router.push("/thank-you?order=" + result.purchaseData.transaction_id);
}
};
Pattern này đảm bảo: chỉ push purchase khi payment confirmed và order đã lưu DB. Nếu user reload trang thank-you → không push lại (vì call action đã chạy 1 lần). Nếu payment fail → không push.
3 sai sót khiến doanh thu GA4 ≠ MISA
Sai sót 1, Track double (track ở 2 nơi)
Tình huống: bạn cài track purchase ở client (thank-you page useEffect) + plugin ecommerce (như GTM4WP) cũng tự push event purchase. Một đơn hàng bị track 2 lần → doanh thu nhân đôi.
Cách check: vào GA4 → DebugView → mua thử 1 sản phẩm 100 nghìn → xem có 1 hay 2 event purchase xuất hiện.
Cách fix: chọn một nguồn duy nhất, gỡ cái kia.
Sai sót 2, Không xử lý refund
User mua 1 triệu → bạn refund 100 nghìn vì 1 sản phẩm bị lỗi. Nhưng GA4 vẫn ghi doanh thu là 1 triệu. Báo cáo doanh thu GA4 sẽ luôn cao hơn thực tế.
Cách fix: khi admin xử lý refund trong hệ thống nội bộ, trigger event refund lên dataLayer:
trackEvent("refund", {
currency: "VND",
value: 100000, // số tiền refund, KHÔNG phải tổng đơn gốc
items: [/* item bị refund */],
transaction_id: "ORDER-123",
});
GA4 sẽ trừ doanh thu refund khỏi báo cáo. Nhưng phải gửi event này, không tự động.
Sai sót 3, Không trừ thuế VAT (hoặc trừ 2 lần)
GA4 có sẵn parameter tax và shipping. Quy tắc:
value= tổng số tiền user trả thực sự (gross, đã bao gồm VAT và shipping)tax= số tiền VAT trong value đóshipping= phí ship trong value đó
Nếu MISA tính doanh thu không bao gồm VAT (net revenue) → bạn cần báo cáo GA4 ở dạng net cũng → khi lập dashboard, trừ tax ra khỏi value.
Nhiều shop ở Việt Nam push value = giá net (không VAT) → đúng so với MISA, nhưng Google Ads attribution lại nhận con số nhỏ → ROAS báo cáo trong Google Ads thấp hơn thực. Phải nhất quán cách tính giữa MISA và GA4 ngay từ đầu.
Audit doanh thu GA4 vs MISA, quy trình tôi dùng
Mỗi tháng tôi audit 1 lần:
- Vào MISA → lấy báo cáo doanh thu online của tháng (gross revenue)
- Vào GA4 → Monetization → Ecommerce purchases → cùng tháng → lấy total revenue
- So sánh
- Lệch dưới 2% → bỏ qua (do timezone, refund chưa report lên GA4...)
- Lệch 2-10% → check refund tracking + currency
- Lệch >10% → có vấn đề lớn, purchase event sai
Nếu doanh thu GA4 và MISA lệch >10% mà chưa rõ vì sao → mọi báo cáo marketing dựa vào GA4 đều chứa số sai. Sửa ngay.
AI hỗ trợ, phần tiết kiệm nhiều nhất
Sinh dataLayer push code cho framework cụ thể
Prompt:
Tôi đang dùng Next.js 15 App Router + Stripe checkout. Sau khi Stripe
confirm thanh toán (webhook callback), tôi cần push event "purchase" lên
dataLayer client-side để GTM bắt và gửi đến GA4.
Cấu trúc order từ Stripe:
- order.id (string)
- order.amount_total (cent USD, cần convert sang VND)
- order.line_items (array, mỗi item có product.metadata.sku, name, price, quantity)
Yêu cầu:
1. Server-side: receive webhook, lưu order vào DB
2. Client-side: trang /thank-you nhận order_id từ query string, fetch order
từ DB, push dataLayer purchase event với đầy đủ items[], value, currency
3. Đảm bảo không push double nếu user reload page (dùng sessionStorage hoặc
gửi flag từ server "already_tracked")
4. Type-safe TypeScript
5. Convert VND không có decimal (Stripe trả về integer cent → chia 100)
Viết code đầy đủ.
Output ra 80-100 dòng code production-ready. So với tự nghĩ + đọc Stripe docs + đọc GA4 ecommerce docs: ~2 giờ thay vì 10 phút.
Sinh dataLayer mẫu để test GTM
Khi bạn build GTM tag cho purchase nhưng chưa có người mua thật để test, bạn có thể tự push event mẫu trong Console:
Sinh cho tôi 3 dataLayer.push() mẫu để test GA4 ecommerce tag trong GTM
Preview mode. Các kịch bản:
1. Mua 1 sản phẩm áo thun 450 nghìn, có VAT 10%, shipping 30 nghìn
2. Mua 3 sản phẩm khác nhau, tổng 1,2 triệu trước VAT
3. Refund toàn bộ đơn 2 (cả 3 sản phẩm)
Mỗi push kèm dataLayer.push({ ecommerce: null }) reset trước.
Items đầy đủ field: item_id, item_name, price, quantity, currency, item_category.
Copy output, paste vào Console của browser, run từng cái, GTM Preview sẽ thấy event tương ứng → kiểm tra tag GA4 bắt đúng.
Audit dataLayer schema hiện tại
Nếu thừa hưởng codebase từ ai đó, không biết dataLayer đang push đúng/sai:
Đây là 10 dataLayer push tôi capture được trên website (trích từ DevTools
Console → dataLayer). Liệt kê:
1. Event nào KHÔNG phải tên chuẩn GA4 → đề xuất tên đúng
2. Field nào thiếu trong items array
3. Vấn đề khác (currency không đúng ISO, value lệch, etc.)
4. Sắp xếp theo mức độ ảnh hưởng cao xuống thấp
[paste dataLayer dump]
AI ra một báo cáo audit nhanh. Tự đọc 10 push để tìm lỗi mất 30 phút. AI ra báo cáo trong 30 giây.
Trước khi sang bài 7
Checklist quan trọng:
- ✅ DataLayer đã push 6 ecommerce event chính (view_item → purchase), verify trong Console
- ✅ GTM có 6 Custom Event trigger + 6 GA4 Event tag tương ứng
- ✅ DebugView GA4 hiển thị 6 event đầy đủ với items[], value, currency
- ✅ Mua thử 1 sản phẩm thật → 1 event
purchase(không phải 2) - ✅ Doanh thu báo trong GA4 sau 24h khớp với đơn hàng thật ±2%
Khi xong, GA4 của bạn đã có ecommerce tracking hoàn chỉnh. Báo cáo Monetization → Ecommerce purchases có data. Google Ads / Facebook Ads có thể import conversion purchase với value chính xác → bid theo ROAS chính xác.
Bài 7 sẽ là phần kỹ thuật nhất series: Server-side Tagging (sGTM). Đây là bước nâng cao cho doanh nghiệp đã chạy quy mô đủ lớn, gửi event qua server bạn sở hữu thay vì trực tiếp đến Google. Giúp giảm tác động của iOS 14, ad blocker, và cookie banner. Bài đó dành cho ai sẵn sàng tốn ~50-100 USD/tháng cloud + 1 ngày setup.
Nếu bạn dừng ở bài 6
Honestly, nhiều SME Việt không cần sGTM (bài 7). Nếu doanh nghiệp bạn quy mô dưới 5 tỷ doanh thu/tháng online, ecommerce tracking client-side (bài 6) là đủ. Đầu tư thời gian vào bài 8 (debug) và bài 9 (dashboard) tốt hơn.
Bài 7 chỉ cần thiết khi: bạn chạy ads với budget >100 triệu/tháng và conversion attribution là critical, hoặc bạn đã có team dev/devops sẵn sàng vận hành cloud infra.
Đọc tiếp
Bài 1, GA4 và Universal Analytics: vì sao bạn phải làm lại từ đầu
Universal Analytics đã ngừng nhận dữ liệu từ giữa 2023. GA4 không phải bản nâng cấp, mà là một sản phẩm khác hẳn về cách đo lường. Bài này giải thích vì sao bạn không thể chỉ 'chuyển sang GA4' mà phải dựng lại từ kế hoạch.
Đọc bàiBài 2, Viết measurement plan trong 30 phút (với ChatGPT/Claude)
Measurement plan là tài liệu quan trọng nhất nhưng hầu hết doanh nghiệp Việt bỏ qua. Bài này có template đầy đủ + bộ prompt cụ thể để dùng AI dựng bản nháp, sau đó refine với người trong đội ngũ.
Đọc bàiBài 3, Cài GA4 lần đầu: từ tạo property đến dữ liệu chảy về sau 15 phút
Hướng dẫn cài GA4 cho người chưa từng làm, tạo account, property, data stream, gắn vào website (Wordpress / Shopify / custom). Có checklist cài đặt ban đầu và cách dùng AI sinh code cho từng nền tảng.
Đọc bài