Rastreamento no Shopify
Resumo
Conecta o Shopify diretamente ao Varify e analisa os teus experimentos diretamente com os dados de conversão e receita do Shopify. A integração só funciona em conjunto com o uso do GA4, já que o número de visitantes é importado do GA4.
Como usar os dados de rastreamento do Shopify
Passo 1: Ativar a importação de dados do Shopify
Vá para „Tracking Setup“ e clique na aba „Advanced Setup“. Aqui, ative a configuração „Use Shopify Data“.
Passo 2: Criar um pixel personalizado com o código de rastreamento da Varify
Clica em "Settings" na tua loja Shopify e abre "Customer events". Depois clica em "Add custom pixel" e nomeia o pixel: "varify-checkout". Depois insere o seguinte código:
Código Padrão
Usa este código se a tua loja Shopify aceitar pagamentos apenas numa moeda.
let storage;
async function getVarifyData() {
if (storage) {
return storage;
}
storage = (async () => {
try {
const raw = await browser.localStorage.getItem('varify-data');
return raw ? JSON.parse(raw) : {};
} catch (error) {
// Cache nicht dauerhaft vergiften: nach Fehler zuruecksetzen,
// damit der naechste Aufruf erneut versucht statt still tot zu bleiben.
storage = undefined;
console.error('Varify: error reading local storage', error);
return {};
}
})();
return storage;
}
analytics.subscribe('varify', async (event) => {
const {
varify_experimentId,
varify_variationId,
teamId,
storageType = 'localStorage',
} = event.customData || {};
if (!varify_experimentId || !teamId) {
console.warn('Varify: event missing experimentId or teamId.');
return;
}
const validVariationId =
varify_variationId !== undefined ? varify_variationId : null;
const existingData = await getVarifyData();
if (!existingData.data) {
existingData.data = [];
}
const experimentEntry = {
experimentId: varify_experimentId,
variationId: validVariationId,
timestamp: Date.now(),
};
existingData.data = existingData.data.filter(
(entry) => entry.experimentId !== varify_experimentId
);
existingData.data.push(experimentEntry);
existingData.teamId = teamId;
existingData.storageType = storageType;
await browser.localStorage.setItem(
'varify-data',
JSON.stringify(existingData)
);
});
const WINDOW_MS = 28 * 24 * 60 * 60 * 1000;
const pruneEntries = (entries) => {
const threshold = Date.now() - WINDOW_MS;
return (entries || []).filter(
(entry) => entry.timestamp && entry.experimentId && entry.timestamp >= threshold
);
};
analytics.subscribe('checkout_started', async (event) => {
const existingData = await getVarifyData();
if (!existingData.data || existingData.data.length === 0) {
return;
}
existingData.data = pruneEntries(existingData.data);
await browser.localStorage.setItem(
'varify-data',
JSON.stringify(existingData)
);
});
analytics.subscribe('checkout_completed', async (event) => {
const orderId = event.data?.checkout?.order?.id;
const orderRevenue = event.data?.checkout?.subtotalPrice?.amount;
// 0 is a valid revenue (100% discount) and must not count as missing.
if (!orderId || orderRevenue == null) {
console.warn('Varify: checkout_completed missing orderId or revenue.');
return;
}
const parsedData = await getVarifyData();
if (!parsedData.data || parsedData.data.length === 0 || !parsedData.teamId) {
console.warn('Varify: no valid data in local storage.');
return;
}
// The order-status page refires checkout_completed on every visit.
if (!Array.isArray(parsedData.sentOrders)) {
parsedData.sentOrders = [];
}
if (parsedData.sentOrders.includes(orderId)) {
return;
}
parsedData.data = pruneEntries(parsedData.data);
if (parsedData.data.length === 0) {
return;
}
const storedAccountId = parsedData.teamId;
const experiments = parsedData.data.map((entry) => {
return [entry.experimentId, entry.variationId];
});
const storageType = parsedData.storageType;
const storeHandle = init.data?.shop?.myshopifyDomain?.replace('.myshopify.com', '');
const lineItems = event.data?.checkout?.lineItems;
let cartProducts = [];
if (lineItems && Array.isArray(lineItems)) {
cartProducts = lineItems
.map((item) => {
const productId = item.variant?.product?.id?.split("/").pop();
const variantId = item.variant?.id?.split("/").pop();
if (productId && variantId) {
return {
productId,
variantId,
};
}
return null;
})
.filter((p) => p !== null);
}
try {
await fetch('https://ecommerce.varify.io/store_data', {
method: 'POST',
keepalive: true,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
experiments,
orderId,
orderRevenue,
accountId: storedAccountId,
storageType: storageType,
storeHandle,
cartProducts,
}),
});
parsedData.sentOrders = [...parsedData.sentOrders, orderId].slice(-50);
await browser.localStorage.setItem('varify-data', JSON.stringify(parsedData));
} catch (error) {
console.error('Varify: error sending data', error);
}
});
Código Multi-Moeda
Use este código se a sua loja Shopify suportar pagamentos em várias moedas.
let storage;
const SHOP_CURRENCY = init.data?.shop?.paymentSettings?.currencyCode || 'EUR';
const FX_API = 'https://api.frankfurter.dev/v1/latest';
const FX_TTL_MS = 6 * 60 * 60 * 1000;
const FX_TIMEOUT_MS = 2000;
async function getVarifyData() {
if (storage) {
return storage;
}
storage = (async () => {
try {
const raw = await browser.localStorage.getItem('varify-data');
return raw ? JSON.parse(raw) : {};
} catch (error) {
// Reset so the next call retries instead of caching a poisoned promise.
storage = undefined;
console.error('Varify: error reading local storage', error);
return {};
}
})();
return storage;
}
// Returns units of <currency> per 1 <SHOP_CURRENCY>, or null. null => do not send.
async function getFxRate(currency) {
const cacheKey = `varify-fx-${SHOP_CURRENCY}-${currency}`;
try {
const cached = await browser.localStorage.getItem(cacheKey);
if (cached) {
const { rate, ts } = JSON.parse(cached);
if (rate && ts && Date.now() - ts < FX_TTL_MS) {
return rate;
}
}
} catch (error) {
// Broken cache -> fetch fresh below.
}
try {
const url = `${FX_API}?from=${encodeURIComponent(SHOP_CURRENCY)}&to=${encodeURIComponent(currency)}`;
let res;
if (typeof AbortController === 'function') {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), FX_TIMEOUT_MS);
try {
res = await fetch(url, { signal: controller.signal });
} finally {
clearTimeout(timer);
}
} else {
res = await fetch(url);
}
if (!res.ok) {
console.warn(`Varify: FX API returned ${res.status} for ${currency}.`);
return null;
}
const json = await res.json();
const rate = json?.rates?.[currency];
if (typeof rate !== 'number' || !isFinite(rate) || rate <= 0) {
console.warn(`Varify: FX API gave no usable rate for ${currency}.`, json);
return null;
}
await browser.localStorage.setItem(
cacheKey,
JSON.stringify({ rate, ts: Date.now() })
);
return rate;
} catch (error) {
console.warn(`Varify: FX lookup failed for ${currency}.`, error);
return null;
}
}
analytics.subscribe('varify', async (event) => {
const {
varify_experimentId,
varify_variationId,
teamId,
storageType = 'localStorage',
} = event.customData || {};
if (!varify_experimentId || !teamId) {
console.warn('Varify: event missing experimentId or teamId.');
return;
}
const validVariationId =
varify_variationId !== undefined ? varify_variationId : null;
const existingData = await getVarifyData();
if (!existingData.data) {
existingData.data = [];
}
const experimentEntry = {
experimentId: varify_experimentId,
variationId: validVariationId,
timestamp: Date.now(),
};
existingData.data = existingData.data.filter(
(entry) => entry.experimentId !== varify_experimentId
);
existingData.data.push(experimentEntry);
existingData.teamId = teamId;
existingData.storageType = storageType;
await browser.localStorage.setItem(
'varify-data',
JSON.stringify(existingData)
);
});
const WINDOW_MS = 28 * 24 * 60 * 60 * 1000;
const pruneEntries = (entries) => {
const threshold = Date.now() - WINDOW_MS;
return (entries || []).filter(
(entry) => entry.timestamp && entry.experimentId && entry.timestamp >= threshold
);
};
analytics.subscribe('checkout_started', async (event) => {
const existingData = await getVarifyData();
if (!existingData.data || existingData.data.length === 0) {
return;
}
existingData.data = pruneEntries(existingData.data);
await browser.localStorage.setItem(
'varify-data',
JSON.stringify(existingData)
);
// Warm the rate while the customer is still typing.
const currency = event.data?.checkout?.currencyCode;
if (currency && currency !== SHOP_CURRENCY) {
getFxRate(currency);
}
});
analytics.subscribe('checkout_completed', async (event) => {
const orderId = event.data?.checkout?.order?.id;
const money = event.data?.checkout?.subtotalPrice;
let orderRevenue = money?.amount;
const orderCurrency = money?.currencyCode;
// 0 is a valid revenue (100% discount) and must not count as missing.
if (!orderId || orderRevenue == null || !orderCurrency) {
console.warn('Varify: checkout_completed missing orderId, revenue or currency.');
return;
}
// subtotalPrice is presentment currency; the event carries no rate or shop-money
// amount, and /store_data books the number as shop currency. Convert here, or skip.
if (orderCurrency !== SHOP_CURRENCY) {
const rate = await getFxRate(orderCurrency);
if (!rate) {
console.warn(
`Varify: skipping order ${orderId} — ${orderRevenue} ${orderCurrency}, no FX rate available.`
);
return;
}
const converted = +(orderRevenue / rate).toFixed(2);
console.info(
`Varify: converted order ${orderId} — ${orderRevenue} ${orderCurrency} @ ${rate} -> ${converted} ${SHOP_CURRENCY}.`
);
orderRevenue = converted;
}
const parsedData = await getVarifyData();
if (!parsedData.data || parsedData.data.length === 0 || !parsedData.teamId) {
console.warn('Varify: no valid data in local storage.');
return;
}
// The order-status page refires checkout_completed on every visit.
if (!Array.isArray(parsedData.sentOrders)) {
parsedData.sentOrders = [];
}
if (parsedData.sentOrders.includes(orderId)) {
return;
}
parsedData.data = pruneEntries(parsedData.data);
if (parsedData.data.length === 0) {
return;
}
const storedAccountId = parsedData.teamId;
const experiments = parsedData.data.map((entry) => {
return [entry.experimentId, entry.variationId];
});
const storageType = parsedData.storageType;
const storeHandle = init.data?.shop?.myshopifyDomain?.replace('.myshopify.com', '');
const lineItems = event.data?.checkout?.lineItems;
let cartProducts = [];
if (lineItems && Array.isArray(lineItems)) {
cartProducts = lineItems
.map((item) => {
const productId = item.variant?.product?.id?.split("/").pop();
const variantId = item.variant?.id?.split("/").pop();
if (productId && variantId) {
return {
productId,
variantId,
};
}
return null;
})
.filter((p) => p !== null);
}
try {
await fetch('https://ecommerce.varify.io/store_data', {
method: 'POST',
keepalive: true,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
experiments,
orderId,
orderRevenue,
// Already converted above; always shop currency so a future server-side
// conversion is a no-op, not a double conversion.
orderCurrency: SHOP_CURRENCY,
accountId: storedAccountId,
storageType: storageType,
storeHandle,
cartProducts,
}),
});
parsedData.sentOrders = [...parsedData.sentOrders, orderId].slice(-50);
await browser.localStorage.setItem('varify-data', JSON.stringify(parsedData));
} catch (error) {
console.error('Varify: error sending data', error);
}
});
Depois de adicionar o código, clique em salvar. Verifique também se as suas permissões estão configuradas corretamente, caso contrário o script não será executado se faltar uma permissão de utilizador. Depois clique em "Connect". A integração está agora instalada com sucesso.
Passo 3: Adicionar Shopify Goals à sua experiência
Certifique-se de que a sua configuração de tracking está feita e que selecionou o GA4 como fornecedor de tracking e que o item "Test Evaluation" está definido como "In Varify and GA4". Se for esse o caso, um "Results Link" irá agora aparecer para experiências "recém-criadas" no canto superior direito da experiência iniciada.
Clicar no "Results Link" abre o relatório de resultados correspondente da experiência. Agora podes adicionar "Conversions" ou "Revenue" como objetivo, clicando em "Add Shopify Goal". Os visitantes do objetivo são obtidos a partir do GA4. O valor de conversão e o valor de receita vêm diretamente do Shopify.
Importante: os dados do GA4 só são atualizados após aproximadamente 24 horas. Os dados do Shopify são atualizados em tempo real.
Filtro de produtos no relatório de resultados
Analisa o desempenho de determinados produtos na tua experiência A/B. Podes observar produtos individuais ou variantes de produtos. Para isso, insere um ID de produto ou vários IDs no campo, separados por vírgulas.