diff --git a/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/adyen_checkout/__tests__/applePayExpress.test.js b/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/adyen_checkout/__tests__/applePayExpress.test.js index cc3a920cb..0a714af5d 100644 --- a/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/adyen_checkout/__tests__/applePayExpress.test.js +++ b/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/adyen_checkout/__tests__/applePayExpress.test.js @@ -8,7 +8,7 @@ const { handleApplePayResponse, callPaymentFromComponent, formatCustomerObject, -} = require('../../applePayExpressCommon'); +} = require('../../applePayExpress'); beforeEach(() => { jest.clearAllMocks(); diff --git a/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/adyen_checkout/__tests__/renderGenericComponent.test.js b/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/adyen_checkout/__tests__/renderGenericComponent.test.js index 5651a486d..84645b1b8 100644 --- a/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/adyen_checkout/__tests__/renderGenericComponent.test.js +++ b/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/adyen_checkout/__tests__/renderGenericComponent.test.js @@ -58,6 +58,9 @@ beforeEach(() => { paymentMethodsResponse: { storedPaymentMethods: [{ supportedShopperInteractions: ['Ecommerce'] }], paymentMethods: [{ type: 'amazonpay' }], + adyenDescriptions: { + amazonpay: 'testDescription' + } }, })); window.installments = '[[0,2,["amex","hipercard"]]]'; @@ -69,11 +72,9 @@ beforeEach(() => { countryCode: 'mocked_countrycode', }; getPaymentMethods.mockReturnValue({ - json: jest.fn().mockReturnValue({ - adyenConnectedTerminals: { uniqueTerminalIds: ['mocked_id'] }, - imagePath: 'example.com', - adyenDescriptions: {}, - }), + adyenConnectedTerminals: { uniqueTerminalIds: ['mocked_id'] }, + imagePath: 'example.com', + adyenDescriptions: {}, }); }); describe('Render Generic Component', () => { diff --git a/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/adyen_checkout/renderGenericComponent.js b/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/adyen_checkout/renderGenericComponent.js index 7f02b9482..99c4d77f4 100644 --- a/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/adyen_checkout/renderGenericComponent.js +++ b/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/adyen_checkout/renderGenericComponent.js @@ -217,8 +217,7 @@ function setGiftCardContainerVisibility() { } export async function initializeCheckout() { - const paymentMethods = await getPaymentMethods(); - const paymentMethodsResponse = await paymentMethods.json(); + const paymentMethodsResponse = await getPaymentMethods(); const giftCardsData = await fetchGiftCards(); setCheckoutConfiguration(paymentMethodsResponse); diff --git a/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/applePayExpressCommon.js b/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/applePayExpressCommon.js deleted file mode 100644 index 8f759e9f6..000000000 --- a/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/applePayExpressCommon.js +++ /dev/null @@ -1,366 +0,0 @@ -const helpers = require('./adyen_checkout/helpers'); -const { - checkIfExpressMethodsAreReady, - updateLoadedExpressMethods, - getPaymentMethods, - createTemporaryBasket, -} = require('./commons'); -const { APPLE_PAY } = require('./constants'); - -let checkout; -let shippingMethodsData; -let paymentMethodsResponse; -let temporaryBasketId; - -function formatCustomerObject(customerData, billingData) { - return { - addressBook: { - addresses: {}, - preferredAddress: { - address1: customerData.addressLines[0], - address2: - customerData.addressLines.length > 1 - ? customerData.addressLines[1] - : null, - city: customerData.locality, - countryCode: { - displayValue: customerData.country, - value: customerData.countryCode, - }, - firstName: customerData.givenName, - lastName: customerData.familyName, - ID: customerData.emailAddress, - postalCode: customerData.postalCode, - stateCode: customerData.administrativeArea, - }, - }, - billingAddressDetails: { - address1: billingData.addressLines[0], - address2: - billingData.addressLines.length > 1 - ? billingData.addressLines[1] - : null, - city: billingData.locality, - countryCode: { - displayValue: billingData.country, - value: billingData.countryCode, - }, - firstName: billingData.givenName, - lastName: billingData.familyName, - postalCode: billingData.postalCode, - stateCode: billingData.administrativeArea, - }, - customer: {}, - profile: { - firstName: customerData.givenName, - lastName: customerData.familyName, - email: customerData.emailAddress, - phone: customerData.phoneNumber, - }, - }; -} - -function handleAuthorised(response, resolveApplePay) { - resolveApplePay(); - document.querySelector('#result').value = JSON.stringify({ - pspReference: response.fullResponse?.pspReference, - resultCode: response.fullResponse?.resultCode, - paymentMethod: response.fullResponse?.paymentMethod - ? response.fullResponse.paymentMethod - : response.fullResponse?.additionalData?.paymentMethod, - donationToken: response.fullResponse?.donationToken, - amount: response.fullResponse?.amount, - }); - document.querySelector('#showConfirmationForm').submit(); -} - -function handleError(rejectApplePay) { - rejectApplePay(); - document.querySelector('#result').value = JSON.stringify({ - error: true, - }); - document.querySelector('#showConfirmationForm').submit(); -} - -function handleApplePayResponse(response, resolveApplePay, rejectApplePay) { - if (response.resultCode === 'Authorised') { - handleAuthorised(response, resolveApplePay); - } else { - handleError(rejectApplePay); - } -} - -function callPaymentFromComponent(data, resolveApplePay, rejectApplePay) { - return $.ajax({ - url: window.paymentFromComponentURL, - type: 'post', - data: { - data: JSON.stringify(data), - paymentMethod: APPLE_PAY, - csrf_token: $('#adyen-token').val(), - }, - success(response) { - helpers.createShowConfirmationForm(window.showConfirmationAction); - helpers.setOrderFormData(response); - document.querySelector('#additionalDetailsHidden').value = - JSON.stringify(data); - handleApplePayResponse(response, resolveApplePay, rejectApplePay); - }, - }).fail(() => { - rejectApplePay(); - }); -} - -function selectShippingMethod({ shipmentUUID, ID }, basketId) { - const request = { - paymentMethodType: APPLE_PAY, - shipmentUUID, - methodID: ID, - basketId, - }; - return fetch(window.selectShippingMethodUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json; charset=utf-8', - }, - body: JSON.stringify(request), - }); -} - -function getShippingMethod(shippingContact, basketId) { - const request = { - paymentMethodType: APPLE_PAY, - basketId, - }; - if (shippingContact) { - request.address = { - city: shippingContact.locality, - country: shippingContact.country, - countryCode: shippingContact.countryCode, - stateCode: shippingContact.administrativeArea, - postalCode: shippingContact.postalCode, - }; - } - return fetch(window.shippingMethodsUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json; charset=utf-8', - }, - body: JSON.stringify(request), - }); -} - -async function initializeCheckout() { - const paymentMethods = await getPaymentMethods(); - paymentMethodsResponse = await paymentMethods.json(); - const applicationInfo = paymentMethodsResponse?.applicationInfo; - checkout = await AdyenCheckout({ - environment: window.environment, - clientKey: window.clientKey, - locale: window.locale, - analytics: { - analyticsData: { applicationInfo }, - }, - }); -} - -async function createApplePayButton(applePayButtonConfig) { - return checkout.create(APPLE_PAY, applePayButtonConfig); -} - -async function init() { - initializeCheckout() - .then(async () => { - const applePayPaymentMethod = - paymentMethodsResponse?.AdyenPaymentMethods?.paymentMethods.find( - (pm) => pm.type === APPLE_PAY, - ); - if (!applePayPaymentMethod) { - updateLoadedExpressMethods(APPLE_PAY); - checkIfExpressMethodsAreReady(); - return; - } - - const applePayConfig = applePayPaymentMethod.configuration; - const applePayButtonConfig = { - showPayButton: true, - isExpress: true, - configuration: applePayConfig, - amount: JSON.parse(window.basketAmount), - requiredShippingContactFields: ['postalAddress', 'email', 'phone'], - requiredBillingContactFields: ['postalAddress', 'phone'], - // shippingMethods: shippingMethodsData.shippingMethods.map((sm) => ({ - // label: sm.displayName, - // detail: sm.description, - // identifier: sm.ID, - // amount: `${sm.shippingCost.value}`, - // })), - onAuthorized: async (resolve, reject, event) => { - try { - const customerData = event.payment.shippingContact; - const billingData = event.payment.billingContact; - const customer = formatCustomerObject(customerData, billingData); - const stateData = { - paymentMethod: { - type: APPLE_PAY, - applePayToken: event.payment.token.paymentData, - }, - paymentType: 'express', - }; - - const resolveApplePay = () => { - // ** is used instead of Math.pow - const value = - applePayButtonConfig.amount.value * - 10 ** parseInt(window.digitsNumber, 10); - const finalPriceUpdate = { - newTotal: { - type: 'final', - label: applePayConfig.merchantName, - amount: `${Math.round(value)}`, - }, - }; - resolve(finalPriceUpdate); - }; - - await callPaymentFromComponent( - { ...stateData, customer, basketId: temporaryBasketId }, - resolveApplePay, - reject, - ); - } catch (error) { - reject(error); - } - }, - onSubmit: () => { - // This handler is empty to prevent sending a second payment request - // We already do the payment in paymentFromComponent - }, - onClick: async (resolve, reject) => { - if (window.isExpressPdp) { - const tempBasket = await createTemporaryBasket(); - if (tempBasket.ok) { - const tempBasketResponse = await tempBasket.json(); - temporaryBasketId = tempBasketResponse.basketId; - applePayButtonConfig.amount = { - value: tempBasketResponse.amount.value, - currency: tempBasketResponse.amount.currency, - }; - const applePayAmountUpdate = { - newTotal: { - type: 'final', - label: applePayConfig.merchantName, - amount: tempBasketResponse.amount.value, - }, - }; - resolve(applePayAmountUpdate); - } else { - reject(); - } - } else { - resolve(); - } - }, - onShippingMethodSelected: async (resolve, reject, event) => { - const { shippingMethod } = event; - const matchingShippingMethod = - shippingMethodsData.shippingMethods.find( - (sm) => sm.ID === shippingMethod.identifier, - ); - const calculationResponse = await selectShippingMethod( - matchingShippingMethod, - temporaryBasketId, - ); - if (calculationResponse.ok) { - const newCalculation = await calculationResponse.json(); - applePayButtonConfig.amount = { - value: newCalculation.grandTotalAmount.value, - currency: newCalculation.grandTotalAmount.currency, - }; - const applePayShippingMethodUpdate = { - newTotal: { - type: 'final', - label: applePayConfig.merchantName, - amount: newCalculation.grandTotalAmount.value, - }, - }; - resolve(applePayShippingMethodUpdate); - } else { - reject(); - } - }, - onShippingContactSelected: async (resolve, reject, event) => { - const { shippingContact } = event; - const shippingMethods = await getShippingMethod( - shippingContact, - temporaryBasketId, - ); - if (shippingMethods.ok) { - shippingMethodsData = await shippingMethods.json(); - if (shippingMethodsData.shippingMethods?.length) { - const selectedShippingMethod = - shippingMethodsData.shippingMethods[0]; - const calculationResponse = await selectShippingMethod( - selectedShippingMethod, - temporaryBasketId, - ); - if (calculationResponse.ok) { - const shippingMethodsStructured = - shippingMethodsData.shippingMethods.map((sm) => ({ - label: sm.displayName, - detail: sm.description, - identifier: sm.ID, - amount: `${sm.shippingCost.value}`, - })); - const newCalculation = await calculationResponse.json(); - const applePayShippingContactUpdate = { - newShippingMethods: shippingMethodsStructured, - newTotal: { - type: 'final', - label: applePayConfig.merchantName, - amount: newCalculation.grandTotalAmount.value, - }, - }; - resolve(applePayShippingContactUpdate); - } else { - reject(); - } - } else { - reject(); - } - } else { - reject(); - } - }, - }; - - const cartContainer = document.getElementsByClassName(APPLE_PAY); - const applePayButton = await createApplePayButton(applePayButtonConfig); - const isApplePayButtonAvailable = await applePayButton.isAvailable(); - if (isApplePayButtonAvailable) { - for ( - let expressCheckoutNodesIndex = 0; - expressCheckoutNodesIndex < cartContainer.length; - expressCheckoutNodesIndex += 1 - ) { - applePayButton.mount(cartContainer[expressCheckoutNodesIndex]); - } - } - - updateLoadedExpressMethods(APPLE_PAY); - checkIfExpressMethodsAreReady(); - }) - .catch(() => { - updateLoadedExpressMethods(APPLE_PAY); - checkIfExpressMethodsAreReady(); - }); -} - -module.exports = { - handleAuthorised, - handleError, - handleApplePayResponse, - callPaymentFromComponent, - formatCustomerObject, - init, -}; diff --git a/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/googlePayExpress.js b/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/googlePayExpress.js index 38d70d3dd..e54fdd088 100644 --- a/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/googlePayExpress.js +++ b/src/cartridges/app_adyen_SFRA/cartridge/client/default/js/googlePayExpress.js @@ -7,6 +7,14 @@ const { GOOGLE_PAY } = require('./constants'); let checkout; let googlePayButton; +let shippingMethodsData; +let temporaryBasketId; + +const CALLBACK_TRIGGERS = { + INITIALIZE: 'INITIALIZE', + SHIPPING_ADDRESS: 'SHIPPING_ADDRESS', + SHIPPING_OPTION: 'SHIPPING_OPTION', +}; function formatCustomerObject(customerData) { const shippingData = customerData.shippingAddress; @@ -55,6 +63,122 @@ function formatCustomerObject(customerData) { }; } +async function getShippingMethods(shippingAddress, basketId) { + const requestBody = { + paymentMethodType: GOOGLE_PAY, + basketId, + }; + if (shippingAddress) { + requestBody.address = { + city: shippingAddress.locality, + country: shippingAddress.country, + countryCode: shippingAddress.countryCode, + stateCode: shippingAddress.administrativeArea, + postalCode: shippingAddress.postalCode, + }; + } + return $.ajax({ + type: 'POST', + url: window.shippingMethodsUrl, + data: { + csrf_token: $('#adyen-token').val(), + data: JSON.stringify(requestBody), + }, + success(response) { + return response; + }, + }); +} + +async function selectShippingMethod({ shipmentUUID, ID }, basketId) { + const requestBody = { + paymentMethodType: GOOGLE_PAY, + shipmentUUID, + methodID: ID, + basketId, + }; + return $.ajax({ + type: 'POST', + url: window.selectShippingMethodUrl, + data: { + csrf_token: $('#adyen-token').val(), + data: JSON.stringify(requestBody), + }, + success(response) { + return response; + }, + }); +} + +function getTransactionInfo(newCalculation) { + return { + displayItems: [ + { + price: newCalculation.totals.totalShippingCost.substring(1), + label: 'Shipping', + type: 'LINE_ITEM', + status: 'FINAL', + }, + { + price: newCalculation.totals.totalTax.substring(1), + label: 'Tax', + type: 'TAX', + status: 'FINAL', + }, + { + price: newCalculation.totals.subTotal.substring(1), + label: 'Subtotal', + type: 'SUBTOTAL', + status: 'FINAL', + }, + ], + countryCode: shippingMethodsData.locale.slice(-2), + currencyCode: newCalculation.grandTotalAmount.currency, + totalPriceStatus: 'FINAL', + totalPriceLabel: 'Total', + totalPrice: `${newCalculation.grandTotalAmount.value}`, + }; +} + +function getShippingOptionsParameters(selectedShippingMethod) { + return { + defaultSelectedOptionId: selectedShippingMethod.ID, + shippingOptions: shippingMethodsData.shippingMethods.map((sm) => ({ + label: sm.displayName, + description: sm.description, + id: sm.ID, + })), + }; +} + +function handleAuthorised(response) { + document.querySelector('#result').value = JSON.stringify({ + pspReference: response.fullResponse?.pspReference, + resultCode: response.fullResponse?.resultCode, + paymentMethod: response.fullResponse?.paymentMethod + ? response.fullResponse.paymentMethod + : response.fullResponse?.additionalData?.paymentMethod, + donationToken: response.fullResponse?.donationToken, + amount: response.fullResponse?.amount, + }); + document.querySelector('#showConfirmationForm').submit(); +} + +function handleError() { + document.querySelector('#result').value = JSON.stringify({ + error: true, + }); + document.querySelector('#showConfirmationForm').submit(); +} + +function handleGooglePayResponse(response) { + if (response.resultCode === 'Authorised') { + handleAuthorised(response); + } else { + handleError(); + } +} + function paymentFromComponent(data) { $.ajax({ url: window.paymentFromComponentURL, @@ -67,6 +191,13 @@ function paymentFromComponent(data) { success(response) { helpers.createShowConfirmationForm(window.showConfirmationAction); helpers.setOrderFormData(response); + document.querySelector('#additionalDetailsHidden').value = JSON.stringify( + { + ...data, + ...response, + }, + ); + handleGooglePayResponse(response); }, }); } @@ -83,6 +214,52 @@ async function initializeCheckout(paymentMethodsResponse) { }); } +async function onShippingAddressChange( + shippingAddress, + paymentDataRequestUpdate, +) { + shippingMethodsData = await getShippingMethods( + shippingAddress, + temporaryBasketId, + ); + if (shippingMethodsData?.shippingMethods?.length) { + const selectedShippingMethod = shippingMethodsData.shippingMethods[0]; + const newCalculation = await selectShippingMethod( + selectedShippingMethod, + temporaryBasketId, + ); + if (newCalculation?.grandTotalAmount) { + paymentDataRequestUpdate.newShippingOptionParameters = + getShippingOptionsParameters(selectedShippingMethod); + paymentDataRequestUpdate.newTransactionInfo = + getTransactionInfo(newCalculation); + return true; + } + return false; + } + return false; +} + +async function onShippingOptionChange( + shippingOptionData, + paymentDataRequestUpdate, +) { + const shippingMethods = shippingMethodsData?.shippingMethods; + const matchingShippingMethod = shippingMethods.find( + (sm) => sm.ID === shippingOptionData.id, + ); + const newCalculation = await selectShippingMethod( + matchingShippingMethod, + temporaryBasketId, + ); + if (newCalculation?.grandTotalAmount) { + paymentDataRequestUpdate.newTransactionInfo = + getTransactionInfo(newCalculation); + return true; + } + return false; +} + async function init(paymentMethodsResponse) { initializeCheckout(paymentMethodsResponse) .then(async () => { @@ -127,38 +304,40 @@ async function init(paymentMethodsResponse) { }, onSubmit: async () => {}, paymentDataCallbacks: { - onPaymentDataChanged() { - // TODO : This callback will be replaced by actual implementation on SFI-1020 - return new Promise((resolve) => { - const paymentDataRequestUpdate = { - newShippingOptionParameters: { - defaultSelectedOptionId: 'shipping-001', - shippingOptions: [ - { - id: 'shipping-001', - label: '$0.00: Free shipping', - description: - 'Free shipping: delivered in 10 business days.', - }, - ], - }, - newTransactionInfo: { - displayItems: [ - { - label: 'Shipping', - type: 'LINE_ITEM', - price: '80.00', - status: 'FINAL', - }, - ], - currencyCode: 'EUR', - totalPriceStatus: 'FINAL', - totalPrice: '80.00', - totalPriceLabel: 'Total', - countryCode: 'US', - }, - }; - resolve(paymentDataRequestUpdate); + async onPaymentDataChanged(intermediatePaymentData) { + const { callbackTrigger, shippingAddress, shippingOptionData } = + intermediatePaymentData; + + const paymentDataRequestUpdate = {}; + let onShippingAddressChangeStatus = true; + let onShippingOptionChangeStatus = true; + + if ( + callbackTrigger === CALLBACK_TRIGGERS.INITIALIZE || + callbackTrigger === CALLBACK_TRIGGERS.SHIPPING_ADDRESS + ) { + onShippingAddressChangeStatus = await onShippingAddressChange( + shippingAddress, + paymentDataRequestUpdate, + ); + } + + if (callbackTrigger === CALLBACK_TRIGGERS.SHIPPING_OPTION) { + onShippingOptionChangeStatus = await onShippingOptionChange( + shippingOptionData, + paymentDataRequestUpdate, + ); + } + + return new Promise((resolve, reject) => { + if ( + onShippingAddressChangeStatus && + onShippingOptionChangeStatus + ) { + resolve(paymentDataRequestUpdate); + } else { + reject(); + } }); }, }, diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/showConfirmation/handlePaymentFromComponent.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/showConfirmation/handlePaymentFromComponent.js index 51bd060ab..dc0522f0a 100644 --- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/showConfirmation/handlePaymentFromComponent.js +++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/showConfirmation/handlePaymentFromComponent.js @@ -135,6 +135,7 @@ function handlePayment(stateData, order, options) { result && (JSON.stringify(result).indexOf('amazonpay') > -1 || JSON.stringify(result).indexOf('applepay') > -1 || + JSON.stringify(result).indexOf('googlepay') > -1 || JSON.stringify(result).indexOf('cashapp') > -1) ) { finalResult = JSON.parse(result); diff --git a/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenHelper.js b/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenHelper.js index 4feb36364..97d2fa38d 100644 --- a/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenHelper.js +++ b/src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenHelper.js @@ -634,6 +634,9 @@ let adyenHelperObj = { case 'paypal': methodName = 'PayPal'; break; + case 'googlepay': + methodName = 'Google Pay'; + break; default: methodName = paymentMethod; } diff --git a/src/cartridges/int_adyen_SFRA/cartridge/controllers/Adyen.js b/src/cartridges/int_adyen_SFRA/cartridge/controllers/Adyen.js index 301897cb3..da6dfebab 100644 --- a/src/cartridges/int_adyen_SFRA/cartridge/controllers/Adyen.js +++ b/src/cartridges/int_adyen_SFRA/cartridge/controllers/Adyen.js @@ -29,6 +29,7 @@ server.post( server.post( 'ShippingMethods', server.middleware.https, + csrf.validateRequest, adyen.callGetShippingMethods, ); @@ -38,6 +39,7 @@ server.post( server.post( 'SelectShippingMethod', server.middleware.https, + csrf.validateRequest, adyen.callSelectShippingMethod, );