REPUBLIKEN
dataLayer-kravspec för den nya köpfunneln, observerade luckor och buggar att åtgärda inför lansering
Vad vi har granskat och vad det betyder inför lanseringen av nya checkouten.
Vi har gått igenom hela den nya köpfunneln på utvecklingsmiljön och kartlagt exakt vad som skickas till dataLayer i varje steg, hela vägen fram till genomfört testköp. Den här rapporten är ett konkret kravspec på vad den nya checkouten ska pusha, plus de buggar vi hittade som behöver åtgärdas innan lansering.
Grunden är god: bygget skickar redan flera funnel-events och ett purchase-event med en ecommerce-liknande struktur, och hela valet kodas i URL-state. Men det finns tre saker som behöver fixas, och en av dem dubbelräknar konverteringar i Google.
Det mesta löses genom att pusha data som redan finns i appens state. Den enskilt viktigaste åtgärden är att se till att purchase-eventet bara pushas en gång och bär ett konsekvent transaction_id, så att Google och Meta kan deduplicera korrekt.
Events som faktiskt fyrar idag, observerat genom hela funneln på utvecklingsmiljön.
| Steg | Event idag | Data |
|---|---|---|
| Funnel laddas | purchase_funnel_loaded | user_data (email, telefon) |
| Gym valt | inget event | gym-id hamnar i URL, ingen push |
| Medlemskapstyp | inget event | Ordinarie / Student / Senior |
| Åldersgrupp | purchase_funnel_age_selection | age_group |
| Tier (Platinum/Premium) | inget event | syns i UI, ej i dataLayer |
| Tillval togglat | inget event | bindningstid, passerkort, förtur |
| Sammanfattning | purchase_funnel_summary | endast user_data, ingen valdata |
| Personnummer | purchase_funnel_personal_id | status, member_age_group |
| Kontakt + order | initiate_checkout, checkout_loaded | city, user_data (email + telefon) |
| Köp klart | purchase (×2) | rik data + items[], men club: null |
Notera namn-inkonsekvensen: de flesta events har prefixet purchase_funnel_*, men initiate_checkout och checkout_loaded följer inte mönstret. Vi rekommenderar att allt standardiseras under purchase_funnel_*, så att GTM-triggers blir förutsägbara.
Tre saker som behöver vara på plats utöver själva eventen. Alla påverkar mätdata direkt.
På bekräftelsesidan pushas purchase två gånger med identiskt innehåll. Det ser ut att vara en remount eller en effekt som körs två gånger (t.ex. React StrictMode). Eventet ska fyra exakt en gång per genomfört köp, lägg en idempotensvakt.
Köpet ska samtidigt bära ett stabilt, unikt transaction_id (och event_id), så att konverteringar kan dedupliceras nedströms mot Google och Meta. Utan det riskerar varje köp att räknas dubbelt.
Det viktigaste eventet saknar gym. Utan gym på köpet går det inte att segmentera konverteringar per klubb eller region, eller bygga lokala målgrupper. Gym ska följa med från gym-valet hela vägen till purchase.
På checkout-steget läggs ssn, email och phone som query-parametrar i URL:en. Det läcker personuppgifter till GTM:s history-events, referrer-headers och serverloggar. Håll dem i state eller POST-body, aldrig i query-strängen.
Gäller alla events i specen.
purchase_funnel_*. Köp-eventet heter purchase (standardnamn, rör ej). Döp om initiate_checkout och checkout_loaded enligt katalogen.data-nyckel (samma konvention som idag). Ta bort tomma {data: null}-pushar.user_data skickas bara med marknadsförings-/statistik-consent (Cookiebot). Utan consent: pusha eventet men med fälten som null.Samma struktur på varje event, fylls på progressivt. Skicka null för det som inte är känt ännu. Idag tappas telefon på purchase och namn/kön/födelsedatum saknas helt, trots att de går att härleda ur personnumret.
user_data: { email_address: 'eva.svensson@example.se', // normaliserad, gemener phone_number: '+46701234567', // E.164 address: { first_name: 'eva', last_name: 'svensson', city: 'ängelholm', postal_code: '26234', country: 'se' }, gender: 'f', // härlett ur personnummer date_of_birth: '19570414' // YYYYMMDD }
Exakt vad varje steg ska pusha. Alla events bär även user_data enligt sidan innan.
När gym väljs. Viktigast av tilläggen, gym och tier ska följa med hela vägen till purchase.
dataLayer.push({
event: 'purchase_funnel_gym_selected',
data: { gym_id: '17920', gym_name: 'Alvesta Kyrkogatan- Practice',
gym_city: 'Alvesta', gym_tier: 'Platinum' }
});
När medlemskapstyp väljs.
dataLayer.push({
event: 'purchase_funnel_membership_selected',
data: { membership_category: 'Ordinarie', age_group: 'Vuxen' }
});
När ett tillval togglas (bindningstid, passerkort, förtur gruppträning).
dataLayer.push({
event: 'purchase_funnel_addon_changed',
data: { addon_id: 'fortur_grupptraning', addon_name: 'Förtur Gruppträning',
addon_price: 29, action: 'added' } // added | removed
});
Idag tom på valdata. Fyll med hela valet.
dataLayer.push({
event: 'purchase_funnel_summary',
data: { gym_id: '17920', gym_name: '...', gym_city: 'Alvesta', gym_tier: 'Platinum',
membership_category: 'Ordinarie', age_group: 'Vuxen',
binding_months: 12, passcard: 'app',
addons: [{ id: 'fortur_grupptraning', price: 29 }],
monthly_price: 529, startup_fee: 299, value: 828, currency: 'SEK' }
});
Hette initiate_checkout. Berika med val + value.
dataLayer.push({
event: 'purchase_funnel_checkout_initiated',
data: { gym_id: '17920', gym_tier: 'Platinum', membership_category: 'Ordinarie',
age_group: 'Vuxen', value: 828, currency: 'SEK' }
});
Hette checkout_loaded. Samma data som ovan.
När användaren skickas till Netaxept.
dataLayer.push({
event: 'purchase_funnel_payment_started',
data: { payment_option: 'Kort', payment_type: 'Månadsvis', value: 828, currency: 'SEK' }
});
Behåll som idag. Lägg gärna till landing_path och user_data om inloggad.
Behåll. Lägg till membership_category så det matchar membership_selected.
Behåll. Lägg inte personnummer i klartext i data, använd härledd kön och födelsedatum i user_data.
Befintlig struktur är en bra grund. Behåll namnet, fixa det markerade.
dataLayer.push({
event: 'purchase',
event_id: '45515553', // NYTT: unikt per köp, för Meta CAPI-dedup (= ordernr)
data: {
transaction_id: 45515553, // samma som event_id, för Google-dedup
value: 828, // NYTT: lägg till value (= total_revenue)
total_revenue: 828, currency: 'SEK', tax: 46.86, coupon: '',
payment_type: 'Månadsvis', payment_option: 'Kort',
subscription_period_months: 12,
gym_id: '17920', // FIXA: idag club: null
gym_name: 'Alvesta Kyrkogatan- Practice', gym_city: 'Alvesta', gym_tier: 'Platinum',
membership_category: 'Ordinarie', age_group: 'Vuxen',
items: [
{ item_id: '21519', item_name: 'Platinum Ordinarie', item_category: 'Medlemskap',
item_category2: 'Platinum', price: 529, quantity: 1 },
{ item_id: '15211', item_name: 'Startavgift', item_category: 'Avgift', price: 299, quantity: 1 }
]
},
user_id: '<hashad>', member_id: '<hashad>',
user_data: { /* KOMPLETT: email + telefon (null idag) + namn + kön + dob + postnr */ }
});
gym_id istället för club: null.value och event_id.user_data (telefon tappas idag).Vad ni implementerar, och vad vi gör på GTM-sidan.
purchase: en push, stabilt transaction_id, gym_id ej null, lägg value + event_idpurchase_funnel_*Snabb sanity-check i konsolen under utveckling: window.dataLayer.filter(e => (e.event||'').startsWith('purchase')). Varje steg ska ge exakt ett event, och purchase ska bara förekomma en gång.