diff --git a/environment-variables.js b/environment-variables.js index 4d894b006..17c797cfd 100644 --- a/environment-variables.js +++ b/environment-variables.js @@ -13,6 +13,8 @@ const environmentVariables = { ['__VUE_APP_SAFELITE_HOP__']: "https://sv2-safelitehop-dev.safelite.com/fmgCheckoutShared.aspx", ['__VUE_APP_SESSION_TIMEOUT_MINUTES__']: 30, ['__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__']: "", + ['__VUE_APP_ADYEN_ENVIRONMENT__']: "test", + ['__VUE_APP_ADYEN_CLIENT_KEY__']: "test_WYGT4F5ZT5BRRL5MPWHK6QLYOIXZXROD", }; const keysOnly = Object.keys(environmentVariables); diff --git a/package-lock.json b/package-lock.json index 7d44b181a..2e45411d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "Safelite", "version": "0.1.0", "dependencies": { + "@adyen/adyen-web": "^6.27.0", "@iframe-resizer/child": "^5.3.3", "axios": "^0.30.2", "bootstrap": "^5.3.3", @@ -59,6 +60,18 @@ "node": "8 || 9 || 10 || 11 || 12 || 13 || 14 || 15 || 16 || 17 || 18 || 19 || 20 || 21 || 22" } }, + "node_modules/@adyen/adyen-web": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/@adyen/adyen-web/-/adyen-web-6.27.0.tgz", + "integrity": "sha512-6E6hqEkMkoY/FevjO53zhgeCWMt+i/PDc11MX6eVM7Fvd+Sdgc9cIwTBz+NKIXs4ZerG6n2gd+ZIZEywqF/aBQ==", + "license": "MIT", + "dependencies": { + "@types/applepayjs": "14.0.9", + "@types/googlepay": "0.7.8", + "classnames": "2.5.1", + "preact": "10.22.1" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -3173,6 +3186,12 @@ "node": ">=10.13.0" } }, + "node_modules/@types/applepayjs": { + "version": "14.0.9", + "resolved": "https://registry.npmjs.org/@types/applepayjs/-/applepayjs-14.0.9.tgz", + "integrity": "sha512-xEprYbb0TEP/XIiDPbVnTYpDai8fTFpsQfVSfTd81Is2GOMUy7ie019eyX6Mz2ECxfjoUVKaiGSL577roIeHCg==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3328,6 +3347,12 @@ "@types/send": "*" } }, + "node_modules/@types/googlepay": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@types/googlepay/-/googlepay-0.7.8.tgz", + "integrity": "sha512-EHl7G7jIeit/Px/fi/Du/19SZDvqrRhy0DjJX40Vzs/97m3sro9mzVyAYNC0pfzpWGDY+zPjtTLEfhV+OYWUcQ==", + "license": "MIT" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -5789,6 +5814,12 @@ "dev": true, "license": "MIT" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -13681,6 +13712,16 @@ "dev": true, "license": "MIT" }, + "node_modules/preact": { + "version": "10.22.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.1.tgz", + "integrity": "sha512-jRYbDDgMpIb5LHq3hkI0bbl+l/TQ9UnkdQ0ww+lp+4MMOdqaUYdFc5qeyP+IV8FAd/2Em7drVPeKdQxsiWCf/A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/package.json b/package.json index 7d06ad112..c55259920 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "@adyen/adyen-web": "^6.27.0", "@iframe-resizer/child": "^5.3.3", "axios": "^0.30.2", "bootstrap": "^5.3.3", diff --git a/playwright-tests/pages/HomePage.ts b/playwright-tests/pages/HomePage.ts index 39e349790..3d44b023a 100644 --- a/playwright-tests/pages/HomePage.ts +++ b/playwright-tests/pages/HomePage.ts @@ -47,7 +47,7 @@ export class HomePage extends BasePage { //Zip Entry this.enterServiceZipTextBox = this.page.locator('#zipCodeTextbox'); - this.zipEntryLetsGetStartedButton = this.page.locator('#zipCodeTextboxButton'); + this.zipEntryLetsGetStartedButton = this.page.locator('#zipCodeTextbox'); this.getQuoteAndScheduleButton = this.page.getByLabel('main').getByRole('link', { name: 'Get quote + schedule' }); } @@ -61,10 +61,11 @@ export class HomePage extends BasePage { async letsGetStarted(zip: string, enterFunnelWithZip: boolean) { if (enterFunnelWithZip) { - await this.letsGetStartedButton.evaluate((element, zip) => { + await this.zipEntryLetsGetStartedButton.fill(zip); + /* await this.letsGetStartedButton.evaluate((element, zip) => { const currentHref = element.getAttribute('href') || ''; - element.setAttribute('href', `${currentHref}&zipCode=${zip}`); - }, zip); + element.setAttribute('href', `${currentHref}?zipCode=${zip}`); + }, zip);*/ } // Wait until modal pop up opens and close pop up diff --git a/playwright-tests/pages/LookupPage.ts b/playwright-tests/pages/LookupPage.ts index e5f2fca04..7d6c1df03 100644 --- a/playwright-tests/pages/LookupPage.ts +++ b/playwright-tests/pages/LookupPage.ts @@ -81,9 +81,9 @@ export class LookupPage extends BasePage { // Map of expected messages by lookup type const expectedMessages = { - 'address': "Your address didn't return a VIN match.Please re-enter the information below or provide your VIN in a different way.", - 'license plate': "Your license plate didn’t return a VIN match.Please re-enter the information above or provide your VIN in a different way.", - 'VIN': "Your VIN didn’t return a vehicle matchPlease re-enter your VIN or we can look up your VIN for you.", + 'address': "Your address didn't return a VIN match.Please re-enter the information below or provide your VIN in a different way.", + 'license plate': "Your license plate didn’t return a VIN match.Please re-enter the information above or provide your VIN in a different way.", + 'VIN': "Your VIN didn’t return a vehicle matchPlease re-enter your VIN or we can look up your VIN for you.", 'ZIP code': "Your ZIP code didn't return a match.Please re-enter the information above or provide your VIN in a different way." }; @@ -106,19 +106,19 @@ export class LookupPage extends BasePage { } } - async handleZipValidation(testData: Partial, zip: string, lookupType: VehicleLookupType, alertFlags?: IAlertFlags): Promise { + async handleZipValidation(testData: Partial): Promise { - let { vehicleDamage } = testData; + let { customerDetails,vehicleDamage, vehicleDetails, alertFlags } = testData; // Only proceed with validation if alertFlags is provided if (alertFlags) { if (alertFlags.isUnserviceableZip) { - await this.checkForUnserviceableZipAlertMessage(zip); + await this.checkForUnserviceableZipAlertMessage(customerDetails!.address.postalCode!); throw new TestSuccessAlert('Unserviceable ZIP validation successful.'); } else if (alertFlags.isInvalidZip) { await this.checkForInvalidZipAlertMessage(); throw new TestSuccessAlert('Invalid ZIP validation successful.'); } else if (alertFlags.isVinNotFound) { - await this.checkForVinNotFoundAlertMessage(lookupType); + await this.checkForVinNotFoundAlertMessage(vehicleDetails!.vehicleLookupType!); throw new TestSuccessAlert('Vin not found validation successful.'); } else { diff --git a/playwright-tests/pages/PaypalPage.ts b/playwright-tests/pages/PaypalPage.ts index ce5547f3c..e67e0a57b 100644 --- a/playwright-tests/pages/PaypalPage.ts +++ b/playwright-tests/pages/PaypalPage.ts @@ -41,6 +41,8 @@ export class PaypalPage extends BasePage { await this.paypalLoginButton.click(); await this.completePurchaseButton.click(); } else { + await this.usernameTextBox.waitFor({ state: 'visible' }); + await this.page.screenshot({ path: `test-results\\ortoni-data\\paypal-username-${Date.now()}.png`, fullPage: true }); await this.usernameTextBox.fill(paymentDetails.username!); await this.nextButton.click(); await this.tryAnotherWayButton.click(); diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 77b330aff..944b84136 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -35,6 +35,7 @@ export class SchedulePage extends BasePage { readonly timeSlots: Locator; readonly allDayDropOffButton: Locator; readonly pickATimeButton: Locator; + readonly mobileFirstModalCloseButton: Locator; constructor(page: Page) { super(page); @@ -65,6 +66,7 @@ export class SchedulePage extends BasePage { this.viewMoreDatesLink = this.page.getByText(/View more dates/).first(); this.appointmentDuration = this.page.locator('.duration-text-block'); this.timeSlots = this.page.locator('fieldset:has(>legend#chooseTimeSlot) label').filter({ visible: true}); + this.mobileFirstModalCloseButton = this.page.getByRole('dialog').getByRole('button', { name: 'Close' }); } async selectLocation(testData: Partial) { @@ -229,6 +231,9 @@ export class SchedulePage extends BasePage { async handleSchedulePage(testData: Partial) { const { appointmentDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.SchedulePage); + if (await this.mobileFirstModalCloseButton.isVisible()) { + await this.mobileFirstModalCloseButton.click(); + } await this.selectLocation(testData); await this.scheduleFirstAppointment(testData); await this.nextPage(); diff --git a/playwright-tests/pages/ServiceZipPage.ts b/playwright-tests/pages/ServiceZipPage.ts index 91393f892..6c1a178f5 100644 --- a/playwright-tests/pages/ServiceZipPage.ts +++ b/playwright-tests/pages/ServiceZipPage.ts @@ -12,9 +12,9 @@ export class ServiceZipPage extends LookupPage { @step("ZipLookupPage >> Lookup by service ZIP: ") async handleServiceZipPage(testData: Partial) { - const { customerDetails, vehicleDetails, alertFlags } = testData; + const { customerDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.ServiceZipPage); await this.enterZip(customerDetails!.address.postalCode!); - await this.handleZipValidation(testData, customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + await this.handleZipValidation(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/VehicleLookupAddressPage.ts b/playwright-tests/pages/VehicleLookupAddressPage.ts index c5f37ee3d..50a7f1b24 100644 --- a/playwright-tests/pages/VehicleLookupAddressPage.ts +++ b/playwright-tests/pages/VehicleLookupAddressPage.ts @@ -29,10 +29,10 @@ export class VehicleLookupAddressPage extends LookupPage { @step("VehicleLookupAddressPage >> Lookup by address: ") async handleVehicleLookupAddressPage(testData: Partial) { - const { customerDetails, vehicleDetails, alertFlags } = testData; + const { customerDetails, vehicleDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.VehicleLookupAddressPage); await this.lookupVehicleByAddress(customerDetails!, vehicleDetails!); - await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + await this.handleZipValidation(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/VehicleLookupLicensePage.ts b/playwright-tests/pages/VehicleLookupLicensePage.ts index e20828b6e..a19d2156b 100644 --- a/playwright-tests/pages/VehicleLookupLicensePage.ts +++ b/playwright-tests/pages/VehicleLookupLicensePage.ts @@ -34,11 +34,11 @@ export class VehicleLookupLicensePage extends LookupPage { @step("VehicleLookupLicensePage >> Lookup by license plate: ") async handleVehicleLookupLicensePage(testData: Partial) { - const { customerDetails, vehicleDetails, alertFlags } = testData; + const { customerDetails, vehicleDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.VehicleLookupLicensePage); await this.enterPlateDetails(vehicleDetails!); await this.enterZip(customerDetails!.address.postalCode!); - await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + await this.handleZipValidation(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/VinLookupPage.ts b/playwright-tests/pages/VinLookupPage.ts index 3b69fc2d6..2f508acda 100644 --- a/playwright-tests/pages/VinLookupPage.ts +++ b/playwright-tests/pages/VinLookupPage.ts @@ -24,11 +24,11 @@ export class VinLookupPage extends LookupPage { @step("VinLookupPage >> Lookup by VIN: ") async handleVehicleLookupVinPage(testData: Partial) { - const { customerDetails, vehicleDetails, alertFlags } = testData; + const { customerDetails, vehicleDetails } = testData; let vin: string; if (Array.isArray(vehicleDetails?.vin)) { - vin = vehicleDetails.vin[0]; + vin = vehicleDetails!.vin[0]; } else { vin = vehicleDetails?.vin || ''; } @@ -36,6 +36,6 @@ export class VinLookupPage extends LookupPage { await this.validateProgressBar(ProgressBarPercentages.VinLookupPage); await this.enterVin(vin); await this.enterZip(customerDetails!.address.postalCode!); - await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + await this.handleZipValidation(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/forms/AddressForm.ts b/playwright-tests/pages/forms/AddressForm.ts index 4ddb6a512..92f270320 100644 --- a/playwright-tests/pages/forms/AddressForm.ts +++ b/playwright-tests/pages/forms/AddressForm.ts @@ -57,7 +57,7 @@ export class AddressForm extends BasePage { ); }); - await this.page.hover(".pac-container .pac-item"); + await this.addressSuggestionList.hover(); await new Promise(resolve => setTimeout(resolve, 1000)); await this.addressSuggestionList.click(); // Select the first suggestion //await this.streetAddressTextBox.press('Tab'); // Move focus to ZIP code field diff --git a/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts b/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts index 7b16f582b..5705a95a3 100644 --- a/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts +++ b/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts @@ -1,6 +1,6 @@ //Imports here import { ITestData } from 'framework/TestData' -import { ServiceLocation, PaymentType } from 'safelite-playwright-core'; +import { ServiceLocation, ServicePackage, PaymentType } from 'safelite-playwright-core'; import { ITestCase } from '../framework/Typedefs' import { VehicleLookupType } from 'safelite-playwright-core'; import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core'; @@ -29,7 +29,8 @@ const cashReplaceDynamicRecalMobileData: Partial = { postalCode: '21237' } }, - + + servicePackage: ServicePackage.Premium, // Override vehicle details vehicleDetails: { ...getDefaultTestData().vehicleDetails!, @@ -59,7 +60,8 @@ const cashReplaceDynamicRecalMobileData: Partial = { // Override payment details paymentDetails: { - paymentType: PaymentType.PayAtService + paymentType: PaymentType.PayAtService, + promoCode: 'digitalrain' } } diff --git a/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts b/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts index f488a3555..4042c67de 100644 --- a/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts +++ b/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts @@ -35,7 +35,7 @@ const cashReplaceMultiSlidingGlassDropoffData: Partial = { appointmentDetails: { serviceLocation: ServiceLocation.DropOff, appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, - shopAddress: "6826 sawmill rd, Columbus, OH 43235" + shopAddress: "1343 cameron ave, lewis center, oh 43035" }, // Override vehicle details diff --git a/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts b/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts index d88ceeab6..7c01188cb 100644 --- a/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts +++ b/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts @@ -75,10 +75,10 @@ const lookupTypesToTest: LookupTestCase[] = [ }, customerDetails: { address: { - street: '4076 Spectacle Drive', + street: '4219 Turpin Ln', city: 'Columbus', state: 'OH', - postalCode: '59261', + postalCode: '43230', country: 'United States' } }, diff --git a/src/constants/application-config.js b/src/constants/application-config.js index 93a6f70d8..91873d455 100644 --- a/src/constants/application-config.js +++ b/src/constants/application-config.js @@ -22,6 +22,7 @@ const applicationConfig = { PIA_ERROR_URL: location.protocol + "//" + location.host + "/fmg/payment-pia-return?src=concept-funnel", CONFIRMATION_URL: location.protocol + "//" + location.host + "/fmg/confirmation", + PIA_ADYEN_RETURN_URL: location.protocol + "//" + location.host + "/fmg/virtual/payment/return", GOOGLE_CALENDAR: "https://www.google.com/calendar/render?action=TEMPLATE", YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60", OUTLOOK_CALENDAR: @@ -31,6 +32,8 @@ const applicationConfig = { COOKIE_NAME_LENGTH_MAX_LIMIT: 44, SESSION_TIMEOUT_MINUTES: process.env.__VUE_APP_SESSION_TIMEOUT_MINUTES__, RETURN_USER_PAGE: "/fmg/return-user", + ADYEN_CLIENT_KEY: process.env.__VUE_APP_ADYEN_CLIENT_KEY__, + ADYEN_ENVIRONMENT: process.env.__VUE_APP_ADYEN_ENVIRONMENT__, }; export { applicationConfig }; diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index ba11ad3a1..c23de7766 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -216,6 +216,14 @@ const endpoints = { url: "/parts/api/v1/parts/oem-after-market-parts", method: "POST", }, + InitializeAdyenPayment: { + url: "/payment/api/v1/payment/payment/session/initialize-session", + method: "POST", + }, + GetAdyenSessionResult: { + url: "/payment/api/v1/payment/payment/session/session-result", + method: "POST", + }, }; export { endpoints }; diff --git a/src/constants/experiments.js b/src/constants/experiments.js index bec7c9a39..a2a648a65 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -34,6 +34,7 @@ const experimentSettings = { SHOW_NO_PM_MOBILE_DAYS: "Show_NoPMmobileDays", SHOW_NO_MOBILE_AVAILABLE_DAYS: "Show_NoMobileAvailableDays", OFFER_OEM: "OfferOem", + USE_ADYEN_PAYMENT: "UseAdyenPayment", }; const experimentTriggers = { diff --git a/src/constants/progress-bar-mapper.js b/src/constants/progress-bar-mapper.js index a6e41d31b..a57774c21 100644 --- a/src/constants/progress-bar-mapper.js +++ b/src/constants/progress-bar-mapper.js @@ -80,6 +80,10 @@ export const pageProgressMapper = { percent: 98.5, step: 4, }, + "payment-adyen": { + percent: 98.5, + step: 4, + }, confirmation: { percent: 100, step: 4, diff --git a/src/digital-components/date-picker/date-picker.vue b/src/digital-components/date-picker/date-picker.vue index f48b1f43b..f38df0372 100644 --- a/src/digital-components/date-picker/date-picker.vue +++ b/src/digital-components/date-picker/date-picker.vue @@ -281,7 +281,7 @@ export default { if (event.screenX === 0 && event.screenY === 0) { return; } - this.$emit("date-clicked", date); + this.$emit("date-selected", date); }, getWeekStartDate(dateString) { const date = convertDateStringToDate(dateString); @@ -846,12 +846,6 @@ export default { }, }, watch: { - isLoading(newValue) { - // if done loading dates, then set to first available date - if (newValue === false && this.firstAvailableSelectableDate) { - this.selectedDate = this.firstAvailableSelectableDate; - } - }, modelValue(newValue) { this.resetField({ value: newValue, diff --git a/src/helpers/adyen-helper.js b/src/helpers/adyen-helper.js new file mode 100644 index 000000000..8de92bf89 --- /dev/null +++ b/src/helpers/adyen-helper.js @@ -0,0 +1,153 @@ +import { AdyenCheckout } from "@adyen/adyen-web/auto"; +import store from "@/store"; +import globalMethods from "@/global-methods"; +import { endpoints } from "@/constants/endpoints"; +import { AppointmentTypeStrings } from "@/constants/schedule-constants"; +import { paymentMethods } from "@/constants/payment-method-constants"; +import { applicationConfig } from "../constants/application-config"; + +export async function getNewSession(sessionConfig) {} + +export async function getSessionInfo(sessionId, encryptedSessionResult, pageNameToLog = "") { + const order = store.getters.order; + const location = getLocationInfo(order); + const workOrder = getWorkOrderLastSixDigits(order); + const system = getSourceSystem(); + + const request = { + sessionId: sessionId, + sessionResult: encryptedSessionResult, + zipCodeCtu: location.zipCodeCtu, + workOrderNumber: workOrder, + sourceSystem: system, + }; + + const response = await globalMethods.callHttpClient({ + method: endpoints.GetAdyenSessionResult.method, + endpoint: endpoints.GetAdyenSessionResult.url, + payload: request, + logApiCall: true, + pageNameToLog: pageNameToLog, + }); + + return response?.data; +} + +export async function createAdyenCheckout({ + session: { id, sessionData }, + handlers: { onPaymentCompleted, onPaymentFailed, onError }, + options: { showPayButton = true, amount }, +}) { + const config = { + session: { + id: id, + sessionData: sessionData, + }, + clientKey: applicationConfig.ADYEN_CLIENT_KEY, + environment: applicationConfig.ADYEN_ENVIRONMENT, + locale: "en_US", + countryCode: "US", + showPayButton: showPayButton, + translations: { + en_US: { + "creditCard.securityCode.label": "CVV/CVC", + }, + }, + onPaymentCompleted: onPaymentCompleted, + onPaymentFailed: onPaymentFailed, + onError: onError, + }; + + if (amount !== undefined && amount !== null) { + config.amount = { + value: amount, + currency: "USD", + }; + } + + console.log(`Creating checkout with configuration:`); + console.log(config); + + return await AdyenCheckout(config); +} + +export function mapAdyenToFmgPaymentMethod(adyenMethod) { + const paymentMethodMap = { + ["scheme"]: paymentMethods.CREDIT_CARD, + ["afterpaytouch"]: paymentMethods.AFTERPAY, + ["paypal"]: paymentMethods.PAYPAL, + ["applepay"]: paymentMethods.APPLE_PAY, + }; + + const match = paymentMethodMap[adyenMethod]; + + return match ?? paymentMethods.CREDIT_CARD; +} + +export function generateCcToken(adyenSessionInfo) { + const order = store.getters.order; + const locationInfo = getLocationInfo(order); + + const ccToken = { + subscriptionId: adyenSessionInfo?.storedToken, + expMonth: adyenSessionInfo?.cardExpiryMonth, + expYear: adyenSessionInfo?.cardExpiryYear, + cardType: adyenSessionInfo?.cardType, + billToPostalCode: locationInfo.zipCode, // TODO - real data? + billToFirstName: order.customer.firstName, // TODO - real data? + billToLastName: order.customer.lastName, // TODO - real data? + referenceNumber: adyenSessionInfo?.workOrderNumber, + authCode: adyenSessionInfo?.authCode, + transactionId: adyenSessionInfo?.transactionReference, + transReferenceNumber: adyenSessionInfo?.transactionReference, + lastFour: adyenSessionInfo?.last4DigitsOfCard, + }; + + return ccToken; +} + +function getLocationInfo(order) { + const serviceLocation = order.serviceLocation; + const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE; + if (isMobile) { + return { + address: serviceLocation.address, + address2: serviceLocation.address2, + city: serviceLocation.city, + state: serviceLocation.state, + zipCode: serviceLocation.zipCode, + zipCodeCtu: serviceLocation.zipCodeCtu, + }; + } else { + const providerInfo = serviceLocation.provider.address; + return { + address: providerInfo.streetAddress, + address2: "", + city: providerInfo.city, + state: providerInfo.state, + zipCode: providerInfo.zipCode, + zipCodeCtu: providerInfo.zipCodeCtu, + }; + } +} + +function getWorkOrderLastSixDigits(order) { + const workOrderNumber = order.workOrderNumber ?? ""; + const tokens = workOrderNumber.split("-"); + const lastSegment = tokens[tokens.length - 1] ?? ""; + const numDigits = lastSegment.length; + const numZeroes = 6 - numDigits; + + let result = ""; + for (let i = 0; i < numZeroes; i++) { + result += "0"; + } + + result += lastSegment; + + return result; +} + +function getSourceSystem() { + return "FMG-2.0"; +} diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue new file mode 100644 index 000000000..b407f9d55 --- /dev/null +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -0,0 +1,611 @@ + + + diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index 955d106b5..0fa02e80a 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -170,6 +170,7 @@ import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stas import { debugLog } from "@/helpers/debug-log-helper"; import { ErrorMessage } from "vee-validate"; import { Field } from "vee-validate"; +import experimentMixin from "../../mixins/experiment-mixin"; defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED)); defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED)); @@ -609,10 +610,17 @@ export default { } this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE); - this.$router.navigateWithoutSaving( - this.navigationScenarios.CLICKED_PAY_NOW, - this.pageName + + const shouldUseAdyen = experimentMixin.methods.hasSettingEqualTo( + experimentSettings.USE_ADYEN_PAYMENT, + "true" ); + + const scenarioName = shouldUseAdyen + ? this.navigationScenarios.CLICKED_PAY_NOW_ADYEN + : this.navigationScenarios.CLICKED_PAY_NOW; + + this.$router.navigateWithoutSaving(scenarioName, this.pageName); }, async evaluatePromosAndTaxItemsOnOrder() { //Revalidate promos if there are any inactive, or active promos diff --git a/src/layouts/payment-pia-return/payment-pia-return.vue b/src/layouts/payment-pia-return/payment-pia-return.vue index b1ec2b120..c905f3d78 100644 --- a/src/layouts/payment-pia-return/payment-pia-return.vue +++ b/src/layouts/payment-pia-return/payment-pia-return.vue @@ -156,6 +156,9 @@ export default { lastFour: lastFour, }; + console.log(`CCToken =`); + console.log(ccToken); + baseMixin.methods.dispatchStoreAction(storeActions.SAVE_CCTOKEN, ccToken, false); baseMixin.methods.dispatchStoreAction( storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT, diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 1ebf47c7e..948a7de19 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -22,6 +22,7 @@ cmsWidgetName="ScheduleYourServiceWidget" id="schedule-your-service" /> 0; + } else { + return this.selectableDatesInshop?.days?.length > 0; + } + }, }, methods: { splitCopyOnCMSPlaceHolder, @@ -1134,29 +1140,6 @@ export default { ); } }, - getTimeSlotInfo() { - // Get the current date - const currentDate = getTodayDate(); - - // Add 10 days to the current date - currentDate.setDate(currentDate.getDate() + 10); - - // Format the date as yyyy-mm-dd - const year = currentDate.getFullYear(); - const month = String(currentDate.getMonth() + 1).padStart(2, "0"); - const day = String(currentDate.getDate()).padStart(2, "0"); - - const formattedDate = `${year}-${month}-${day}`; - - return { - date: formattedDate, - endTime: "12:00", - jobMaxMinutes: "180", - jobMinMinutes: "120", - routeCode: "3357I-03357-M-I*20988*AM", - startTime: "08:00", - }; - }, onShopSelected(shopQuestionPopUpData) { this.resetWaitlist(); this.preSelectedDate = null; @@ -1209,8 +1192,6 @@ export default { async initializeDatePicker() { this.isShowMobileFirstAppt && this.showLoadingModal(); - this.selectedDate = null; - const includeMobileTimeSlots = this.isServiceableMobile; const includeInshopTimeSlots = this.isServiceableInshop || this.isServiceableDropoff; const datePickerInitialData = await this.$refs.datePicker.loadInitialData({ @@ -1601,28 +1582,40 @@ export default { handleWaitListRequested(value) { this.waitListRequested = value; }, - handleDateClicked(date) { - // do something to mark this as upcharge day or not... - if (date.isPricingByDayUpchargeDay) { - this.includePricingByDayUpcharge = true; - } else { - this.includePricingByDayUpcharge = false; + handleDateSelected(date) { + const previousDate = this.selectedDate; + + // check if date actually changed + if (previousDate !== date.dateString) { + this.selectedTimeSlotInfo = this.getEmptyTimeSlot(); + this.updateFooterButtonText(this.selectedTimeSlotInfo); } + + // TODO: REMOVE AS PART OF CASH-1634 + // // do something to mark this as upcharge day or not... + // if (date.isPricingByDayUpchargeDay) { + // this.includePricingByDayUpcharge = true; + // } else { + // this.includePricingByDayUpcharge = false; + // } + }, + getEmptyTimeSlot() { + return { + timeSlot: { + date: null, + routeCode: null, + startTime: null, + endTime: null, + jobMaxMinutes: null, + jobMinMinutes: null, + }, + isPremiumAppointment: null, + }; }, updateTimeSlot(timeSlotObj) { if (!timeSlotObj?.routeCode) { this.appointmentType = AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF; - this.selectedTimeSlotInfo = { - timeSlot: { - date: null, - routeCode: null, - startTime: null, - endTime: null, - jobMaxMinutes: null, - jobMinMinutes: null, - }, - isPremiumAppointment: null, - }; + this.selectedTimeSlotInfo = this.getEmptyTimeSlot(); return; } const timeSlot = this.timeSlotsForSelectedDate?.timeSlots?.find( @@ -1721,6 +1714,9 @@ export default { }, handleAppointmentTypeChange(newAppointmentType) { this.updateFooterButtonText(); + // if time appointment type changes, clear any selected time slot + this.selectedTimeSlotInfo = this.getEmptyTimeSlot(); + if (newAppointmentType === AppointmentTypeStrings.MOBILE) { // Remember last shop selected if previous selection was inshop/dropoff if ( @@ -1729,17 +1725,6 @@ export default { AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF) && this.selectedProvider ) { - this.selectedTimeSlotInfo = { - timeSlot: { - date: null, - routeCode: null, - startTime: null, - endTime: null, - jobMaxMinutes: null, - jobMinMinutes: null, - }, - isPremiumAppointment: null, - }; this.lastSelectedInshopOrDropoffProvider = this.selectedProvider; } this.appointmentType = AppointmentTypeStrings.MOBILE; @@ -1766,8 +1751,8 @@ export default { } else { this.appointmentType = null; } - this.setSelectedDateToFirstAvailable(); - this.setDisplayWaitList(); + this.setSelectedDateToFirstAvailable(); // v-if on appointmentTypeQuestion ensures dates have been loaded by now + this.setDisplayWaitList(); // v-if on appointmentTypeQuestion ensures dates have been loaded by now }, setSelectedDateToFirstAvailable() { this.selectedDate = this.getFirstAvailableDate(); @@ -1899,35 +1884,9 @@ export default { watch: { appointmentTypeFromAppointmentTypeQuestion: { handler(newValue, oldValue) { - this.handleAppointmentTypeChange(newValue); + this.handleAppointmentTypeChange(newValue); // v-if on appointmentTypeQuestion ensures dates have been loaded by now }, }, - selectedDate(newValue, oldValue) { - // Clear time slot selection if date selected changes - const selectedDate = this.getSelectedDateFromStore(); - if (oldValue && this.appointmentType === AppointmentTypeStrings.MOBILE) { - oldValue = `${oldValue}-mobile`; - } - const hasValueChanged = newValue !== oldValue; - const isDateDifferent = (newValue || oldValue) !== selectedDate; - - if (hasValueChanged && isDateDifferent && !this.selectedMobileFirstAppointment) { - this.selectedTimeSlotInfo = { - timeSlot: { - date: null, - routeCode: null, - startTime: null, - endTime: null, - jobMaxMinutes: null, - jobMinMinutes: null, - }, - isPremiumAppointment: null, - }; - } - }, - selectedTimeSlotInfo(newValue) { - this.updateFooterButtonText(newValue); - }, }, components: { funnelHeader, @@ -1940,7 +1899,6 @@ export default { textBlock, timeSlotQuestion, mobileFirstModal, - alert, serviceZipModalQuestion, appointmentTypeQuestion, diff --git a/src/layouts/schedule/time-slot-question/time-slot-question.vue b/src/layouts/schedule/time-slot-question/time-slot-question.vue index 1b8a2cb2e..d12757571 100644 --- a/src/layouts/schedule/time-slot-question/time-slot-question.vue +++ b/src/layouts/schedule/time-slot-question/time-slot-question.vue @@ -1,6 +1,7 @@