• Suivi Shopify

    Table des matières

    En bref

    Connecte Shopify directement à Varify et analyse tes expériences directement avec les données de conversion et de revenus de Shopify. L'intégration ne fonctionne que conjointement avec l'utilisation de GA4, car le nombre de visiteurs est importé depuis GA4.

    Comment utiliser les données de suivi Shopify ?

    Étape 1 : Activer l'importation de données Shopify

    Va dans "Tracking Setup" et clique sur l'onglet "Advanced Setup". Active ici le paramètre "Use Shopify Data".

    Étape 2 : Créer un pixel personnalisé avec le code de suivi Varify

    Clique sur "Settings" dans ton Shopify Store et ouvre "Customer events". Clique ensuite sur "Add custom pixel" et nomme le pixel : "varify-checkout". Ensuite, insère le code suivant :

    Code standard

    Utilise ce code si ta boutique Shopify n'accepte qu'une seule devise.

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

    Code multidevises

    Utilise ce code si ta boutique Shopify permet d'effectuer des paiements dans plusieurs devises.

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

    Une fois que tu as ajouté le code, clique sur enregistrer. Vérifie en outre que tes permissions sont correctement définies, sinon le script ne sera pas exécuté s'il manque une permission d'utilisateur. Clique ensuite sur "Connect". L'intégration est maintenant installée avec succès.

    Étape 3 : Ajoute des objectifs Shopify à ton expérience

    Assure-toi que ta configuration de suivi est configurée et que tu as sélectionné GA4 comme fournisseur de suivi et que le point "Test Evaluation" est réglé sur "In Varify and GA4". Si c'est le cas, un "Results Link" apparaît désormais en haut à droite de l'expérience lancée pour les expériences "nouvellement créées". 

    En cliquant sur le lien "Results", le rapport de résultats correspondant à l'expérience s'ouvre. En cliquant sur "Add Shopify Goal", tu peux maintenant ajouter soit "Conversions" soit "Revenue" comme objectif. Les visiteurs du goal sont repris de GA4. La valeur de conversion et la valeur de revenu proviennent directement de Shopify.

    Important : les données GA4 ne sont mises à jour qu'après environ 24 heures. Les données de Shopify sont mises à jour en temps réel.

    Filtre de produits dans le rapport de résultats

    Analyse la performance de certains produits dans ton test A/B. Tu peux examiner soit des produits individuels, soit des variantes de produits. Pour ce faire, saisis dans le champ un ID de produit ou plusieurs ID, séparés par des virgules.

  • Premiers pas