${affiliateName ? decodeURIComponent(affiliateName) : "We've"} sent you ${
discountType === 'percentage' ? `${discountValue}%` : `$${discountValue}`
} off!
Click below to redeem your discount code
Your discount will automatically be applied at checkout
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
// Close handler
document.getElementById('__growi-close-modal').onclick = () => {
window.growiCookieUtils.setCookie('discount_seen', `${affiliateId.toLowerCase()}`, -1);
overlay.remove();
};
// Redeem handler
document.getElementById('__growi-redeem-btn').onclick = () => {
const redeemBtn = document.getElementById('__growi-redeem-btn');
// Set loading state
redeemBtn.disabled = true;
redeemBtn.style.cursor = 'not-allowed';
redeemBtn.innerHTML = `
Loading...
`;
// Add spinning animation
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
// Simulate loading delay then show discount code
setTimeout(() => {
// Replace button with discount code
redeemBtn.style.display = 'none';
// Create discount code container
const discountCodeDiv = document.createElement('div');
discountCodeDiv.id = '__growi-discount-code';
discountCodeDiv.style.cssText = `
background-color:#ecf0ff;
display:block;
width:90%;
margin:20px auto 0 auto;
text-align:center;
color:#3860fb;
font-size:25px;
padding:8px;
cursor:pointer;
border-radius:5px;
font-weight:bold;
font-family:-apple-system, Inter, BlinkMacSystemFont, Segoe UI, Roboto,
Helvetica Neue, Arial, Noto Sans, sans-serif;
`;
discountCodeDiv.innerHTML = `
${discountCode.toUpperCase()}

`;
// Add copy functionality
discountCodeDiv.onclick = () => {
navigator.clipboard
.writeText(discountCode.toUpperCase())
.then(() => {
// Show copied feedback under the code
let copiedText = document.getElementById('__growi-copied-text');
if (!copiedText) {
copiedText = document.createElement('div');
copiedText.id = '__growi-copied-text';
copiedText.style.cssText = `
font-size:0.9em;
color:#28a745;
margin-top:8px;
text-align:center;
`;
discountCodeDiv.parentNode.insertBefore(copiedText, discountCodeDiv.nextSibling);
}
copiedText.innerHTML = '✓ Discount applied!';
})
.catch(() => {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = discountCode.toUpperCase();
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
let copiedText = document.getElementById('__growi-copied-text');
if (!copiedText) {
copiedText = document.createElement('div');
copiedText.id = '__growi-copied-text';
copiedText.style.cssText = `
font-size:0.9em;
color:#28a745;
margin-top:8px;
text-align:center;
`;
discountCodeDiv.parentNode.insertBefore(copiedText, discountCodeDiv.nextSibling);
}
copiedText.innerHTML = '✓ Discount applied!';
});
};
// Insert the discount code div after the button
redeemBtn.parentNode.insertBefore(discountCodeDiv, redeemBtn.nextSibling);
}, 1500);
window.growiCookieUtils.setCookie('discount_seen', `${affiliateId.toLowerCase()}`);
};
}
async function showDiscountModalV2(discountCode, affiliateName, discountValue, discountType, affiliateId) {
console.log('showDiscountModalV2', discountCode, affiliateName, discountValue, discountType, affiliateId);
if (
!discountValue ||
discountValue === '0' ||
discountValue === 0 ||
discountValue === null ||
discountValue === undefined ||
discountValue === 'null'
)
return;
// Fetch customization from API
const customization = await fetchCustomization(affiliateId);
// If no customization exists, fall back to the original showDiscountModal
if (!customization) {
console.log('ℹ️ No customization available, falling back to default modal');
showDiscountModal(discountCode, affiliateName, discountValue, discountType, affiliateId);
return;
}
// Prevent duplicate modals
if (document.getElementById('__growi-modal-overlay')) return;
// Merge customization with defaults
const config = { ...defaultCustomization, ...customization };
// Check if popup is enabled
if (!config.popup_enabled) {
console.log('ℹ️ Popup is disabled by customization settings');
if (config.banner_enabled) {
window.growiCookieUtils.setCookie('discount_seen', affiliateId.toLowerCase());
showBanner(affiliateId, discountCode, customization);
}
return;
}
const headingText =
config.heading_text ||
`${affiliateName ? decodeURIComponent(affiliateName) : "We've"} sent you ${
discountType === 'percentage' ? `${discountValue}%` : `$${discountValue}`
} off!`;
const bodyText = config.body_text || 'Click below to redeem your discount code';
const buttonText = config.button_text || 'Redeem Coupon';
const helperText = config.helper_text || 'Your discount will automatically be applied at checkout';
const buttonMarkup = config.hide_button
? ''
: `
`;
const helperMarkup = `
${helperText}
`;
const overlay = document.createElement('div');
overlay.id = '__growi-modal-overlay';
overlay.style.cssText = `
display:block;
position:fixed;
top:0; left:0;
width:100%; height:100%;
background-color:rgba(0,0,0,0.5);
z-index:9998;
`;
const modal = document.createElement('div');
modal.className = '__growi_modal_content';
modal.style.cssText = `
display:block;
background-color:${config.background_color};
z-index:9999;
border-top:10px solid #2C2C2C;
border-radius:10px;
text-align:center;
margin:auto;
min-height:400px;
height:auto;
position:fixed;
top:50%; left:50%;
transform:translate(-50%, -50%);
width:90%; max-width:500px;
`;
// Add mobile-specific styles
const mobileStyles = document.createElement('style');
mobileStyles.textContent = `
@media (max-width: 768px) {
.__growi_modal_content {
top: calc(50% + 20px) !important;
}
}
@media (max-width: 480px) {
.__growi_modal_content {
top: calc(50% + 30px) !important;
}
}
`;
document.head.appendChild(mobileStyles);
modal.innerHTML = `
${config.emoji || '🎉'}
${headingText}
${bodyText}
${buttonMarkup}
${helperMarkup}
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
// Close handler
const closeButton = document.getElementById('__growi-close-modal');
if (closeButton) {
closeButton.onclick = () => {
window.growiCookieUtils.setCookie('discount_seen', affiliateId.toLowerCase(), -1);
overlay.remove();
};
}
// Redeem handler
const redeemBtn = document.getElementById('__growi-redeem-btn');
if (redeemBtn) {
redeemBtn.onclick = () => {
// Set loading state (preserve custom button colors)
redeemBtn.disabled = true;
redeemBtn.style.cursor = 'not-allowed';
redeemBtn.style.backgroundColor = config.button_color;
redeemBtn.style.color = config.button_text_color;
redeemBtn.innerHTML = `
Loading...
`;
// Add spinning animation
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
// Simulate loading delay then show discount code
setTimeout(() => {
// Replace button with discount code
redeemBtn.style.display = 'none';
// Create discount code container
const discountCodeDiv = document.createElement('div');
discountCodeDiv.id = '__growi-discount-code';
discountCodeDiv.style.cssText = `
background-color:#ecf0ff;
display:block;
width:90%;
margin:20px auto 0 auto;
text-align:center;
color:#3860fb;
font-size:25px;
padding:8px;
cursor:pointer;
border-radius:5px;
font-weight:bold;
font-family:-apple-system, Inter, BlinkMacSystemFont, Segoe UI, Roboto,
Helvetica Neue, Arial, Noto Sans, sans-serif;
`;
discountCodeDiv.innerHTML = `
${discountCode.toUpperCase()}

`;
// Add copy functionality
discountCodeDiv.onclick = () => {
navigator.clipboard
.writeText(discountCode.toUpperCase())
.then(() => {
// Show copied feedback under the code
let copiedText = document.getElementById('__growi-copied-text');
if (!copiedText) {
copiedText = document.createElement('div');
copiedText.id = '__growi-copied-text';
copiedText.style.cssText = `
font-size:0.9em;
color:#28a745;
margin-top:8px;
text-align:center;
`;
discountCodeDiv.parentNode.insertBefore(copiedText, discountCodeDiv.nextSibling);
}
copiedText.innerHTML = '✓ Discount applied!';
})
.catch(() => {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = discountCode.toUpperCase();
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
let copiedText = document.getElementById('__growi-copied-text');
if (!copiedText) {
copiedText = document.createElement('div');
copiedText.id = '__growi-copied-text';
copiedText.style.cssText = `
font-size:0.9em;
color:#28a745;
margin-top:8px;
text-align:center;
`;
discountCodeDiv.parentNode.insertBefore(copiedText, discountCodeDiv.nextSibling);
}
copiedText.innerHTML = '✓ Discount applied!';
});
};
// Insert the discount code div after the button
redeemBtn.parentNode.insertBefore(discountCodeDiv, redeemBtn.nextSibling);
}, 1500);
window.growiCookieUtils.setCookie('discount_seen', affiliateId.toLowerCase());
};
}
}
async function showBanner(affiliateId, discountCode, customizationData = null) {
// Prevent duplicate banners
if (document.getElementById('__growi-banner')) return;
// Fetch customization from API if not provided
const customization = customizationData || (await fetchCustomization(affiliateId));
// If no customization exists or banner not enabled, return
if (!customization || !customization.banner_enabled) {
console.log('ℹ️ Banner is disabled or no customization available');
return;
}
// Merge customization with defaults
const config = { ...defaultCustomization, ...customization };
const affiliateName = window.growiCookieUtils.getCookie('affiliate_name');
const discountValue = window.growiCookieUtils.getCookie('discount_value');
const discountType = window.growiCookieUtils.getCookie('discount_type');
const bannerHeading =
config.heading_text ||
`${affiliateName ? decodeURIComponent(affiliateName) : 'We'} sent you ${
discountType === 'percentage' ? `${discountValue}%` : `$${discountValue}`
} off!`;
const bannerSubtext = config.body_text || `Click below to redeem your discount code`;
const bannerButtonText = config.button_text || 'Redeem Coupon';
const bannerButtonMarkup = config.hide_button
? ''
: `
`;
// Create banner element
const banner = document.createElement('div');
banner.id = '__growi-banner';
banner.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
background-color: ${config.background_color};
color: ${config.text_color};
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
z-index: 9997;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border-bottom: 1px solid rgba(0,0,0,0.08);
font-family: -apple-system, Inter, BlinkMacSystemFont, Segoe UI, Roboto,
Helvetica Neue, Arial, Noto Sans, sans-serif;
gap: 20px;
box-sizing: border-box;
`;
banner.innerHTML = `
${config.emoji || '🎉'}
${bannerHeading}
${bannerSubtext}
${bannerButtonMarkup}
`;
// Add responsive styles and hover effects
const bannerStyles = document.createElement('style');
bannerStyles.textContent = `
#__growi-banner-btn:hover {
opacity: 0.9;
}
#__growi-banner-close:hover {
opacity: 1;
}
@media (max-width: 768px) {
#__growi-banner {
flex-direction: column;
align-items: stretch !important;
padding: 16px;
width: 100%;
}
#__growi-banner > div:first-child {
margin-bottom: 12px;
}
#__growi-banner-btn {
width: 100%;
}
#__growi-banner-close {
position: absolute;
top: 16px;
right: 16px;
}
}
`;
document.head.appendChild(bannerStyles);
// Append to body
document.body.appendChild(banner);
// Add body padding to prevent content from being hidden behind banner
const originalPaddingTop = document.body.style.paddingTop;
// Wait for banner to render to get actual height
setTimeout(() => {
const bannerBottom = banner.getBoundingClientRect().bottom;
document.body.style.paddingTop = `${bannerBottom}px`;
// Push down any existing sticky announcement bars so they appear below our banner
const stickyAnnouncementBars = document.querySelectorAll(
'[class*="announcement-bar"], [class*="announcement_bar"], [id*="announcement-bar"]'
);
stickyAnnouncementBars.forEach((el) => {
const style = getComputedStyle(el);
if (style.position === 'sticky') {
el.style.top = `${bannerBottom}px`;
}
});
}, 0);
// Store original padding for cleanup
banner.dataset.originalPadding = originalPaddingTop;
// Button click handler - always copy code to clipboard, and optionally open modal
const bannerBtn = document.getElementById('__growi-banner-btn');
if (bannerBtn) {
bannerBtn.onclick = () => {
const discountCode = window.growiCookieUtils.getCookie('discount_code');
const affiliateName = window.growiCookieUtils.getCookie('affiliate_name');
const discountType = window.growiCookieUtils.getCookie('discount_type');
const discountValue = window.growiCookieUtils.getCookie('discount_value');
if (discountCode && discountValue) {
// Always copy discount code to clipboard
const originalText = bannerBtn.innerHTML;
navigator.clipboard
.writeText(discountCode.toUpperCase())
.then(() => {
bannerBtn.innerHTML = '✓ Discount applied!';
bannerBtn.style.backgroundColor = '#28a745';
setTimeout(() => {
bannerBtn.innerHTML = originalText;
bannerBtn.style.backgroundColor = config.button_color;
}, 2000);
})
.catch(() => {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = discountCode.toUpperCase();
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
bannerBtn.innerHTML = '✓ Discount applied!';
bannerBtn.style.backgroundColor = '#28a745';
setTimeout(() => {
bannerBtn.innerHTML = originalText;
bannerBtn.style.backgroundColor = config.button_color;
}, 2000);
});
// Additionally show modal if enabled
if (config.popup_enabled) {
showDiscountModalV2(discountCode, affiliateName, discountValue, discountType, affiliateId);
}
}
};
}
// Close button handler
document.getElementById('__growi-banner-close').onclick = () => {
// Restore original padding
const originalPadding = banner.dataset.originalPadding || '';
document.body.style.paddingTop = originalPadding;
banner.remove();
// Store in cookie that banner was dismissed
window.growiCookieUtils.setCookie('banner_dismissed', affiliateId.toLowerCase(), 1); // 1 day
};
}
// ---- PAGE VIEW (exact referrer only) ----
function hasFiredPageView(visitorUid) {
const key = `growi_pv_sent::${visitorUid || 'anon'}::${location.pathname}`;
try {
if (sessionStorage.getItem(key)) return true;
} catch {}
if (window.growiCookieUtils) {
const ck = window.growiCookieUtils.getCookie(encodeURIComponent(key));
if (ck === '1') return true;
}
return false;
}
function markFiredPageView(visitorUid) {
const key = `growi_pv_sent::${visitorUid || 'anon'}::${location.pathname}`;
try {
sessionStorage.setItem(key, '1');
} catch {}
// ~30 minutes lock across tabs
window.growiCookieUtils && window.growiCookieUtils.setCookie(encodeURIComponent(key), '1', 0.02);
}
async function sendOneTimePageView(externalId) {
console.log('sendOneTimePageView', externalId);
if (!window.growiCookieUtils) return;
console.log('sendOneTimePageView1', externalId);
if (!externalId) return;
const affiliateId = window.growiCookieUtils.getCookie('growi_affiliate_id');
const visitorUid = window.growiCookieUtils.getCookie('growi_visitor_uid');
if (!affiliateId || affiliateId === 'null') return; // keep your current rule
console.log('sendOneTimePageView2', affiliateId);
if (hasFiredPageView(visitorUid)) return;
console.log('sendOneTimePageView3', visitorUid);
markFiredPageView(visitorUid);
// EXACT referrer only
const referrerRaw = document.referrer || null;
let referrerHost = null;
try {
referrerHost = referrerRaw ? new URL(referrerRaw).host : null;
} catch {}
// Referrer captured at script-execute time, before any same-host nav
// could blank document.referrer. Survives pushState / SPA-style nav
// inside in-app browsers.
const referrerInitialRaw =
(typeof window !== 'undefined' && typeof window.__growi_initial_referrer !== 'undefined'
? window.__growi_initial_referrer
: null) || null;
let referrerInitialHost = null;
try {
referrerInitialHost = referrerInitialRaw ? new URL(referrerInitialRaw).host : null;
} catch {}
// Get UTM parameters for Meta/Facebook ad tracking
const utmParams = window.growiCookieUtils.getUtmParams ? window.growiCookieUtils.getUtmParams() : {};
// Check if traffic is from Facebook/Meta based on utm_source
const isFacebookSource = utmParams.utm_source &&
['fb', 'facebook', 'meta'].includes(utmParams.utm_source.toLowerCase());
// Click IDs + first-touch snapshot for paid-search fraud detection
const clickIds = window.growiCookieUtils.getClickIds ? window.growiCookieUtils.getClickIds() : {};
const firstTouch = window.growiCookieUtils.getFirstTouch ? window.growiCookieUtils.getFirstTouch() : null;
// In-app-browser / platform inference from UA. Always safe to call —
// returns { platform: 'browser', in_app_browser: false, ... } when
// nothing matches.
const clientPlatform = window.growiCookieUtils.detectClientPlatform
? window.growiCookieUtils.detectClientPlatform()
: { platform: 'browser', in_app_browser: false, user_agent: '', is_mobile: false };
// Prefer the value stamped onto first_touch (computed when the affiliate
// cookie was set / backfilled — most accurate). Fall back to recomputing
// live so payloads without a first_touch still carry signal.
let inferredSource = (firstTouch && firstTouch.inferred_source) || null;
if (!inferredSource) {
if (clientPlatform && clientPlatform.platform && clientPlatform.platform !== 'browser') {
inferredSource = clientPlatform.platform;
} else if (referrerHost) {
inferredSource = referrerHost;
} else if (clickIds.gclid) {
inferredSource = 'google_ads';
} else if (clickIds.msclkid) {
inferredSource = 'bing_ads';
} else if (clickIds.fbclid) {
inferredSource = 'facebook';
} else if (clickIds.ttclid) {
inferredSource = 'tiktok';
} else {
inferredSource = 'direct';
}
}
const event = {
name: 'page_viewed',
id: (crypto.randomUUID && crypto.randomUUID()) || `${Date.now()}-${Math.random().toString(16).slice(2)}`,
timestamp: new Date().toISOString(),
data: {
title: document.title || null,
path: location.pathname,
url: location.href,
referrer_raw: referrerRaw,
referrer_host: referrerHost,
referrer_initial_raw: referrerInitialRaw,
referrer_initial_host: referrerInitialHost,
utm_source: utmParams.utm_source || null,
utm_medium: utmParams.utm_medium || null,
utm_campaign: utmParams.utm_campaign || null,
utm_content: utmParams.utm_content || null,
utm_term: utmParams.utm_term || null,
utm_id: utmParams.utm_id || null,
// Meta Ad ID (only set if utm_source is fb/facebook/meta)
meta_ad_id: isFacebookSource ? (utmParams.tw_id || utmParams.utm_id || utmParams.utm_content || null) : null,
click_ids: clickIds,
first_touch: firstTouch,
inferred_source: inferredSource,
client_platform: clientPlatform,
},
clientId: visitorUid,
};
const body = {
tracking_event: {
event_name: event.name,
occurred_at: event.timestamp,
visitor_uid: visitorUid,
event_data: {
pageEventId: event.id,
timeStamp: event.timestamp,
data: { ...event.data, clientId: event.clientId },
},
},
campaign_affiliate_id: affiliateId,
external_id: `gid://shopify/Shop/${externalId}`,
conversion_platform: 'shopify',
};
// Optional server dedupe
const idempKey = `${visitorUid || 'anon'}::${location.pathname}::${Math.floor(Date.now() / 60000)}`;
try {
await fetch('https://api.growi.io/api/v1/tracking_events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': idempKey,
},
body: JSON.stringify(body),
});
} catch (err) {
console.warn('page_viewed send failed', err);
}
}
function waitForUrlParams(retries = 10, delay = 200) {
// Check if URL has any parameters yet
const hasParams = window.location.search.length > 0;
const affiliateParam =
new URLSearchParams(window.location.search).get('a') ||
new URLSearchParams(window.location.search).get('snowball') ||
new URLSearchParams(window.location.search).get('growi');
// If we found affiliate params or exhausted retries, proceed
if (affiliateParam || retries <= 0 || hasParams) {
console.log(`✅ URL stabilized after ${10 - retries} attempts. Affiliate param:`, affiliateParam);
initializeAffiliate();
return;
}
// Wait and retry - Instagram WebView needs time to stabilize
console.log(`⏳ Waiting for URL params to stabilize... (${retries} retries left)`);
setTimeout(() => waitForUrlParams(retries - 1, delay), delay);
}
function waitForUtils(retries = 5) {
console.log(`🔍 [WAIT-FOR-UTILS] Attempt ${6 - retries}/5 - Checking for window.growiCookieUtils...`);
console.log(
`🔍 [WAIT-FOR-UTILS] window.growiCookieUtils exists?`,
typeof window.growiCookieUtils !== 'undefined'
);
console.log(`🔍 [WAIT-FOR-UTILS] Document readyState:`, document.readyState);
console.log(`🔍 [WAIT-FOR-UTILS] Timestamp:`, new Date().toISOString());
if (window.growiCookieUtils) {
console.log('✅ [WAIT-FOR-UTILS] Cookie utils found! Available methods:', Object.keys(window.growiCookieUtils));
// Don't immediately read params - wait for URL to stabilize first
waitForUrlParams();
} else if (retries > 0) {
console.log(`⏳ [WAIT-FOR-UTILS] Not found yet, retrying in 100ms... (${retries} retries left)`);
setTimeout(() => waitForUtils(retries - 1), 100);
} else {
console.error('❌ [WAIT-FOR-UTILS] Growi cookie utilities not loaded after 5 retries (500ms total)');
console.error('❌ [WAIT-FOR-UTILS] All window properties:', Object.keys(window));
console.error('❌ [WAIT-FOR-UTILS] Check if cookie-utils.js is loading correctly');
}
}
function initializeAffiliate() {
console.log('🎯 [INIT-AFFILIATE] initializeAffiliate() called');
console.log(
'🎯 [INIT-AFFILIATE] window.growiCookieUtils exists?',
typeof window.growiCookieUtils !== 'undefined'
);
if (!window.growiCookieUtils) {
console.error('❌ [INIT-AFFILIATE] Cookie utils not available - CANNOT PROCEED');
console.error('❌ [INIT-AFFILIATE] This usually means cookie-utils.js failed to load or execute');
return;
}
console.log('✅ [INIT-AFFILIATE] Cookie utils available, proceeding...');
// Now read affiliate params after URL has stabilized
const urlAffiliateId =
new URLSearchParams(window.location.search).get('a') ||
new URLSearchParams(window.location.search).get('snowball') ||
new URLSearchParams(window.location.search).get('growi');
// Get affiliate info from cookies
const cookieInfo = window.growiCookieUtils.manageAffiliateCookies();
// Determine which affiliate ID to use (URL param takes precedence)
const affiliateId = urlAffiliateId || cookieInfo.affiliateId;
const visitorUid = cookieInfo.visitorUid;
// Brand-built Meta ads carry only a gcs_id (no affiliate param/cookie).
// gcs_id alone is enough to stamp the cart so the backend can attribute
// the sale.
const gcsId =
window.growiCookieUtils.getQueryParam('gcs_id') ||
window.growiCookieUtils.getCookie('growi_gcs_id');
const autoResponderId =
window.growiCookieUtils.getQueryParam('igar_id') ||
cookieInfo.autoResponderId ||
window.growiCookieUtils.getCookie('growi_auto_responder_id');
// Creator-set sub-ID for platform-level tracking (e.g. ?sub_id=instagram)
const subId =
window.growiCookieUtils.getQueryParam('sub_id') ||
cookieInfo.subId ||
window.growiCookieUtils.getCookie('growi_sub_id');
console.log('🔍 Final affiliate detection:', {
urlAffiliateId,
cookieAffiliateId: cookieInfo.affiliateId,
finalAffiliateId: affiliateId,
gcsId,
autoResponderId,
subId,
});
// Run if we have an affiliate from either source OR a gcs_id (brand-built
// Meta ad). visitorUid is always created by manageAffiliateCookies.
if ((affiliateId || gcsId || autoResponderId) && visitorUid) {
// Get UTM parameters to pass to order
const utmParams = window.growiCookieUtils.getUtmParams ? window.growiCookieUtils.getUtmParams() : {};
// Check if traffic is from Facebook/Meta based on utm_source
const isFacebookSource = utmParams.utm_source &&
['fb', 'facebook', 'meta'].includes(utmParams.utm_source.toLowerCase());
// Build cart attributes
const cartAttributes = {
__growi_visitor_uid: visitorUid,
};
if (affiliateId) cartAttributes.__growi_affiliate_id = affiliateId;
if (gcsId) cartAttributes.__growi_gcs_id = gcsId;
if (autoResponderId) cartAttributes.__growi_auto_responder_id = autoResponderId;
if (subId) cartAttributes.__growi_sub_id = subId;
if (utmParams.utm_source) cartAttributes.__growi_utm_source = utmParams.utm_source;
if (utmParams.utm_medium) cartAttributes.__growi_utm_medium = utmParams.utm_medium;
if (utmParams.utm_campaign) cartAttributes.__growi_utm_campaign = utmParams.utm_campaign;
if (utmParams.utm_content) cartAttributes.__growi_utm_content = utmParams.utm_content;
if (utmParams.utm_term) cartAttributes.__growi_utm_term = utmParams.utm_term;
if (utmParams.utm_id) cartAttributes.__growi_utm_id = utmParams.utm_id;
if (utmParams.campaign_id) cartAttributes.__growi_campaign_id = utmParams.campaign_id;
if (utmParams.user_id) cartAttributes.__growi_user_id = utmParams.user_id;
if (utmParams.campaign_affiliate_id) cartAttributes.__growi_campaign_affiliate_id = utmParams.campaign_affiliate_id;
if (utmParams.asset_id) cartAttributes.__growi_asset_id = utmParams.asset_id;
if (utmParams.tw_id) cartAttributes.__growi_tw_id = utmParams.tw_id;
if (isFacebookSource && (utmParams.tw_id || utmParams.utm_id || utmParams.utm_content)) {
cartAttributes.__growi_meta_ad_id = utmParams.tw_id || utmParams.utm_id || utmParams.utm_content;
}
fetch('/cart/update.js', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
attributes: cartAttributes,
}),
})
.then((res) => {
// Extract x-shopid from response headers
const externalId = res.headers.get('x-shopid');
console.log('🏪 Shop ID extracted from headers:', externalId);
return res.json().then((data) => ({ data, externalId }));
})
.then(({ data, externalId }) => {
console.log('✅ Cart attributes updated with affiliate & visitor ID', data);
// --- Discount code logic start ---
// Only fetch if we have an affiliateId and no discount_code cookie
// Only fetch if we have an affiliateId and no discount_code cookie
const discountCode = window.growiCookieUtils.getCookie('discount_code');
const affiliateName = window.growiCookieUtils.getCookie('affiliate_name');
const discountType = window.growiCookieUtils.getCookie('discount_type');
const discountValue = window.growiCookieUtils.getCookie('discount_value');
const campaignType = window.growiCookieUtils.getCookie('campaign_type');
const discountSeen = window.growiCookieUtils.getCookie('discount_seen');
const isDiscountSeen = discountSeen && discountSeen === `${affiliateId}`.toLowerCase();
console.log('Test', affiliateId, discountCode, affiliateId !== 'null', externalId);
if (affiliateId && !!discountCode && affiliateId !== 'null' && isDiscountSeen) {
const bannerDismissed = window.growiCookieUtils.getCookie('banner_dismissed');
const isBannerDismissed = bannerDismissed && bannerDismissed === `${affiliateId}`.toLowerCase();
if (!isBannerDismissed) {
// Check if banner is enabled before showing
fetchCustomization(affiliateId).then((customization) => {
if (customization && customization.banner_enabled) {
showBanner(affiliateId, discountCode, customization);
}
});
}
sendOneTimePageView(externalId);
} else if (
affiliateId &&
(!discountCode || (!!discountCode && !isDiscountSeen)) &&
affiliateId !== 'null'
) {
const currentUrl = window.location.href;
// gcs_id (Growi Content Submission ID) lets the backend attribute
// brand-built Meta ads. Prefer the live URL param, fall back to the
// cookie captured on the landing page.
const gcsId =
window.growiCookieUtils.getQueryParam('gcs_id') ||
window.growiCookieUtils.getCookie('growi_gcs_id');
const gcsQuery = gcsId ? `&gcs_id=${encodeURIComponent(gcsId)}` : '';
fetch(
`https://api.growi.io/api/v1/campaign_affiliates/${encodeURIComponent(
affiliateId
)}/discount_code?url=${encodeURIComponent(currentUrl)}${gcsQuery}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
)
.then((response) => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then((data) => {
if (data.success) {
cookieDuration = data.cookie_duration;
if (!!cookieDuration) {
updateAffiliateCookieDuration(cookieDuration);
console.log('✅ Affiliate cookie duration updated:', cookieDuration);
}
}
if (data.success && data.discount_code) {
// Set discount_code cookie for 30 days
window.growiCookieUtils.setCookie('discount_code', data.discount_code, 30);
// Set new response format cookies
if (data.affiliate_name) {
window.growiCookieUtils.setCookie('affiliate_name', data.affiliate_name, 30);
}
if (data.discount_type) {
window.growiCookieUtils.setCookie('discount_type', data.discount_type, 30);
}
if (data.discount_value) {
window.growiCookieUtils.setCookie('discount_value', data.discount_value, 30);
}
if (data.campaign_type) {
window.growiCookieUtils.setCookie('campaign_type', data.campaign_type, 30);
}
console.log('✅ Discount code cookies set:', data.discount_code);
// Show discount modal only for ambassador programs and if not seen yet
const discountSeen = window.growiCookieUtils.getCookie('discount_seen');
const isDiscountSeen = discountSeen && discountSeen === `${affiliateId}`.toLowerCase();
if (!isDiscountSeen) {
if (data.is_v2) {
showDiscountModalV2(
data.discount_code,
data.affiliate_name,
data.discount_value,
data.discount_type,
affiliateId
);
} else {
showDiscountModal(
data.discount_code,
data.affiliate_name,
data.discount_value,
data.discount_type,
affiliateId
);
}
} else {
// If modal was already seen, show banner instead (if not dismissed)
const bannerDismissed = window.growiCookieUtils.getCookie('banner_dismissed');
const isBannerDismissed = bannerDismissed && bannerDismissed === `${affiliateId}`.toLowerCase();
if (!isBannerDismissed) {
// Check if banner is enabled before showing
fetchCustomization(affiliateId).then((customization) => {
if (customization && customization.banner_enabled) {
showBanner(affiliateId, data.discount_code, customization);
}
});
}
}
}
})
.catch((error) => {
console.error('There has been a problem with your fetch operation:', error);
});
}
// --- Discount code logic end ---
sendOneTimePageView(externalId);
})
.catch((err) => {
console.error('❌ Failed to update cart attributes', err);
});
} else {
console.log('ℹ️ No affiliate data to process');
}
}
// Start the process - wait for utils first, then URL stability
console.log('🚀 [GROWI-EMBED] Starting waitForUtils() immediately...');
console.log('🚀 [GROWI-EMBED] Current timestamp:', new Date().toISOString());
waitForUtils();
// Also try on window.load as a fallback for very slow WebViews
window.addEventListener('load', () => {
setTimeout(() => {
console.log('🔄 [WINDOW-LOAD] Window load backup trigger fired (500ms after window.load)');
console.log('🔄 [WINDOW-LOAD] window.growiCookieUtils exists?', typeof window.growiCookieUtils !== 'undefined');
initializeAffiliate();
}, 500);
});
})();
Skip to main content
- Choosing a selection results in a full page refresh.
- Opens in a new window.