Merge pull request #1181 from Safelite/feature/humphries/INSR-8882
INSR-8882: Events added to payment page and order confirmation page
This commit is contained in:
commit
aa3306ec06
5 changed files with 390 additions and 14 deletions
|
|
@ -1,3 +1,6 @@
|
||||||
|
import packageNames from '@/constants/package-names';
|
||||||
|
import { paymentMethods } from '@/constants/payment-method-constants';
|
||||||
|
|
||||||
const analyticsPageEvents = Object.freeze({
|
const analyticsPageEvents = Object.freeze({
|
||||||
ENTRY: 'ENTRY',
|
ENTRY: 'ENTRY',
|
||||||
EVENT: 'EVENT'
|
EVENT: 'EVENT'
|
||||||
|
|
@ -40,4 +43,18 @@ const ValueToLogTypes = Object.freeze({
|
||||||
LAST_5: 'last_5'
|
LAST_5: 'last_5'
|
||||||
});
|
});
|
||||||
|
|
||||||
export { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents, ValueToLogTypes };
|
const analyticsPaymentTypeMap = new Map([
|
||||||
|
[paymentMethods.AFTERPAY, 'after_pay'],
|
||||||
|
[paymentMethods.CREDIT_CARD, 'credit_card'],
|
||||||
|
[paymentMethods.PAY_AT_TIME_OF_SERVICE, 'pay_at_service'],
|
||||||
|
[paymentMethods.PAYPAL, 'pay_pal'],
|
||||||
|
[paymentMethods.APPLEPAY, 'apple_pay'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const analyticsServicePackageMap = new Map([
|
||||||
|
[packageNames.TIER_ONE, 'basic'],
|
||||||
|
[packageNames.TIER_TWO, 'essentials'],
|
||||||
|
[packageNames.TIER_THREE, 'essentialsplus']
|
||||||
|
]);
|
||||||
|
|
||||||
|
export { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents, ValueToLogTypes, analyticsPaymentTypeMap, analyticsServicePackageMap };
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import coverageType from '@/constants/coverage-type';
|
||||||
import { deepClone } from '@/helpers/object-helper';
|
import { deepClone } from '@/helpers/object-helper';
|
||||||
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
|
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
|
||||||
import { paymentMethods } from '@/constants/payment-method-constants';
|
import { paymentMethods } from '@/constants/payment-method-constants';
|
||||||
|
import partTypeStrings from '@/constants/part-type-strings';
|
||||||
|
import packageNames from '@/constants/package-names';
|
||||||
|
|
||||||
jest.mock('@/helpers/layout-helper.js', () => jest.fn());
|
jest.mock('@/helpers/layout-helper.js', () => jest.fn());
|
||||||
|
|
||||||
|
|
@ -27,7 +29,9 @@ jest.mock('@/helpers/cms-content-helper', () => ({
|
||||||
const mockMixin = {
|
const mockMixin = {
|
||||||
methods: {
|
methods: {
|
||||||
getCmsContent: jest.fn(),
|
getCmsContent: jest.fn(),
|
||||||
setCmsContent: jest.fn()
|
setCmsContent: jest.fn(),
|
||||||
|
savePageDataToStore: jest.fn(),
|
||||||
|
pushEventToGA: jest.fn()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -152,7 +156,8 @@ const sessionStorage = {
|
||||||
currentDeductible: {
|
currentDeductible: {
|
||||||
replace: 100,
|
replace: 100,
|
||||||
repair: 0
|
repair: 0
|
||||||
}
|
},
|
||||||
|
isUnverified: false
|
||||||
};
|
};
|
||||||
|
|
||||||
function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRun = () => {}, mixin = mockMixin) {
|
function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRun = () => {}, mixin = mockMixin) {
|
||||||
|
|
@ -778,7 +783,8 @@ describe('OrderConfirmation.vue', () => {
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}),
|
}),
|
||||||
setCmsContent: jest.fn()
|
setCmsContent: jest.fn(),
|
||||||
|
savePageDataToStore: jest.fn()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
@ -827,7 +833,8 @@ describe('OrderConfirmation.vue', () => {
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}),
|
}),
|
||||||
setCmsContent: jest.fn()
|
setCmsContent: jest.fn(),
|
||||||
|
savePageDataToStore: jest.fn()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
@ -955,6 +962,251 @@ describe('OrderConfirmation.vue', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('Methods', () => {
|
describe('Methods', () => {
|
||||||
|
describe('handleAnalyticsEvents', () => {
|
||||||
|
let mixin;
|
||||||
|
beforeEach(() => {
|
||||||
|
mixin = {
|
||||||
|
methods: {
|
||||||
|
getCmsContent: jest.fn(),
|
||||||
|
setCmsContent: jest.fn(),
|
||||||
|
savePageDataToStore: jest.fn(),
|
||||||
|
pushEventToGA: jest.fn()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
test('should call pushEventToGA with confirmation event for mobile repair', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
order.serviceLocation.appointmentType = AppointmentTypeStrings.MOBILE;
|
||||||
|
order.damage.isRepair = true;
|
||||||
|
order.isVerified = false;
|
||||||
|
order.isMobileAppointment = true;
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('confirmation', 'safelite', expect.stringContaining('mobile'), true);
|
||||||
|
});
|
||||||
|
test('should call pushEventToGA with confirmation event for in-shop replace', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
order.serviceLocation.appointmentType = AppointmentTypeStrings.IN_SHOP;
|
||||||
|
order.damage.isRepair = false;
|
||||||
|
order.isVerified = true;
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('confirmation', 'safelite', expect.stringContaining('replace'), true);
|
||||||
|
});
|
||||||
|
test('should call pushEventToGA for payment PIA successful when payment.isPayInAdvance is true', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
order.payment.isPayInAdvance = true;
|
||||||
|
order.payment.paymentMethod = paymentMethods.CREDIT_CARD;
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('payment_page', 'pia_successful', expect.any(String), true);
|
||||||
|
});
|
||||||
|
test('should not call pushEventToGA for PIA when payment.isPayInAdvance is false', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
order.payment.isPayInAdvance = false;
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const paymentPageCalls = mixin.methods.pushEventToGA.mock.calls.filter(call => call[0] === 'payment_page');
|
||||||
|
expect(paymentPageCalls.length).toBe(0);
|
||||||
|
});
|
||||||
|
test('should call pushEventToGA for service package when submittedOrder.servicePackage exists', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
order.servicePackage = packageNames.TIER_ONE;
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('service_package', 'package_purchased', expect.any(String), true);
|
||||||
|
});
|
||||||
|
test('should not call pushEventToGA for service package when submittedOrder.servicePackage is null', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
order.servicePackage = null;
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const servicePackageCalls = mixin.methods.pushEventToGA.mock.calls.filter(call => call[0] === 'service_package');
|
||||||
|
expect(servicePackageCalls.length).toBe(0);
|
||||||
|
});
|
||||||
|
test('should call pushEventToGA for total price when isFirstLoad is true and cart total greater than 0', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
const expectedTotal = 100;
|
||||||
|
order.lineItems.glassParts = [{ partType: 'WINDSHIELD', sellingPrice: expectedTotal }];
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.handleAnalyticsEvents(true);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('total_price', expectedTotal.toString(), expect.any(String), true);
|
||||||
|
});
|
||||||
|
test('should not call pushEventToGA for total price when isFirstLoad is false', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
const expectedTotal = 100;
|
||||||
|
order.lineItems.glassParts = [{ partType: 'WINDSHIELD', sellingPrice: expectedTotal }];
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
useMainStore().pageData = jest.fn().mockReturnValue({ isFirstLoad: false });
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const totalPriceCalls = mixin.methods.pushEventToGA.mock.calls.filter(call => call[0] === 'total_price');
|
||||||
|
expect(totalPriceCalls.length).toBe(0);
|
||||||
|
});
|
||||||
|
test('should not call pushEventToGA for total price when cart total is 0', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
const expectedTotal = 0;
|
||||||
|
order.lineItems.glassParts = [{ partType: 'WINDSHIELD', sellingPrice: 0 }];
|
||||||
|
order.insuranceCoverage.coverageType = coverageType.ITAC;
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const totalPriceCalls = mixin.methods.pushEventToGA.mock.calls.filter(call => call[0] === 'total_price');
|
||||||
|
expect(totalPriceCalls.length).toBe(0);
|
||||||
|
});
|
||||||
|
test('should call pushEventToGA for rain defense purchased when hasRainRepel is true', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
order.lineItems.vaps = [{ partType: partTypeStrings.RAIN_DEFENSE }];
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('rain_defense', 'purchased', expect.any(String), true);
|
||||||
|
});
|
||||||
|
test('should call pushEventToGA for rain defense no_purchase when hasRainRepel is false', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
order.lineItems.vaps = [];
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('rain_defense', 'no_purchase', expect.any(String), true);
|
||||||
|
});
|
||||||
|
test('should call pushEventToGA for visitor_info_confirmation with ClientSite referring_site', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('visitor_info_confirmation', 'referring_site', 'ClientSite', true);
|
||||||
|
});
|
||||||
|
test('should call pushEventToGA for visitor_info_confirmation with client_name', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('visitor_info_confirmation', 'client_name', expect.any(String), true);
|
||||||
|
});
|
||||||
|
test('should call pushEventToGA for wipers purchased when hasWipers is true', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
order.lineItems.vaps = [{ partType: partTypeStrings.FRONT_WIPER }];
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const mixin = {
|
||||||
|
methods: {
|
||||||
|
getCmsContent: jest.fn().mockReturnValue([{ Text: 'Front Beam' }]),
|
||||||
|
setCmsContent: jest.fn(),
|
||||||
|
savePageDataToStore: jest.fn(),
|
||||||
|
pushEventToGA: jest.fn()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('wipers', 'purchased', expect.any(String), true);
|
||||||
|
});
|
||||||
|
test('should call pushEventToGA for wipers no_purchase when hasWipers is false', () => {
|
||||||
|
// Arrange
|
||||||
|
const order = deepClone(sessionStorage);
|
||||||
|
order.lineItems.vaps = [];
|
||||||
|
const mockStoreActions = () => {
|
||||||
|
useMainStore().getSubmittedOrder = jest.fn().mockImplementation(() => order);
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent({}, {}, mockStoreActions, mixin);
|
||||||
|
|
||||||
|
// handleAnalyticsEvents is called on mount, no need to act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mixin.methods.pushEventToGA).toHaveBeenCalledWith('wipers', 'no_purchase', 'none_none', true);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('formatDate', () => {
|
describe('formatDate', () => {
|
||||||
test.each([
|
test.each([
|
||||||
['Wednesday, April 22, 2020', '2020-04-22'],
|
['Wednesday, April 22, 2020', '2020-04-22'],
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
:isRepair="isRepair" />
|
:isRepair="isRepair" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="displayWipers"
|
v-if="hasWipers"
|
||||||
class="wiper-details confirmation-section">
|
class="wiper-details confirmation-section">
|
||||||
<span class="wipers-title">{{ wipersTitle }}</span>
|
<span class="wipers-title">{{ wipersTitle }}</span>
|
||||||
<span
|
<span
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
class="wipers-body">{{ description }}</span>
|
class="wipers-body">{{ description }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="displayRainRepel"
|
v-if="hasRainRepel"
|
||||||
class="rain-repel-details confirmation-section">
|
class="rain-repel-details confirmation-section">
|
||||||
<span class="rain-repel-title">{{ rainRepelTitle }}</span>
|
<span class="rain-repel-title">{{ rainRepelTitle }}</span>
|
||||||
<span class="rain-repel-body">{{ rainRepelBody }}</span>
|
<span class="rain-repel-body">{{ rainRepelBody }}</span>
|
||||||
|
|
@ -135,6 +135,10 @@ import widgetFields from '@/constants/cms-widget-fields.js';
|
||||||
import { getGlassList } from '@/helpers/damage-helper';
|
import { getGlassList } from '@/helpers/damage-helper';
|
||||||
import partTypeStrings from '@/constants/part-type-strings';
|
import partTypeStrings from '@/constants/part-type-strings';
|
||||||
import coverageType from '@/constants/coverage-type';
|
import coverageType from '@/constants/coverage-type';
|
||||||
|
import { analyticsPaymentTypeMap, analyticsServicePackageMap } from '@/constants/analytics';
|
||||||
|
import { containsRecalParts } from '@/helpers/recal-helper';
|
||||||
|
import issPageValues from '@/router/router-constants/issPage-values';
|
||||||
|
import { getCartTotal } from '@/helpers/cart-helper';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'order-confirmation',
|
name: 'order-confirmation',
|
||||||
|
|
@ -199,6 +203,7 @@ export default {
|
||||||
hasRecalibrationPart,
|
hasRecalibrationPart,
|
||||||
referralNumber: referralNumber?.toString(),
|
referralNumber: referralNumber?.toString(),
|
||||||
isNoComp: this.submittedOrder.insuranceCoverage.coverageType === coverageType.NO_COMP,
|
isNoComp: this.submittedOrder.insuranceCoverage.coverageType === coverageType.NO_COMP,
|
||||||
|
isITAC: this.submittedOrder.insuranceCoverage.coverageType === coverageType.ITAC,
|
||||||
widgets: {
|
widgets: {
|
||||||
siteHeader: 'SiteHeaderWidget',
|
siteHeader: 'SiteHeaderWidget',
|
||||||
emailConfirmation: 'EmailConfirmationWordingWidget',
|
emailConfirmation: 'EmailConfirmationWordingWidget',
|
||||||
|
|
@ -418,10 +423,16 @@ export default {
|
||||||
// Temporarily set return true to see cart in localhost or dev environment
|
// Temporarily set return true to see cart in localhost or dev environment
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
displayWipers() {
|
hasWipers() {
|
||||||
const hasWiperPart = this.submittedOrder.lineItems.vaps.some((part) => part.partType.toLowerCase().includes('wiper'));
|
const hasWiperPart = this.submittedOrder.lineItems.vaps.some((part) => part.partType.toLowerCase().includes('wiper'));
|
||||||
return hasWiperPart;
|
return hasWiperPart;
|
||||||
},
|
},
|
||||||
|
hasFrontWiper() {
|
||||||
|
return this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.FRONT_WIPER);
|
||||||
|
},
|
||||||
|
hasRearWiper() {
|
||||||
|
return this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.REAR_WIPER);
|
||||||
|
},
|
||||||
wipersTitle() {
|
wipersTitle() {
|
||||||
return this.getCmsContent(
|
return this.getCmsContent(
|
||||||
this.widgets.wipersText,
|
this.widgets.wipersText,
|
||||||
|
|
@ -431,13 +442,10 @@ export default {
|
||||||
wipersBody() {
|
wipersBody() {
|
||||||
const wiperTypesOnOrder = [];
|
const wiperTypesOnOrder = [];
|
||||||
|
|
||||||
const hasFrontWiper = this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.FRONT_WIPER);
|
if (this.hasFrontWiper) {
|
||||||
const hasRearWiper = this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.REAR_WIPER);
|
|
||||||
|
|
||||||
if (hasFrontWiper) {
|
|
||||||
wiperTypesOnOrder.push(partTypeStrings.FRONT_WIPER);
|
wiperTypesOnOrder.push(partTypeStrings.FRONT_WIPER);
|
||||||
}
|
}
|
||||||
if (hasRearWiper) {
|
if (this.hasRearWiper) {
|
||||||
wiperTypesOnOrder.push(partTypeStrings.REAR_WIPER);
|
wiperTypesOnOrder.push(partTypeStrings.REAR_WIPER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -447,7 +455,7 @@ export default {
|
||||||
|
|
||||||
return wiperDescriptions;
|
return wiperDescriptions;
|
||||||
},
|
},
|
||||||
displayRainRepel() {
|
hasRainRepel() {
|
||||||
return this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.RAIN_DEFENSE);
|
return this.submittedOrder.lineItems.vaps.some((part) => part.partType === partTypeStrings.RAIN_DEFENSE);
|
||||||
},
|
},
|
||||||
rainRepelTitle() {
|
rainRepelTitle() {
|
||||||
|
|
@ -508,6 +516,10 @@ export default {
|
||||||
if (this.carrierUrl) {
|
if (this.carrierUrl) {
|
||||||
this.$refs.siteFooter.updateButtonText(`Go back to ${this.carrierName}`);
|
this.$refs.siteFooter.updateButtonText(`Go back to ${this.carrierName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isFirstLoad = this.mainStore.pageData(issPageValues.ORDER_CONFIRMATION)?.isFirstLoad ?? true;
|
||||||
|
this.savePageDataToStore(issPageValues.ORDER_CONFIRMATION, { isFirstLoad: false });
|
||||||
|
this.handleAnalyticsEvents(isFirstLoad);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
|
|
@ -611,6 +623,72 @@ export default {
|
||||||
|
|
||||||
const vapsTypeDescription = vapsItemDescriptions?.find((entry) => entry?.Name === vapsPartType);
|
const vapsTypeDescription = vapsItemDescriptions?.find((entry) => entry?.Name === vapsPartType);
|
||||||
return vapsTypeDescription?.Text ?? '';
|
return vapsTypeDescription?.Text ?? '';
|
||||||
|
},
|
||||||
|
handleAnalyticsEvents(isFirstLoad) {
|
||||||
|
const mobileOrInshop = this.submittedOrder.isMobileAppointment ? 'mobile' : 'in_shop';
|
||||||
|
const verifiedOrNotVerified = this.submittedOrder.isVerified ? 'verified' : 'not_verified';
|
||||||
|
const repairOrReplace = this.submittedOrder.isRepair ? 'repair' : 'replace';
|
||||||
|
this.pushEventToGA('confirmation', 'safelite', `${mobileOrInshop}_${repairOrReplace}_${verifiedOrNotVerified}`, true);
|
||||||
|
|
||||||
|
if (this.payment.isPayInAdvance) {
|
||||||
|
const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.payment.paymentMethod);
|
||||||
|
this.pushEventToGA('payment_page', 'pia_successful', paymentMethodForAnalytics, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.submittedOrder.servicePackage) {
|
||||||
|
const servicePackageForAnalytics = analyticsServicePackageMap.get(this.submittedOrder.servicePackage);
|
||||||
|
this.pushEventToGA('service_package', 'package_purchased', servicePackageForAnalytics, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containsRecalParts(this.submittedOrder.lineItems)) {
|
||||||
|
let coverageType = '';
|
||||||
|
if (this.isITAC) {
|
||||||
|
coverageType = 'ITAC';
|
||||||
|
} else if (this.isNoComp) {
|
||||||
|
coverageType = 'no_comp';
|
||||||
|
} else if (this.submittedOrder.isVerified) {
|
||||||
|
coverageType = 'verified';
|
||||||
|
} else {
|
||||||
|
coverageType = 'unverified';
|
||||||
|
}
|
||||||
|
const recalibrationType = this.submittedOrder.lineItems.glassParts?.find((part) => part.requiresRecalibration)?.recalibrationType;
|
||||||
|
this.pushEventToGA(`recalibration_scheduled_${coverageType}`, this.vehicle.carId, `recal_type_${recalibrationType}`.replace(/ /g, '_').toLowerCase(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ymms = `${this.vehicle.year}_${this.vehicle.make}_${this.vehicle.model}_${this.vehicle.style}`;
|
||||||
|
if (isFirstLoad && getCartTotal(this.submittedOrder) > 0) {
|
||||||
|
this.pushEventToGA('total_price', getCartTotal(this.submittedOrder).toString(), ymms, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pushEventToGA('rain_defense', this.hasRainRepel ? 'purchased' : 'no_purchase', ymms, true);
|
||||||
|
|
||||||
|
// TODO: Check if we're coming from SFA and add relevant events
|
||||||
|
// eslint-disable-next-line
|
||||||
|
if (false) {
|
||||||
|
this.pushEventToGA('visitor_info_confirmation', 'referring_site', 'SFA', null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.pushEventToGA('visitor_info_confirmation', 'referring_site', 'ClientSite', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pushEventToGA('visitor_info_confirmation', 'client_name', this.mainStore.accountNameForEvents, true);
|
||||||
|
|
||||||
|
const wiperAction = this.hasWipers ? 'purchased' : 'no_purchase';
|
||||||
|
|
||||||
|
let wiperLabel = this.wipersBody.join('_').toLowerCase().replace('<br />', '').replace(/beam/g, '').replace(/blades/g, '').trim().replace(/ /g, '_');
|
||||||
|
if (wiperLabel.indexOf('front') < 0 && wiperLabel.indexOf('rear') < 0) {
|
||||||
|
wiperLabel = 'none_none';
|
||||||
|
} else {
|
||||||
|
if (wiperLabel.indexOf('front') < 0) {
|
||||||
|
wiperLabel = 'front_none_' + wiperLabel;
|
||||||
|
}
|
||||||
|
if (wiperLabel.indexOf('rear') < 0) {
|
||||||
|
wiperLabel = wiperLabel + '_rear_none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wiperLabel = wiperLabel.replace(/__/g, '_');
|
||||||
|
|
||||||
|
this.pushEventToGA('wipers', wiperAction, wiperLabel, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ import { mapAdyenToIssPaymentMethod, mapIssToAdyenPaymentMethod } from '@/helper
|
||||||
import { createAdyenCheckout } from "@/helpers/adyen-helper";
|
import { createAdyenCheckout } from "@/helpers/adyen-helper";
|
||||||
import { Dropin } from "@adyen/adyen-web/auto";
|
import { Dropin } from "@adyen/adyen-web/auto";
|
||||||
import applicationConfig from '@/constants/application-config';
|
import applicationConfig from '@/constants/application-config';
|
||||||
|
import { analyticsPaymentTypeMap } from '@/constants/analytics';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'payment-page-adyen',
|
name: 'payment-page-adyen',
|
||||||
|
|
@ -118,6 +119,7 @@ export default {
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
showIssLoadingModal(true);
|
showIssLoadingModal(true);
|
||||||
|
this.pushEventToGA('payment_page', 'Mode', 'Adyen', true);
|
||||||
this.initializeAdyen().finally(() => {
|
this.initializeAdyen().finally(() => {
|
||||||
showIssLoadingModal(false);
|
showIssLoadingModal(false);
|
||||||
});
|
});
|
||||||
|
|
@ -353,6 +355,8 @@ export default {
|
||||||
},
|
},
|
||||||
async paymentFailedPayLater() {
|
async paymentFailedPayLater() {
|
||||||
this.showIssLoadingModal(true);
|
this.showIssLoadingModal(true);
|
||||||
|
const paymentMethod = analyticsPaymentTypeMap.get(this.piaType);
|
||||||
|
this.pushEventToGA('transaction_declined_displayed', 'save_your_appointment_clicked', `${paymentMethod}_declined`, true);
|
||||||
this.mainStore.savePaymentMethodChoice(paymentMethods.PAY_AT_TIME_OF_SERVICE);
|
this.mainStore.savePaymentMethodChoice(paymentMethods.PAY_AT_TIME_OF_SERVICE);
|
||||||
await submitWorkOrder({ submitType: submitType.SAFELITE });
|
await submitWorkOrder({ submitType: submitType.SAFELITE });
|
||||||
this.$router.navigate(
|
this.$router.navigate(
|
||||||
|
|
@ -523,6 +527,8 @@ export default {
|
||||||
const paymentMethodFromSession = adyenResponse?.paymentMethod;
|
const paymentMethodFromSession = adyenResponse?.paymentMethod;
|
||||||
|
|
||||||
const paymentMethod = mapAdyenToIssPaymentMethod(paymentMethodFromSession);
|
const paymentMethod = mapAdyenToIssPaymentMethod(paymentMethodFromSession);
|
||||||
|
const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(paymentMethod);
|
||||||
|
this.pushEventToGA('PIA', 'Pay Today', paymentMethodForAnalytics, true, null, 0);
|
||||||
|
|
||||||
this.mainStore.savePaymentMethodChoice(paymentMethod);
|
this.mainStore.savePaymentMethodChoice(paymentMethod);
|
||||||
|
|
||||||
|
|
@ -544,6 +550,10 @@ export default {
|
||||||
|
|
||||||
await global.$logger.logError(stringToLog);
|
await global.$logger.logError(stringToLog);
|
||||||
|
|
||||||
|
const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.piaType);
|
||||||
|
this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, 'true', true, null, 0);
|
||||||
|
this.pushEventToGA('payment_page', 'pia_failed', paymentMethodForAnalytics, true, null, result?.resultCode);
|
||||||
|
|
||||||
this.hasPaymentFailureError = true;
|
this.hasPaymentFailureError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -556,6 +566,9 @@ export default {
|
||||||
const stringToLog = `ADYEN ERROR. Name = ${error?.name}. Details = ${error?.message}.`;
|
const stringToLog = `ADYEN ERROR. Name = ${error?.name}. Details = ${error?.message}.`;
|
||||||
|
|
||||||
await global.$logger.logError(stringToLog);
|
await global.$logger.logError(stringToLog);
|
||||||
|
const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.piaType);
|
||||||
|
this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, 'true', true, null, 0);
|
||||||
|
this.pushEventToGA('payment_page', 'pia_error', paymentMethodForAnalytics, true, null, error?.name);
|
||||||
|
|
||||||
this.hasPaymentFailureError = true;
|
this.hasPaymentFailureError = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import navigationScenarios from '@/router/router-constants/navigation-scenarios'
|
||||||
import submitType from '@/constants/submit-type';
|
import submitType from '@/constants/submit-type';
|
||||||
import showIssLoadingModal from '@/helpers/loading-modal-helper';
|
import showIssLoadingModal from '@/helpers/loading-modal-helper';
|
||||||
import { paymentMethods } from '@/constants/payment-method-constants';
|
import { paymentMethods } from '@/constants/payment-method-constants';
|
||||||
|
import { analyticsPaymentTypeMap } from '@/constants/analytics';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'payment-return-adyen',
|
name: 'payment-return-adyen',
|
||||||
|
|
@ -58,6 +59,7 @@ export default {
|
||||||
const sessionInfo = await getSessionInfo(sessionId, result.sessionResult);
|
const sessionInfo = await getSessionInfo(sessionId, result.sessionResult);
|
||||||
|
|
||||||
const paymentMethod = mapAdyenToIssPaymentMethod(sessionInfo?.paymentMethod);
|
const paymentMethod = mapAdyenToIssPaymentMethod(sessionInfo?.paymentMethod);
|
||||||
|
const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(paymentMethod);
|
||||||
const amountDue = getCartTotal(store.order);
|
const amountDue = getCartTotal(store.order);
|
||||||
const ccToken = generateCcToken(sessionInfo);
|
const ccToken = generateCcToken(sessionInfo);
|
||||||
if (paymentMethod === paymentMethods.AFTERPAY) {
|
if (paymentMethod === paymentMethods.AFTERPAY) {
|
||||||
|
|
@ -67,6 +69,7 @@ export default {
|
||||||
ccToken.expMonth = "03";
|
ccToken.expMonth = "03";
|
||||||
ccToken.expYear = "2030";
|
ccToken.expYear = "2030";
|
||||||
}
|
}
|
||||||
|
this.pushEventToGA('PIA', 'Pay Today', paymentMethodForAnalytics, true, null, 0);
|
||||||
|
|
||||||
store.savePaymentMethodChoice(paymentMethod);
|
store.savePaymentMethodChoice(paymentMethod);
|
||||||
store.updateCreditCardToken(ccToken);
|
store.updateCreditCardToken(ccToken);
|
||||||
|
|
@ -95,6 +98,11 @@ export default {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
piaType() {
|
||||||
|
return this.mainStore.payment.paymentMethod;
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
finalizeAdyenPayment(sessionId, redirectResult) {
|
finalizeAdyenPayment(sessionId, redirectResult) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
@ -111,6 +119,10 @@ export default {
|
||||||
const stringToLog = `ADYEN PAYMENT NOT AUTHORIZED. Code = ${result?.resultCode}. Id = ${sessionId}`;
|
const stringToLog = `ADYEN PAYMENT NOT AUTHORIZED. Code = ${result?.resultCode}. Id = ${sessionId}`;
|
||||||
|
|
||||||
global.$logger.logError(stringToLog);
|
global.$logger.logError(stringToLog);
|
||||||
|
|
||||||
|
const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.piaType);
|
||||||
|
this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, 'true', true, null, 0);
|
||||||
|
this.pushEventToGA('payment_page', 'pia_failed', paymentMethodForAnalytics, true, null, result?.resultCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
reject({
|
reject({
|
||||||
|
|
@ -123,6 +135,10 @@ export default {
|
||||||
const stringToLog = `ADYEN ERROR. Name = ${error?.name}. Details = ${error?.message}.`;
|
const stringToLog = `ADYEN ERROR. Name = ${error?.name}. Details = ${error?.message}.`;
|
||||||
|
|
||||||
global.$logger.logError(stringToLog);
|
global.$logger.logError(stringToLog);
|
||||||
|
|
||||||
|
const paymentMethodForAnalytics = analyticsPaymentTypeMap.get(this.piaType);
|
||||||
|
this.pushEventToGA('transaction_declined_displayed', `${paymentMethodForAnalytics}_declined`, 'true', true, null, 0);
|
||||||
|
this.pushEventToGA('payment_page', 'pia_error', paymentMethodForAnalytics, true, null, error?.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
reject({
|
reject({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue