بازگشت به وبلاگ

وبلاگ

رویدادهای تجارت الکترونیک بدون پلتفرم فروشگاهی: اتصال فرایند پرداخت اختصاصی به GA4

منتشرشده · به‌روزشده · 6 دقیقه مطالعه

یک پلتفرم فروشگاهی دست‌کم افزونه‌ای به شما می‌دهد که بتوانید با آن کلنجار بروید. فرایند پرداختی که خودتان ساخته‌اید — یک اپ React، یک فرایند رزرو، یک ثبت‌نام SaaS با Stripe در پشت آن — هیچ چیزی به شما نمی‌دهد: هیچ رویدادی اجرا نمی‌شود تا وقتی کد خودتان آن را push کند. خبر خوب این است که قرارداد کوچک است: چند نام، یک ساختار شیء و چند قاعده درباره اینکه هر push کجا باید باشد. همه‌اش اینجاست، از جمله بخشی که بیشتر راهنماها از قلم می‌اندازند — خریدهایی که وقتی هیچ‌کس در صفحه شما نیست انجام می‌شوند.

قرارداد: یک نام و یک شیء ecommerce

همه‌چیز در اینجا به یک شکل به Analytics می‌رسد. صفحه شما رویدادی را با نام GA4 آن به window.dataLayer می‌فرستد (push می‌کند) که یک شیء ecommerce با ساختار GA4 همراه دارد. یک تریگر Tag Manager با این نام مطابقت پیدا می‌کند و یک تگ رویداد GA4 شیء را می‌خواند و به کانتینر سرور شما می‌فرستد. هر کانتینر SignalHost این تریگرها و تگ‌ها را از پیش در کانتینر وب خود دارد، با پیشوند SH در نامشان — برای رویدادهای فروشگاهی زیر و برای ثبت‌نام‌ها، دوره‌های آزمایشی و سرنخ‌ها. آنچه می‌ماند، خود push است.

دو قاعده جلوی بیشتر اشتباه‌ها را می‌گیرد. اول شیء 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 — در handler دکمه، پس از آنکه سبد خرید شما واقعاً تغییر کرده است.
  • view_cart — وقتی سبد خرید نمایش داده می‌شود: صفحه آن یا باز شدن کشوی (drawer) آن.
  • begin_checkout — لحظه‌ای که بازدیدکننده وارد مرحله پرداخت می‌شود: نخستین رندر صفحه پرداخت یا کلیکی که پنجره پرداخت را باز می‌کند.
  • add_shipping_info — وقتی روش ارسال انتخاب می‌شود. اگر چیزی ارسال نمی‌کنید، آن را کنار بگذارید.
  • add_payment_info — وقتی اطلاعات پرداخت پذیرفته می‌شود، نه وقتی فرم ظاهر می‌شود.
  • purchase — یک بار، وقتی سفارش تأیید می‌شود. بیشتر این مقاله درباره درست انجام دادن همین یکی است.
  • sign_up و start_trial — برای کسب‌وکار اشتراکی، تبدیل‌هایی که پیش از دست‌به‌دست شدن هر پولی رخ می‌دهند. SignalHost برای هر دو، در کنار رویدادهای فروشگاهی، تگ می‌سازد و برای سرنخ‌ها هم: generate_lead، contact، schedule، submit_application و subscribe.

از handlerی push کنید که تغییر را ایجاد کرده، نه از یک رندر. یک اپ تک‌صفحه‌ای آزادانه دوباره رندر می‌شود و React در حالت توسعه effectها را دو بار اجرا می‌کند: push درون یک effect در نسخه توسعه دو برابر می‌شود و هر بار که کامپوننتی دوباره mount شود تکرار می‌شود.

خرید در صفحه تأیید: سفارش را از سرور خودتان بخوانید

وقتی بازدیدکننده پس از پرداخت به یکی از صفحه‌های شما برمی‌گردد، جای purchase همان صفحه است — با دو شرط. سفارش را از بک‌اند خودتان بگیرید، نه از URL: پارامترهای query قابل ویرایش‌اند، در ریدایرکت‌ها حذف می‌شوند و در پرداخت‌های ناموفق هم می‌رسند. برای نمونه، Stripe چه پرداخت انجام شده باشد چه نه، به همان نشانی برمی‌گردد و نتیجه را در پارامتری مخصوص خودش اعلام می‌کند. و آن را فقط یک بار push کنید: بارگذاری دوباره، دکمه بازگشت یا صفحه رسیدی که نشانک‌گذاری شده، همگی دوباره به آن برمی‌گردند.

// 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 یک تور ایمنی است، نه یک راهبرد. Analytics یک transaction_id تکراری را در یک نشست کنار می‌گذارد، اما بارگذاری دوباره صفحه رسید در صبح روز بعد را نه.

پرداخت در صفحه دیگران: Stripe Checkout، PayPal، انتقال به درگاه بانک

صفحه پرداخت میزبانی‌شده انتخاب رایج برای فرایندهای اختصاصی است و یک لبه تیز دارد: ممکن است بازدیدکننده هرگز برنگردد. پرداخت می‌کند، زبانه را می‌بندد و صفحه تأیید شما هرگز رندر نمی‌شود. با کارت، این چند درصد خریدهاست. با انتقال بانکی، صورت‌حساب و روش‌های «حالا بخر، بعداً پرداخت کن» می‌تواند بیشتر آن‌ها باشد، چون پول ساعت‌ها یا روزها بعد می‌رسد.

