diff --git a/blocks/plans-quote/form.js b/blocks/plans-quote/form.js index 01f31fd..aa7d3a9 100644 --- a/blocks/plans-quote/form.js +++ b/blocks/plans-quote/form.js @@ -1,7 +1,7 @@ import { jsx } from '../../scripts/scripts.js'; import { isCanada } from '../../scripts/lib-franklin.js'; import Loader from './loader.js'; -import APIClient from '../../scripts/24petwatch-api.js'; +import APIClient, { getAPIBaseUrl } from '../../scripts/24petwatch-api.js'; import { COOKIE_NAME_SAVED_OWNER_ID, SS_KEY_FORM_ENTRY_URL, @@ -19,6 +19,7 @@ import { isSummaryPage, getSelectedProductAdditionalInfo, getItemInfoFragment, + DL_EVENTS, } from '../../scripts/24petwatch-utils.js'; import { getConfigValue } from '../../scripts/configs.js'; import { trackGTMEvent } from '../../scripts/lib-analytics.js'; @@ -39,7 +40,26 @@ const usedChipNumbers = new Set(); const promoResultKey = await getConfigValue('promo-result'); -export default function formDecoration(block, apiBaseUrl) { +const apiBaseUrl = await getAPIBaseUrl(); +const APIClientObj = new APIClient(apiBaseUrl); + +// eslint-disable-next-line no-shadow +async function getPurchaseSummary(ownerId) { + Loader.showLoader(); + // eslint-disable-next-line no-shadow + let purchaseSummary = {}; + try { + purchaseSummary = await APIClientObj.getPurchaseSummary(ownerId); + } catch (status) { + // eslint-disable-next-line no-console + console.log('Failed to get the purchase summary for owner:', ownerId, ' status:', status); + } + Loader.hideLoader(); + + return purchaseSummary; +} + +export default function formDecoration(block) { // prepare for Canada vs US const currencyValue = isCanada ? CURRENCY_CANADA : CURRENCY_US; const zipcodeLabel = isCanada ? 'Postal code*' : 'Zip code*'; @@ -181,7 +201,6 @@ export default function formDecoration(block, apiBaseUrl) { Loader.addLoader(); - const APIClientObj = new APIClient(apiBaseUrl); const countryId = isCanada ? 1 : 2; const formData = {}; const breedLists = {}; @@ -649,51 +668,40 @@ export default function formDecoration(block, apiBaseUrl) { } } - function instrumentTracking(productName) { - const currentPath = window.location.pathname; - const { microchip = '' } = formData || {}; - - let productType = productName || null; - - if (!productType) { - if (currentPath.includes(PET_PLANS_LPM_URL)) { - productType = 'Lifetime Protection Membership'; - } else if (currentPath.includes(PET_PLANS_LPM_PLUS_URL)) { - productType = 'Lifetime Protection Membership - PLUS'; - } else if (currentPath.includes(PET_PLANS_ANNUAL_URL)) { - productType = 'Annual Protection Membership'; - } - } - - if (!productType) { - console.error('Product type could not be determined.'); - return; - } - - // call instrument tracking - const trackingData = { - ecommerce: { - // New GTM dataLayer - product_type: productType, // annual protection membership - items: [ - { - item_name: productType, - // coupon: formData.promoCode, + function setDataLayer(data) { + const dlItems = []; + + if ('petSummaries' in data) { + const { petSummaries } = data; + let membershipName = ''; + if (petSummaries && petSummaries.length > 0) { + petSummaries.forEach((pet) => { + membershipName = pet.membershipName ?? ''; + // push each item object to items array + dlItems.push({ + item_name: membershipName, currency: currencyValue, - discount: '', // not available until step 2 + discount: pet.nonInsurancePetSummary?.discount ?? '', item_category: 'membership', item_variant: '', // okay to be left empty - microchip_number: microchip, - product_type: productType, - price: '', // not available until step 2 - quantity: 1, + microchip_number: pet.microChipNumber ?? '', + product_type: membershipName, + price: pet.nonInsurancePetSummary?.amount ?? '', + quantity: pet.nonInsurancePetSummary?.membership?.quantity ?? '1', + }); + }); + + const trackingData = { + ecommerce: { + product_type: membershipName, + items: dlItems, }, - ], - }, - }; + }; - // send the GTM event - trackGTMEvent('add_to_cart', trackingData); + // send the GTM event + trackGTMEvent(DL_EVENTS.add, trackingData); + } + } } async function executeSubmit() { @@ -701,6 +709,7 @@ export default function formDecoration(block, apiBaseUrl) { const ownerId = (!formData.ownerId) ? '' : formData.ownerId; await saveOwner(ownerId); // Create or Update the owner + if (!formData.ownerId) { // eslint-disable-next-line no-console console.log('Failed to save the owner.'); @@ -739,8 +748,10 @@ export default function formDecoration(block, apiBaseUrl) { return; } + const getPurchaseSummaryDetails = await getPurchaseSummary(formData.ownerId); + // call instrument tracking - instrumentTracking(); + setDataLayer(getPurchaseSummaryDetails); Loader.hideLoader(); @@ -760,8 +771,10 @@ export default function formDecoration(block, apiBaseUrl) { return; } + const getPurchaseSummaryDetails = await getPurchaseSummary(formData.ownerId); + // call instrument tracking - instrumentTracking(productName); + setDataLayer(getPurchaseSummaryDetails); Loader.hideLoader(); window.location.reload(); diff --git a/blocks/plans-quote/summary-quote.js b/blocks/plans-quote/summary-quote.js index f068f91..21c9e04 100644 --- a/blocks/plans-quote/summary-quote.js +++ b/blocks/plans-quote/summary-quote.js @@ -8,7 +8,13 @@ import { getCookie, getSelectedProductAdditionalInfo, getItemInfoFragment, + CURRENCY_CANADA, + CURRENCY_US, + SS_KEY_SUMMARY_ACTION, + DL_EVENTS, } from '../../scripts/24petwatch-utils.js'; +import { isCanada } from '../../scripts/lib-franklin.js'; +import { trackGTMEvent } from '../../scripts/lib-analytics.js'; import { getConfigValue } from '../../scripts/configs.js'; export default async function decorateSummaryQuote(block, apiBaseUrl) { @@ -42,6 +48,96 @@ export default async function decorateSummaryQuote(block, apiBaseUrl) { return purchaseSummary; } + async function getPet(petId) { + try { + const data = await APIClientObj.getPet(petId); + return data; + } catch (status) { + // eslint-disable-next-line no-console + console.log('Failed to get the pet', ' status:', status); + return []; + } + } + + function setDataLayer(data, event, petData = '') { + const currencyValue = isCanada ? CURRENCY_CANADA : CURRENCY_US; + const dlItems = []; + // set items array + if ('petSummaries' in data) { + const { petSummaries } = data; + if (petSummaries && petSummaries.length > 0) { + petSummaries.forEach((pet) => { + // push each item object to items array + dlItems.push({ + item_name: pet.membershipName ?? '', + currency: currencyValue, + discount: pet.nonInsurancePetSummary?.discount ?? '', + item_category: 'membership', // membership + item_variant: '', // okay to be left empty + price: pet.nonInsurancePetSummary?.amount ?? '', + quantity: pet.nonInsurancePetSummary?.membership?.quantity ?? '1', + microchip_number: pet.microChipNumber ?? '', + product_type: pet.membershipName ?? '', + }); + }); + } + } + // cart view + if (event === DL_EVENTS.view) { + const viewCartDL = { + ecommerce: { + value: purchaseSummary?.summary?.totalDueToday, + currency: currencyValue, + items: dlItems, + }, + }; + // Add view cart event + trackGTMEvent(DL_EVENTS.view, viewCartDL); + } + // remove from cart + if (event === DL_EVENTS.remove) { + // check we have all required data + if ('petSummaries' in data && 'microchipId' in petData) { + const { petSummaries } = data; + const microchip = petData.microchipId; + const petIndex = petSummaries.findIndex((item) => item.microChipNumber === microchip); + const petSummary = petSummaries[petIndex]; + if (petSummary) { + // remove cart DL object + const removeCartDL = { + ecommerce: { + items: [{ + item_name: petSummary.membershipName ?? '', + currency: currencyValue, + discount: petSummary.nonInsurancePetSummary?.discount ?? '', + item_category: 'membership', // membership + item_variant: '', // okay to be left empty + price: petSummary.nonInsurancePetSummary?.amount ?? '', + quantity: 1, + microchip_number: microchip ?? '', + product_type: petSummary.membershipName ?? '', + }], + }, + }; + // Add view cart event + trackGTMEvent(DL_EVENTS.remove, removeCartDL); + } + } + } + // begin checkout + if (event === DL_EVENTS.checkout) { + const checkoutCartDL = { + ecommerce: { + value: purchaseSummary?.summary?.totalDueToday, + currency: currencyValue, + items: dlItems, + }, + }; + // Add checkout cart event + trackGTMEvent(DL_EVENTS.checkout, checkoutCartDL); + } + } + Loader.showLoader(); try { ownerData = await APIClientObj.getOwner(ownerId); @@ -102,31 +198,33 @@ export default async function decorateSummaryQuote(block, apiBaseUrl) { console.error('Invalid entry URL'); } - const payload = { - payload: { - Data: { + if (selectedProducts.length > 0 && petsList.length > 0) { + const payload = { + payload: { + Data: { + ContactKey: ownerData.email, + EmailAddress: ownerData.email, + OrderCompleted: false, + OwnerId: ownerData.id, + PetId: selectedProducts[0].petID, + PetName: petsList[0].petName, + SiteURL: entryURL, + Species: petsList[0].speciesId === 1 ? 'Dog' : 'Cat', + }, + EventDefinitionKey: 'APIEvent-6723a35b-b066-640c-1d7b-222f98caa9e1', ContactKey: ownerData.email, - EmailAddress: ownerData.email, - OrderCompleted: false, - OwnerId: ownerData.id, - PetId: selectedProducts[0].petID, - PetName: petsList[0].petName, - SiteURL: entryURL, - Species: petsList[0].speciesId === 1 ? 'Dog' : 'Cat', }, - EventDefinitionKey: 'APIEvent-6723a35b-b066-640c-1d7b-222f98caa9e1', - ContactKey: ownerData.email, - }, - }; + }; - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }; - await fetch(salesforceProxyEndpoint, options); + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }; + await fetch(salesforceProxyEndpoint, options); + } Loader.hideLoader(); } @@ -149,12 +247,19 @@ export default async function decorateSummaryQuote(block, apiBaseUrl) { async function removePet(petId) { Loader.showLoader(); + // capture pet info before delete + const petInfo = await getPet(petId); try { await APIClientObj.deletePet(petId); } catch (status) { // eslint-disable-next-line no-console console.log('Failed to delete the pet:', petId, ' status:', status); } + // set dataLayer + if (purchaseSummary && petInfo) { + sessionStorage.setItem(SS_KEY_SUMMARY_ACTION, DL_EVENTS.remove); + setDataLayer(purchaseSummary, DL_EVENTS.remove, petInfo); + } Loader.hideLoader(); window.location.reload(); } @@ -281,6 +386,17 @@ export default async function decorateSummaryQuote(block, apiBaseUrl) { const confirmationDialogYes = document.getElementById('confirmation-dialog-yes'); const confirmationDialogNo = document.getElementById('confirmation-dialog-no'); + // Trigger cart view DL event + if (purchaseSummary) { + // to avoid page view DL events on page refresh after add and remove + // const lastFormAction = sessionStorage.getItem(SS_KEY_SUMMARY_ACTION); + // if (!lastFormAction || (lastFormAction && !lastFormAction.includes(DL_EVENTS.remove))) { + // setDataLayer(purchaseSummary, DL_EVENTS.view); + // } + // leave default cart view on any page refresh + setDataLayer(purchaseSummary, DL_EVENTS.view); + } + function editPetHandler(petID) { confirmationDialogHeader.textContent = 'Edit Confirmation'; confirmationDialogNote.textContent = 'Please note: If you edit the information, the system will return you to the beginning of the order process.'; @@ -361,6 +477,11 @@ export default async function decorateSummaryQuote(block, apiBaseUrl) { try { const data = await APIClientObj.postSalesForPayment(ownerData.id, ownerData.cartFlow); if (data.isSuccess) { + // trigger DL event + if (purchaseSummary) { + sessionStorage.removeItem(SS_KEY_SUMMARY_ACTION); + setDataLayer(purchaseSummary, DL_EVENTS.checkout); + } window.location.href = replaceUrlPlaceholders( data.paymentProcessorRedirectBackURL, data.paymentProcessingUserId, @@ -380,6 +501,8 @@ export default async function decorateSummaryQuote(block, apiBaseUrl) { addPetButton.onclick = () => { if (formWrapper.innerHTML === '') { formDecoration(formWrapper, apiBaseUrl); + // set sessionStorage with add action + sessionStorage.setItem(SS_KEY_SUMMARY_ACTION, DL_EVENTS.add); } else { formWrapper.innerHTML = ''; } diff --git a/scripts/24petwatch-utils.js b/scripts/24petwatch-utils.js index 44cb938..b54c51d 100644 --- a/scripts/24petwatch-utils.js +++ b/scripts/24petwatch-utils.js @@ -19,8 +19,18 @@ export function getQueryParam(param, defaultValue = null) { return urlParams.has(param) ? urlParams.get(param) : defaultValue; } +// ----- dataLayer event helpers ----- +export const DL_EVENTS = { + add: 'add_to_cart', + remove: 'remove_from_cart', + view: 'view_cart', + checkout: 'begin_checkout', + purchase: 'purchase', +}; + // ----- sessionStorage / localStorage helpers ----- export const SS_KEY_FORM_ENTRY_URL = 'formEntryURL'; +export const SS_KEY_SUMMARY_ACTION = 'summaryAction'; // ----- cookie helpers ----- export const COOKIE_NAME_FOR_PET_TAGS = 'ph.PetTagQuote';