diff --git a/adyen/components/AdyenCheckout.js b/adyen/components/AdyenCheckout.js index 6f1bd76..f4b82f1 100644 --- a/adyen/components/AdyenCheckout.js +++ b/adyen/components/AdyenCheckout.js @@ -1,21 +1,23 @@ -import React, {useRef, useEffect} from 'react' +import React, {useEffect, useRef} from 'react' import AdyenCheckout from '@adyen/adyen-web' import '@adyen/adyen-web/dist/adyen.css' import {useAdyenCheckout} from '../context/adyen-checkout-context' -const AdyenCheckoutComponent = () => { - const {adyenPaymentMethods, adyenPaymentMethodsConfig, setAdyenStateData} = useAdyenCheckout() +const AdyenCheckoutComponent = (props) => { + const {adyenPaymentMethods, getPaymentMethodsConfiguration, setAdyenStateData} = + useAdyenCheckout() const paymentContainer = useRef(null) useEffect(() => { const createCheckout = async () => { + const paymentMethodsConfiguration = await getPaymentMethodsConfiguration(props) const checkout = await AdyenCheckout({ environment: adyenPaymentMethods.ADYEN_ENVIRONMENT, clientKey: adyenPaymentMethods.ADYEN_CLIENT_KEY, paymentMethodsResponse: adyenPaymentMethods, - paymentMethodsConfiguration: adyenPaymentMethodsConfig, + paymentMethodsConfiguration: paymentMethodsConfiguration, onAdditionalDetails(state, element) { - adyenPaymentMethodsConfig.card.onAdditionalDetails(state, element) + paymentMethodsConfiguration.card.onAdditionalDetails(state, element) }, onChange: (state) => { if (state.isValid) { diff --git a/adyen/components/helpers/baseConfig.js b/adyen/components/helpers/baseConfig.js index 438cbe2..2d1d787 100644 --- a/adyen/components/helpers/baseConfig.js +++ b/adyen/components/helpers/baseConfig.js @@ -1,50 +1,54 @@ import {AdyenPaymentsService} from '../../services/payments' import {AdyenPaymentsDetailsService} from '../../services/payments-details' -export const baseConfig = (props) => { - const onSubmit = async (state, component) => { - try { - if (!state.isValid) { - throw new Error('invalid state') - } - const adyenPaymentService = new AdyenPaymentsService(props?.token) - const paymentsResponse = await adyenPaymentService.submitPayment( - state.data, - props.basketId, - props?.customerId - ) - if (paymentsResponse?.isSuccessful) { - props?.successHandler(paymentsResponse.merchantReference) - } else if (paymentsResponse?.action) { - await component.handleAction(paymentsResponse.action) - } else { - // handle error - } - } catch (error) { - props?.errorHandler(error) - } +import {executeCallbacks} from '../../utils/executeCallbacks' + +export const baseConfig = ({ + beforeSubmit = [], + afterSubmit = [], + beforeAdditionalDetails = [], + afterAdditionalDetails = [], + onError = (error) => { + console.log(JSON.stringify(error)) + }, + ...props +}) => { + return { + onSubmit: executeCallbacks([...beforeSubmit, onSubmit, ...afterSubmit], props, onError), + onAdditionalDetails: executeCallbacks( + [...beforeAdditionalDetails, onAdditionalDetails, ...afterAdditionalDetails], + props, + onError + ) } +} - const onAdditionalDetails = async (state, component) => { - try { - const adyenPaymentsDetailsService = new AdyenPaymentsDetailsService(props?.token) - const paymentsDetailsResponse = await adyenPaymentsDetailsService.submitPaymentsDetails( - state.data, - props?.customerId - ) - if (paymentsDetailsResponse?.isSuccessful) { - props?.successHandler(paymentsDetailsResponse.merchantReference) - } else if (paymentsDetailsResponse?.action) { - await component.handleAction(paymentsDetailsResponse.action) - } else { - // handle error - } - } catch (error) { - props?.errorHandler(error) +export const onSubmit = async (state, component, props) => { + try { + if (!state.isValid) { + throw new Error('invalid state') } + // await props.submitBilling() + const adyenPaymentService = new AdyenPaymentsService(props?.token) + const paymentsResponse = await adyenPaymentService.submitPayment( + state.data, + props.basketId, + props?.customerId + ) + return {paymentsResponse: paymentsResponse} + } catch (error) { + return new Error(error) } +} - return { - onSubmit: onSubmit, - onAdditionalDetails: onAdditionalDetails +export const onAdditionalDetails = async (state, component, props) => { + try { + const adyenPaymentsDetailsService = new AdyenPaymentsDetailsService(props?.token) + const paymentsDetailsResponse = await adyenPaymentsDetailsService.submitPaymentsDetails( + state.data, + props?.customerId + ) + return {paymentsDetailsResponse: paymentsDetailsResponse} + } catch (error) { + return new Error(error) } } diff --git a/adyen/components/paymentMethodsConfiguration.js b/adyen/components/paymentMethodsConfiguration.js index b01c383..f7b39cb 100644 --- a/adyen/components/paymentMethodsConfiguration.js +++ b/adyen/components/paymentMethodsConfiguration.js @@ -4,7 +4,7 @@ import {cardConfig} from './card/config' import {paypalConfig} from './paypal/config' import {applePayConfig} from './applepay/config' -export const paymentMethodsConfiguration = ({paymentMethods, ...props}) => { +export const paymentMethodsConfiguration = ({paymentMethods = [], ...props}) => { const defaultConfig = baseConfig(props) const paymentMethodsConfig = { card: cardConfig(props), diff --git a/adyen/context/adyen-checkout-context.js b/adyen/context/adyen-checkout-context.js index c3ee498..dfb80f7 100644 --- a/adyen/context/adyen-checkout-context.js +++ b/adyen/context/adyen-checkout-context.js @@ -1,11 +1,10 @@ import React, {useEffect, useState} from 'react' import {useLocation} from 'react-router-dom' import PropTypes from 'prop-types' -import {useAccessToken, useCustomerId} from '@salesforce/commerce-sdk-react' +import {useAccessToken, useCustomerId, useCustomerType} from '@salesforce/commerce-sdk-react' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' import {resolveLocaleFromUrl} from '@salesforce/retail-react-app/app/utils/site-utils' import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation' -import {useCustomerType} from '@salesforce/commerce-sdk-react' import {AdyenPaymentMethodsService} from '../services/payment-methods' import {paymentMethodsConfiguration} from '../components/paymentMethodsConfiguration' @@ -24,7 +23,6 @@ export const AdyenCheckoutProvider = ({children}) => { const [adyenPaymentMethods, setAdyenPaymentMethods] = useState() const [adyenStateData, setAdyenStateData] = useState() const [paymentConfig, setPaymentConfig] = useState() - const [adyenPaymentMethodsConfig, setAdyenPaymentMethodsConfig] = useState() useEffect(() => { const fetchPaymentMethods = async () => { @@ -37,18 +35,6 @@ export const AdyenCheckoutProvider = ({children}) => { locale ) setAdyenPaymentMethods(data ? data : {error: true}) - setAdyenPaymentMethodsConfig( - paymentMethodsConfiguration({ - paymentMethods: data.paymentMethods, - customerType, - token, - basketId: basket.basketId, - customerId, - successHandler: (merchantReference) => - navigate(`/checkout/confirmation/${merchantReference}`), - errorHandler: (error) => console.log(error) - }) - ) setFetching(false) } catch (error) { setAdyenPaymentMethods({error}) @@ -61,14 +47,56 @@ export const AdyenCheckoutProvider = ({children}) => { } }, [basket?.basketId]) + const getPaymentMethodsConfiguration = async ({ + beforeSubmit = [], + afterSubmit = [], + beforeAdditionalDetails = [], + afterAdditionalDetails = [], + onError + }) => { + const token = await getTokenWhenReady() + return paymentMethodsConfiguration({ + paymentMethods: adyenPaymentMethods?.paymentMethods, + customerType, + token, + basketId: basket.basketId, + customerId, + onError: onError, + afterSubmit: [...afterSubmit, onPaymentsSuccess], + beforeSubmit: beforeSubmit, + afterAdditionalDetails: [...afterAdditionalDetails, onPaymentsDetailsSuccess], + beforeAdditionalDetails: beforeAdditionalDetails + }) + } + + const onPaymentsSuccess = async (state, component, props, responses) => { + if (responses?.paymentsResponse?.isSuccessful) { + navigate(`/checkout/confirmation/${responses?.paymentsResponse?.merchantReference}`) + } else if (responses?.paymentsResponse?.action) { + await component.handleAction(responses?.paymentsResponse?.action) + } else { + return new Error(responses?.paymentsResponse) + } + } + + const onPaymentsDetailsSuccess = async (state, component, props, responses) => { + if (responses?.paymentsDetailsResponse?.isSuccessful) { + navigate( + `/checkout/confirmation/${responses?.paymentsDetailsResponse?.merchantReference}` + ) + } else if (responses?.paymentsDetailsResponse?.action) { + await component.handleAction(responses?.paymentsDetailsResponse?.action) + } else { + return new Error(responses?.paymentsDetailsResponse) + } + } + const value = { adyenPaymentMethods, adyenStateData, paymentConfig, - adyenPaymentMethodsConfig, setAdyenStateData: (data) => setAdyenStateData(data), - setPaymentConfig: (data) => setPaymentConfig(data), - setAdyenPaymentMethodsConfig: (data) => setAdyenPaymentMethodsConfig(data) + getPaymentMethodsConfiguration } return {children} diff --git a/adyen/controllers/payments.js b/adyen/controllers/payments.js index 93d5242..bed212d 100644 --- a/adyen/controllers/payments.js +++ b/adyen/controllers/payments.js @@ -2,15 +2,14 @@ import {formatAddressInAdyenFormat} from '../utils/formatAddress.mjs' import {getCurrencyValueForApi} from '../utils/parsers.mjs' import { APPLICATION_VERSION, - SHOPPER_INTERACTIONS, + PAYMENT_METHODS, RECURRING_PROCESSING_MODEL, - PAYMENT_METHODS + SHOPPER_INTERACTIONS } from '../utils/constants.mjs' import {createCheckoutResponse} from '../utils/createCheckoutResponse.mjs' -import {Checkout} from 'commerce-sdk' -import {ShopperOrders, ShopperBaskets} from 'commerce-sdk-isomorphic' +import {ShopperBaskets, ShopperOrders} from 'commerce-sdk-isomorphic' import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config' -import AdyenCheckoutConfig from './checkout-config'; +import AdyenCheckoutConfig from './checkout-config' const errorMessages = { AMOUNT_NOT_CORRECT: 'amount not correct', @@ -62,15 +61,6 @@ async function sendPayments(req, res) { }) } - if (!basket.billingAddress) { - await shopperBaskets.updateBillingAddressForBasket({ - body: basket.shipments[0].shippingAddress, - parameters: { - basketId: req.headers.basketid - } - }) - } - const shopperOrders = new ShopperOrders({ ...appConfig.commerceAPI, headers: {authorization: req.headers.authorization} diff --git a/adyen/utils/executeCallbacks.js b/adyen/utils/executeCallbacks.js new file mode 100644 index 0000000..b0c4fe9 --- /dev/null +++ b/adyen/utils/executeCallbacks.js @@ -0,0 +1,13 @@ +export const executeCallbacks = (callbacks, props, onError) => { + return async (...params) => { + callbacks.reduce(async (data, func, index, arr) => { + const next = await data + const response = await func(...params, props, next) + if (response instanceof Error) { + onError(response) + arr.splice(index) + } + return {...next, ...response} + }, {}) + } +} diff --git a/overrides/app/pages/checkout/partials/payment.jsx b/overrides/app/pages/checkout/partials/payment.jsx index c4bd6a4..ed8ef99 100644 --- a/overrides/app/pages/checkout/partials/payment.jsx +++ b/overrides/app/pages/checkout/partials/payment.jsx @@ -7,7 +7,7 @@ import React, {useState} from 'react' import PropTypes from 'prop-types' import {FormattedMessage, useIntl} from 'react-intl' -import {Box, Button, Checkbox, Container, Heading, Stack, Text, Divider} from '@chakra-ui/react' +import {Box, Checkbox, Divider, Heading, Stack, Text} from '@chakra-ui/react' import {useForm} from 'react-hook-form' import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast' import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' @@ -93,7 +93,7 @@ const Payment = () => { // Using destructuring to remove properties from the object... // eslint-disable-next-line @typescript-eslint/no-unused-vars const {addressId, creationDate, lastModified, preferred, ...address} = billingAddress - return updateBillingAddressForBasket({ + return await updateBillingAddressForBasket({ body: address, parameters: {basketId: basket.basketId, shipmentId: 'me'} }) @@ -146,7 +146,7 @@ const Payment = () => { - + diff --git a/overrides/app/static/translations/compiled/en-US.json b/overrides/app/static/translations/compiled/en-US.json index b51e690..89ef442 100644 --- a/overrides/app/static/translations/compiled/en-US.json +++ b/overrides/app/static/translations/compiled/en-US.json @@ -533,18 +533,6 @@ "value": "Add New Card" } ], - "checkout.button.place_order": [ - { - "type": 0, - "value": "Place Order" - } - ], - "checkout.message.generic_error": [ - { - "type": 0, - "value": "An unexpected error occurred during checkout." - } - ], "checkout_confirmation.button.create_account": [ { "type": 0, @@ -767,12 +755,6 @@ "value": "Back to cart" } ], - "checkout_payment.button.review_order": [ - { - "type": 0, - "value": "Review Order" - } - ], "checkout_payment.heading.billing_address": [ { "type": 0, diff --git a/translations/en-US.json b/translations/en-US.json index 536594a..d75f762 100644 --- a/translations/en-US.json +++ b/translations/en-US.json @@ -201,12 +201,6 @@ "cc_radio_group.button.add_new_card": { "defaultMessage": "Add New Card" }, - "checkout.button.place_order": { - "defaultMessage": "Place Order" - }, - "checkout.message.generic_error": { - "defaultMessage": "An unexpected error occurred during checkout." - }, "checkout_confirmation.button.create_account": { "defaultMessage": "Create Account" }, @@ -295,9 +289,6 @@ "checkout_header.link.cart": { "defaultMessage": "Back to cart" }, - "checkout_payment.button.review_order": { - "defaultMessage": "Review Order" - }, "checkout_payment.heading.billing_address": { "defaultMessage": "Billing Address" },