پس برای هر روش پرداخت تصمیم بگیرید که منبع حقیقت کجاست. اگر بک‌اند شما از طریق webhook از پرداخت باخبر می‌شود، webhook جای مطمئن برای گزارش آن است — بخش بعد را ببینید — و صفحه تأیید نباید purchase را هم push کند، وگرنه هر پرداخت با کارت دو بار شمرده می‌شود. اگر تنها جایی که از پرداخت باخبر می‌شوید صفحه بازگشت است، همان‌جا گزارشش کنید و این شکاف را بپذیرید.

خریدهایی که هیچ مرورگری شاهدشان نیست: آن‌ها را از سرورتان بفرستید

اشتراک‌ها این کار را اجتناب‌ناپذیر می‌کنند. یک دوره آزمایشی پس از هفت روز به اشتراک پولی تبدیل می‌شود، تمدید هر ماه انجام می‌شود، صورت‌حسابی هفته بعد با انتقال بانکی پرداخت می‌شود. هرکدام یک خرید است و هیچ‌کدام جلوی یک مرورگر اتفاق نمی‌افتد. راه‌حل این است که رویداد را سرور به سرور، از همان webhookی که از پرداخت باخبر می‌شود، به همان نقطه پایانی تگ‌گذاری بفرستید که صفحه‌هایتان استفاده می‌کنند — نام میزبان SignalHost شما یا دامنه خودتان — تا مثل هر ارسال دیگری از کانتینر سرور شما عبور کند.

این کار یک چیز از مرحله پرداخت لازم دارد: client id بازدیدکننده در Analytics، تا خرید به نام همان کسی ثبت شود که بازدیدهایش به آن منجر شده است. این شناسه در کوکی _ga است که درخواست پرداخت شما، وقتی به دامنه خودتان می‌رود، از قبل با خود دارد. آن را همراه با سفارش ذخیره کنید. نبودن کوکی _ga یعنی بازدیدکننده رضایت تحلیلی نداده است: ارسال را به‌عنوان «بدون رضایت» و با یک شناسه یک‌بارمصرف بفرستید، یا اصلاً نفرستید — هرگز برای او هویتی جعل نکنید. اگر تبدیل‌ها را به 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 را push می‌کند؛ سرور ما start_trial را وقتی می‌فرستد که دوره آزمایشی واقعاً شروع شده باشد، تا بسته شدن یک زبانه نتواند آن را گم کند؛ و webhook مربوط به Stripe هنگام پرداخت نخستین صورت‌حساب purchase را می‌فرستد. هیچ رویدادی دو منبع ندارد. Measurement Protocol متعلق به Google هم کار می‌کند، اما مستقیم به Google می‌رود و کانتینر سرور شما را دور می‌زند، بنابراین هیچ‌یک از تگ‌های سمت سرور شما هرگز آن فروش را نمی‌بینند.

خریدهای دیرهنگام یکی از چهار دلیلی است که GA4 کمتر از فهرست سفارش‌های شما می‌شمارد: سه دلیل دیگر، و اینکه کدام‌هایشان را می‌توانید رفع کنید.

رضایت کار اضافه‌ای از شما نمی‌خواهد

رویدادها را push کنید، هر انتخابی که بازدیدکننده کرده باشد. تگ‌ها از Consent Mode پیروی می‌کنند: با رضایت، به‌طور عادی ارسال می‌کنند؛ بدون آن، پینگ‌های بدون کوکی می‌فرستند که Analytics آن‌ها را مدل‌سازی می‌کند، نه گزارش. اگر خودتان pushها را نگه دارید، فقط تبدیل‌ها را از آن مدل‌سازی پنهان کرده‌اید. آنچه واقعاً اهمیت دارد ترتیب است — قطعه‌کد Tag Manager باید پس از اسکریپت بنر رضایت شما بیاید تا مقادیر پیش‌فرض رضایت پیش از ارسال نخستین رویداد وجود داشته باشند.

بررسی اینکه کار می‌کند

  • کانتینر را در SignalHost باز کنید. کارت «رویدادها» آنچه را رسیده به تفکیک نام فهرست می‌کند، همراه با سهمی که رضایت داشته است. نامی که push کرده‌اید و آنجا نمی‌بینید، هرگز به سرور تگ‌گذاری شما نرسیده است.
  • فرایند پرداختتان را در حالت Preview در Tag Manager طی کنید. این حالت هر push، تگی که اجرا شده و شیء ecommerce ارسال‌شده را نشان می‌دهد.
  • در Analytics، بخش DebugView رویدادها را همان لحظه که می‌رسند نشان می‌دهد. گزارش‌های استاندارد ممکن است یک روز طول بکشند.
  • صفحه تأیید را دو بار دوباره بارگذاری کنید و خریدها را بشمارید. باید همچنان یکی باشد.

خطاها در اینجا همه بی‌صدا هستند: نامی با حروف کوچک و بزرگ اشتباه، value ارسال‌شده به‌صورت رشته، items که یک سطح بیش از حد تودرتو شده است. هیچ‌کدام در هیچ جا خطایی ایجاد نمی‌کند. همه‌شان گزارشی سوراخ‌دار به بار می‌آورند.

دیگر داده‌ها را به خاطر مسدودکننده‌های تبلیغات از دست ندهید

رایگان شروع کنید. اولین کانتینر را در چند دقیقه راه‌اندازی کنید.