ব্লগ
শপ প্ল্যাটফর্ম ছাড়াই ই-কমার্স ইভেন্ট: কাস্টম চেকআউট থেকে GA4-এ রিপোর্ট পাঠানো
প্রকাশিত · আপডেট করা হয়েছে · পড়তে 5 মিনিট
একটি শপ প্ল্যাটফর্ম অন্তত আপনাকে একটি প্লাগইন দেয়, যার সঙ্গে লড়াই করা যায়। নিজে বানানো চেকআউট — একটি React অ্যাপ, একটি বুকিং ফ্লো, পেছনে Stripe সহ একটি SaaS সাইন-আপ — আপনাকে কিছুই দেয় না: আপনার নিজের কোড পুশ না করা পর্যন্ত কোনো ইভেন্ট ফায়ার হয় না। ভালো খবর হলো, নিয়মের তালিকাটা ছোট: হাতে গোনা কয়েকটি নাম, একটি অবজেক্টের গঠন, আর প্রতিটি পুশ কোথায় হবে তা নিয়ে কয়েকটি নিয়ম। এখানে তার সবটুকু আছে, সেই অংশসহ যা বেশিরভাগ গাইড বাদ দিয়ে যায় — যে পারচেজগুলো হয় যখন আপনার পেজে কেউ থাকে না।
মূল নিয়ম: একটি নাম আর একটি ecommerce অবজেক্ট
এখানের সবকিছু একইভাবে Analytics-এ পৌঁছায়। আপনার পেজ window.dataLayer-এ GA4-এর নামে একটি ইভেন্ট পুশ করে, যার সঙ্গে থাকে GA4-এর গঠনে একটি ecommerce অবজেক্ট। একটি Tag Manager ট্রিগার নামটি মেলায়, আর একটি GA4 ইভেন্ট ট্যাগ অবজেক্টটি পড়ে আপনার সার্ভার কনটেইনারে পাঠিয়ে দেয়। প্রতিটি SignalHost কনটেইনারের ওয়েব কনটেইনারে এই ট্রিগার ও ট্যাগগুলো আগে থেকেই আছে, SH প্রিফিক্স দিয়ে নাম দেওয়া — নিচের শপ ইভেন্টগুলোর জন্য, আর সাইন-আপ, ট্রায়াল ও লিডের জন্যও। বাকি থাকে শুধু পুশটি।
দুটি নিয়ম মেনে চললে বেশিরভাগ ভুল এড়ানো যায়। প্রথমে আগের ecommerce অবজেক্টটি খালি করুন, নইলে তিন পেজ আগের একটি view_item পরের ইভেন্টের ভেতরে ঢুকে চলে যাবে। আর টাকা বহন করে এমন প্রতিটি ইভেন্টে value পাঠান সংখ্যা হিসেবে এবং currency পাঠান ISO কোড হিসেবে: স্ট্রিং হিসেবে value বা অনুপস্থিত currency কোনো আপত্তি ছাড়াই গ্রহণ করা হয় এবং চুপচাপ রেভিনিউ থেকে বাদ পড়ে।
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'begin_checkout',
ecommerce: {
currency: 'EUR',
value: 49.00,
items: [
{ item_id: 'plan-pro', item_name: 'Pro', price: 49.00, quantity: 1 }
]
}
});কোন ইভেন্ট, আর সেটি কোথায় বসবে
একটি কাস্টম ফ্লোতেও শপ প্ল্যাটফর্মের মতো একই মুহূর্তগুলো থাকে। শুধু আপনার নিজের কোডে সেগুলো খুঁজে বের করতে হয়।
- view_item_list — যে পেজ বা কম্পোনেন্ট কয়েকটি প্রোডাক্ট বা প্ল্যান দেখায়। তালিকাটি দেখানোর সময় একবার, প্রতিটি স্ক্রলে নয়।
- view_item — একটি একক প্রোডাক্ট বা প্ল্যানের পেজ।
- add_to_cart এবং remove_from_cart — বাটন হ্যান্ডলারে, আপনার কার্ট সত্যিই বদলানোর পরে।
- view_cart — যখন কার্ট দেখানো হয়: তার পেজে, বা ড্রয়ার খোলার সময়।
- begin_checkout — যে মুহূর্তে ভিজিটর চেকআউটে ঢোকেন: চেকআউট পেজের প্রথম রেন্ডার, বা যে ক্লিক একটি চেকআউট ডায়ালগ খোলে।
- add_shipping_info — যখন একটি শিপিং পদ্ধতি বেছে নেওয়া হয়। আপনি কিছু শিপ না করলে এটি বাদ দিন।
- add_payment_info — যখন পেমেন্টের তথ্য গৃহীত হয়, ফর্মটি দেখা দেওয়ার সময় নয়।
- purchase — একবার, যখন অর্ডার নিশ্চিত হয়। এই আর্টিকেলের বেশিরভাগটাই এটিকে ঠিকঠাক করা নিয়ে।
- sign_up এবং start_trial — সাবস্ক্রিপশন ব্যবসার জন্য, কোনো টাকা হাতবদলের আগের কনভার্সন। SignalHost শপ ইভেন্টগুলোর পাশাপাশি এ দুটির জন্যও ট্যাগ তৈরি করে, আর লিডের জন্যও: generate_lead, contact, schedule, submit_application এবং subscribe।
যে হ্যান্ডলার পরিবর্তনটি ঘটিয়েছে, সেখান থেকেই পুশ করুন, রেন্ডার থেকে নয়। একটি সিঙ্গেল-পেজ অ্যাপ যখন-তখন রি-রেন্ডার করে, আর ডেভেলপমেন্টে React ইফেক্ট দুবার চালায়: ইফেক্টের ভেতরের একটি পুশ ডেভ বিল্ডে দ্বিগুণ হয় এবং প্রতিবার কোনো কম্পোনেন্ট রিমাউন্ট হলে আবার ঘটে।
কনফার্মেশন পেজে পারচেজ: অর্ডারটি আপনার সার্ভার থেকে পড়ুন
পেমেন্টের পরে ভিজিটর যখন আপনার কোনো পেজে ফিরে আসেন, purchase-এর জায়গা সেই পেজেই — দুটি শর্তে। অর্ডারটি নিন আপনার নিজের ব্যাকএন্ড থেকে, URL থেকে নয়: কোয়েরি প্যারামিটার বদলে ফেলা যায়, রিডাইরেক্টে মুছে যায়, আর ব্যর্থ পেমেন্টেও আসে। যেমন Stripe, পেমেন্ট সফল হোক বা না হোক, একই ঠিকানায় ফেরত পাঠায় এবং কোনটি হয়েছে তা নিজের একটি প্যারামিটারে জানায়। আর এটি একবারই পুশ করুন: রিলোড, ব্যাক বাটন বা বুকমার্ক করা রসিদ পেজ — সবই আবার সেখানে ফিরে আসে।
// On your confirmation page. The order comes from YOUR server, by an id the
// page already knows — never from price or status parameters in the URL.
const order = await fetch(`/api/orders/${orderId}`).then((r) => r.json());
const key = `purchase-sent-${order.id}`;
if (order.status === 'paid' && !localStorage.getItem(key)) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: order.id,
value: order.total,
tax: order.tax,
currency: order.currency,
items: order.items.map((i) => ({
item_id: i.sku, item_name: i.name, price: i.price, quantity: i.quantity
}))
}
});
localStorage.setItem(key, '1');
}transaction_id একটি সেফটি নেট, পরিকল্পনা নয়। একই সেশনের মধ্যে পুনরাবৃত্ত transaction_id Analytics বাদ দেয়, কিন্তু পরের দিন সকালে রসিদ পেজের রিলোড বাদ দেয় না।
অন্য কারও পেজে পেমেন্ট: Stripe Checkout, PayPal, ব্যাংক রিডাইরেক্ট
কাস্টম বিল্ডে হোস্টেড পেমেন্ট পেজই সাধারণ পছন্দ, আর এর একটি ধারালো কিনারা আছে: ভিজিটর হয়তো কখনো ফিরেই আসবেন না। তাঁরা পেমেন্ট করেন, ট্যাব বন্ধ করেন, আর আপনার কনফার্মেশন পেজ কখনো রেন্ডারই হয় না। কার্ডে এটা পারচেজের কয়েক শতাংশ। ব্যাংক ট্রান্সফার, ইনভয়েস আর পে-লেটার পদ্ধতিতে এটা বেশিরভাগই হতে পারে, কারণ টাকা আসে কয়েক ঘণ্টা বা কয়েক দিন পরে।
তাই প্রতিটি পেমেন্ট পদ্ধতির জন্য ঠিক করুন, আসল তথ্য কোথায় থাকে। আপনার ব্যাকএন্ড যদি ওয়েবহুক থেকে পেমেন্টের খবর পায়, তবে রিপোর্ট করার নির্ভরযোগ্য জায়গা সেই ওয়েবহুক — পরের অংশ দেখুন — আর কনফার্মেশন পেজ থেকে purchase আবার পুশ করা চলবে না, নইলে প্রতিটি কার্ড পেমেন্ট দুবার গোনা হবে। আর যদি আপনি শুধু রিটার্ন পেজ থেকেই খবরটা পান, তবে সেখানেই রিপোর্ট করুন এবং ফাঁকটুকু মেনে নিন।
যে পারচেজের সময় কোনো ব্রাউজার নেই: সেগুলো আপনার সার্ভার থেকে পাঠান
সাবস্ক্রিপশনে এটি এড়ানোর উপায় নেই। একটি ট্রায়াল সাত দিন পরে কনভার্ট হয়, প্রতি মাসে একটি রিনিউয়াল হয়, পরের সপ্তাহে ট্রান্সফারে একটি ইনভয়েস পরিশোধ হয়। প্রতিটিই একটি পারচেজ, আর এর কোনোটিই কোনো ব্রাউজারের সামনে ঘটে না। সমাধান হলো ইভেন্টটি সার্ভার-টু-সার্ভার পাঠানো — যে ওয়েবহুক পেমেন্টের খবর পায় সেখান থেকে, আপনার পেজগুলো যে ট্যাগিং এন্ডপয়েন্ট ব্যবহার করে সেখানেই (আপনার SignalHost হোস্টনেম বা আপনার নিজের ডোমেইন) — যাতে এটি অন্য প্রতিটি হিটের মতোই আপনার সার্ভার কনটেইনারের মধ্য দিয়ে যায়।
এর জন্য চেকআউট থেকে একটি জিনিস লাগে: ভিজিটরের Analytics ক্লায়েন্ট ID, যাতে পারচেজটি সেই মানুষটির খাতায় যায়, যাঁর ভিজিট থেকে এটি এসেছে। এটি থাকে _ga কুকিতে, যা আপনার চেকআউট রিকোয়েস্ট আপনার নিজের ডোমেইনে গেলে এমনিতেই সঙ্গে নিয়ে যায়। এটি অর্ডারের সঙ্গে সংরক্ষণ করুন। _ga কুকি না থাকার মানে ভিজিটর অ্যানালিটিক্সের সম্মতি দেননি: হিটটি consent-denied হিসেবে একটি ফেলে-দেওয়া ID দিয়ে পাঠান, অথবা একেবারেই পাঠাবেন না — তাঁদের জন্য কখনো কোনো পরিচয় বানাবেন না। আপনি যদি Google Ads, Meta, TikTok বা অন্যান্য বিজ্ঞাপন প্ল্যাটফর্মেও কনভার্সন পাঠান, তবে ভিজিটরের বিজ্ঞাপন-সংক্রান্ত পছন্দটিও অর্ডারের সঙ্গে সংরক্ষণ করুন এবং তিনি অনুমতি দিয়ে থাকলে G111 পাঠান: G101 চিহ্নিত পারচেজ Analytics-এ পৌঁছায়, কিন্তু কোনো বিজ্ঞাপন প্ল্যাটফর্মে নয়, আর Google Ads সেটিকে কোনো ক্লিকের সঙ্গে জুড়তে পারে না।
// In your checkout request handler: keep the visitor's Analytics id with the order.
const ga = req.cookies._ga; // "GA1.1.1234567890.1700000000"
order.gaClientId = ga ? ga.split('.').slice(-2).join('.') : null;
// Later, in your payment webhook — a trial converting, an invoice paid:
const params = new URLSearchParams({
v: '2',
tid: 'G-XXXXXXXXXX', // your GA4 measurement ID
cid: order.gaClientId ?? `${Date.now()}.${Math.floor(Math.random() * 1e9)}`,
gcs: order.gaClientId ? 'G101' : 'G100', // no _ga cookie = no consent
en: 'purchase',
'ep.transaction_id': invoice.id,
'epn.value': '49.00',
cu: 'EUR',
pr1: 'idplan-pro~nmPro~pr49.00~qt1',
});
await fetch(`https://metrics.example.com/g/collect?${params}`, { method: 'POST' });প্রতিটি ইভেন্টের জন্য একটি উৎস বেছে নিন এবং সেটিতেই থাকুন। signalhost.io ঠিক এভাবেই নিজের বিক্রি মাপে: ব্রাউজার sign_up, begin_checkout এবং add_payment_info পুশ করে; ট্রায়াল সত্যিই শুরু হলে আমাদের সার্ভার start_trial পাঠায়, তাই ট্যাব বন্ধ করলেও এটি হারায় না; আর প্রথম ইনভয়েস পরিশোধ হলে Stripe ওয়েবহুক purchase পাঠায়। কোনো ইভেন্টের দুটি উৎস নেই। Google-এর Measurement Protocol-ও কাজ করত, কিন্তু সেটি সরাসরি Google-এ যায় এবং আপনার সার্ভার কনটেইনারকে পাশ কাটায়, ফলে আপনার কোনো সার্ভার-সাইড ট্যাগ বিক্রিটি কখনো দেখতে পায় না।
আপনার অর্ডার তালিকার তুলনায় GA4 কম গোনার চারটি কারণের একটি হলো দেরিতে আসা পারচেজ: বাকি তিনটি, আর তার কোনগুলো আপনি ঠিক করতে পারেন।
সম্মতির জন্য আপনাকে বাড়তি কিছু করতে হয় না
ভিজিটর যা-ই বেছে নিন, ইভেন্টগুলো পুশ করুন। ট্যাগগুলো Consent Mode মেনে চলে: সম্মতি থাকলে স্বাভাবিকভাবে পাঠায়; না থাকলে কুকিবিহীন পিং পাঠায়, যেগুলো Analytics রিপোর্ট না করে মডেল করে। নিজে থেকে পুশ আটকে রাখলে শুধু সেই মডেলিং থেকে কনভার্সন লুকানো হয়। যা সত্যিই গুরুত্বপূর্ণ তা হলো ক্রম — Tag Manager স্নিপেট বসবে আপনার কুকি ব্যানারের স্ক্রিপ্টের পরে, যাতে প্রথম ইভেন্ট পাঠানোর আগেই সম্মতির ডিফল্টগুলো তৈরি থাকে।
কাজ করছে কি না যাচাই করা
- SignalHost-এ কনটেইনারটি খুলুন। ইভেন্ট কার্ডে যা পৌঁছেছে তা নাম অনুযায়ী তালিকাভুক্ত থাকে, সঙ্গে সম্মতিসহ আসা অংশের হার। যে নাম আপনি পুশ করেছেন অথচ সেখানে দেখছেন না, সেটি কখনো আপনার ট্যাগিং সার্ভারে পৌঁছায়নি।
- Tag Manager-এর Preview মোডে আপনার চেকআউটের প্রতিটি ধাপ পার হয়ে দেখুন। এটি প্রতিটি পুশ, কোন ট্যাগ ফায়ার হয়েছে, আর কোন ecommerce অবজেক্ট পাঠানো হয়েছে তা দেখায়।
- Analytics-এ DebugView ইভেন্টগুলো পৌঁছানোর সঙ্গে সঙ্গে দেখায়। স্ট্যান্ডার্ড রিপোর্টে এক দিন লাগতে পারে।
- কনফার্মেশন পেজ দুবার রিলোড করুন এবং পারচেজ গুনুন। তখনও একটিই থাকার কথা।
এখানের সব ব্যর্থতাই নিঃশব্দ: ভুল কেসে (ছোট/বড় হাতের অক্ষরে) লেখা একটি নাম, স্ট্রিং হিসেবে পাঠানো value, এক স্তর বেশি গভীরে বসানো items। এর কোনোটিই কোথাও কোনো এরর দেখায় না। প্রতিটিই এমন একটি রিপোর্ট তৈরি করে, যাতে একটি ফাঁক থেকে যায়।

