From 654e8899a8bb9d22f16e8c584059e40b074fe635 Mon Sep 17 00:00:00 2001 From: "dev.daminik00" Date: Wed, 24 Dec 2025 21:42:28 +0300 Subject: [PATCH] utm --- .../templates/SpecialOffer/SpecialOffer.tsx | 22 +++++- .../TrialPaymentTemplate.tsx | 22 +++++- src/services/analytics/analyticsService.ts | 2 +- src/shared/utils/url.ts | 78 +++++++++++++++++++ 4 files changed, 119 insertions(+), 5 deletions(-) diff --git a/src/components/funnel/templates/SpecialOffer/SpecialOffer.tsx b/src/components/funnel/templates/SpecialOffer/SpecialOffer.tsx index ab5c1aa..d3c5c57 100644 --- a/src/components/funnel/templates/SpecialOffer/SpecialOffer.tsx +++ b/src/components/funnel/templates/SpecialOffer/SpecialOffer.tsx @@ -18,6 +18,8 @@ import { getFormattedPrice } from "@/shared/utils/price"; import { formatPeriod, formatPeriodHyphen } from "@/shared/utils/period"; import { useState } from "react"; import { getTrackingCookiesForRedirect } from "@/shared/utils/cookies"; +import { getClientSessionId } from "@/shared/session/sessionId"; +import { getStateParamForRedirect } from "@/shared/utils/url"; interface SpecialOfferProps { funnel: FunnelDefinition; @@ -70,9 +72,25 @@ export function SpecialOfferTemplate({ return; } setIsLoadingRedirect(true); - const redirectUrl = `${paymentUrl}?paywallId=${paywallId}&placementId=${placementId}&productId=${productId}&jwtToken=${token}&price=${( + + // Build redirect URL with payment params + const baseParams = `paywallId=${paywallId}&placementId=${placementId}&productId=${productId}&jwtToken=${token}&price=${( (trialPrice || 100) / 100 - ).toFixed(2)}¤cy=${currency}&${getTrackingCookiesForRedirect()}`; + ).toFixed(2)}¤cy=${currency}`; + + // Add sessionId + const sessionId = getClientSessionId(); + const sessionParam = sessionId ? `&sessionId=${sessionId}` : ""; + + // Add state param with current UTM (base64 encoded JSON) + const stateParam = getStateParamForRedirect(); + const stateStr = stateParam ? `&state=${stateParam}` : ""; + + // Add tracking cookies + const trackingCookies = getTrackingCookiesForRedirect(); + const trackingStr = trackingCookies ? `&${trackingCookies}` : ""; + + const redirectUrl = `${paymentUrl}?${baseParams}${sessionParam}${stateStr}${trackingStr}`; return window.location.replace(redirectUrl); }; diff --git a/src/components/funnel/templates/TrialPaymentTemplate/TrialPaymentTemplate.tsx b/src/components/funnel/templates/TrialPaymentTemplate/TrialPaymentTemplate.tsx index ad947c3..862b75d 100644 --- a/src/components/funnel/templates/TrialPaymentTemplate/TrialPaymentTemplate.tsx +++ b/src/components/funnel/templates/TrialPaymentTemplate/TrialPaymentTemplate.tsx @@ -41,6 +41,8 @@ import { getFormattedPrice } from "@/shared/utils/price"; import { useClientToken } from "@/hooks/auth/useClientToken"; import { formatPeriod, formatPeriodHyphen } from "@/shared/utils/period"; import { getTrackingCookiesForRedirect } from "@/shared/utils/cookies"; +import { getClientSessionId } from "@/shared/session/sessionId"; +import { getStateParamForRedirect } from "@/shared/utils/url"; interface TrialPaymentTemplateProps { funnel: FunnelDefinition; @@ -94,9 +96,25 @@ export function TrialPaymentTemplate({ return; } setLoadingButtonIndex(buttonIndex); - const redirectUrl = `${paymentUrl}?paywallId=${paywallId}&placementId=${placementId}&productId=${productId}&jwtToken=${token}&price=${( + + // Build redirect URL with payment params + const baseParams = `paywallId=${paywallId}&placementId=${placementId}&productId=${productId}&jwtToken=${token}&price=${( (trialPrice || 100) / 100 - ).toFixed(2)}¤cy=${currency}&${getTrackingCookiesForRedirect()}`; + ).toFixed(2)}¤cy=${currency}`; + + // Add sessionId + const sessionId = getClientSessionId(); + const sessionParam = sessionId ? `&sessionId=${sessionId}` : ""; + + // Add state param with current UTM (base64 encoded JSON) + const stateParam = getStateParamForRedirect(); + const stateStr = stateParam ? `&state=${stateParam}` : ""; + + // Add tracking cookies + const trackingCookies = getTrackingCookiesForRedirect(); + const trackingStr = trackingCookies ? `&${trackingCookies}` : ""; + + const redirectUrl = `${paymentUrl}?${baseParams}${sessionParam}${stateStr}${trackingStr}`; return window.location.replace(redirectUrl); }; diff --git a/src/services/analytics/analyticsService.ts b/src/services/analytics/analyticsService.ts index 3998988..a6f5b03 100644 --- a/src/services/analytics/analyticsService.ts +++ b/src/services/analytics/analyticsService.ts @@ -112,7 +112,7 @@ function trackFacebookPixelEvent( // Map EnteredEmail to Lead for Facebook const fbEvent = event === AnalyticsEvent.ENTERED_EMAIL ? AnalyticsEvent.LEAD : event; - window.fbq("track", fbEvent, options); + window.fbq?.("track", fbEvent, options); console.log(`[FB] Event: ${fbEvent}`, options); } diff --git a/src/shared/utils/url.ts b/src/shared/utils/url.ts index bbd9394..866ae33 100644 --- a/src/shared/utils/url.ts +++ b/src/shared/utils/url.ts @@ -17,3 +17,81 @@ export const parseQueryParams = () => { return result; }; + +// Params that should NOT be included in state (they are passed separately) +const EXCLUDED_STATE_PARAMS = [ + "paywallId", + "placementId", + "productId", + "jwtToken", + "price", + "currency", + "fb_pixels", + "sessionId", + "state", + // Tracking cookies (passed separately) + "_fbc", + "_fbp", + "_ym_uid", + "_ym_d", + "_ym_isad", + "_ym_visorc", + "yandexuid", + "ymex", +]; + +/** + * Get current query params that should be passed between screens and to payment + * Includes ALL params except internal ones (productId, placementId, etc.) + * Works with utm_*, fbclid, gclid, and any other marketing params + */ +export const getCurrentQueryParams = (): Record => { + if (typeof window === "undefined") return {}; + + const params = parseQueryParams(); + const utmParams: Record = {}; + + for (const [key, value] of Object.entries(params)) { + const isExcluded = + EXCLUDED_STATE_PARAMS.includes(key) || + key.startsWith("_ga") || + key.startsWith("_gid"); + + if (!isExcluded && value) { + utmParams[key] = value; + } + } + + return utmParams; +}; + +/** + * Encode params as base64 JSON for state parameter + * Uses URL-safe base64 encoding + */ +export const encodeStateParam = (params: Record): string => { + if (Object.keys(params).length === 0) return ""; + + try { + const json = JSON.stringify(params); + // Use btoa for base64, replace unsafe chars for URL + const base64 = btoa(json) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); + return base64; + } catch { + return ""; + } +}; + +/** + * Get base64-encoded state parameter with current query params + */ +export const getStateParamForRedirect = (): string => { + const params = getCurrentQueryParams(); + return encodeStateParam(params); +}; + +// Backward compatibility alias +export const getCurrentUtmParams = getCurrentQueryParams;