• Seguimiento Shopify

    Índice

    Corto y dulce

    Conecta Shopify directamente con Varify y analiza tus experimentos directamente con los datos de conversión e ingresos de Shopify. La integración solo funciona junto con el uso de GA4, ya que el número de visitantes se importa desde GA4.

    Cómo utilizar los datos de seguimiento de Shopify

    Paso 1: Activar la importación de datos de Shopify

    Vaya a "Configuración de seguimiento" y haga clic en la pestaña "Configuración avanzada". Activa aquí el ajuste "Usar datos de Shopify".

    Paso 2: Crear un píxel personalizado con el código de seguimiento de Varify

    Haz clic en "Configuración" en tu tienda Shopify y abre "Eventos de clientes". A continuación, haga clic en "Añadir píxel personalizado" y nombre el píxel: "varify-checkout". A continuación, inserta el siguiente código:

    Código estándar

    Utiliza este código si en tu tienda de Shopify solo se puede pagar en una moneda.

    				
    					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 multidivisa

    Utiliza este código si en tu tienda de Shopify se puede pagar en varias divisas.

    				
    					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);
      }
    });
    				
    			

    Después de añadir el código, haga clic en Guardar. Comprueba también que tus permisos están configurados correctamente, de lo contrario el script no se ejecutará si falta algún permiso de usuario. A continuación, haga clic en "Conectar". La integración se ha instalado correctamente.

    Paso 3: Añade objetivos de Shopify a tu experimento

    Asegúrese de que su configuración de seguimiento está configurada y que ha seleccionado GA4 como proveedor de seguimiento y que el elemento "Evaluación de la prueba" está configurado como "En Varify y GA4". Si es así, aparecerá un "Enlace de resultados" para los experimentos "recién creados" en la parte superior derecha del experimento iniciado. 

    Al hacer clic en el "Enlace de Resultados" se abre el correspondiente informe de resultados del experimento. Ahora puede hacer clic en "Añadir objetivo de Shopify" para añadir "Conversiones" o "Ingresos" como objetivo. Los visitantes del objetivo se toman de GA4. El valor de conversión y el valor de ingresos vienen directamente de Shopify.

    Importante: Los datos de GA4 sólo se actualizan después de aprox. 24 horas. Los datos de Shopify se actualizan en tiempo real.

    Filtro de productos en el informe de resultados

    Analice el rendimiento de determinados productos en su prueba A/B. Puede analizar productos individuales o variantes de productos. Para ello, introduce un ID de producto o varios ID en el campo, separados por comas.

  • Primeros pasos