Broadleaf Microservices
  • v1.0.0-latest-prod

Adyen Drop-in Web Integration

Overview

The server-side supports the Sessions flow with additional methods implementation. This documentation contains an example of the client-side integration using Web Drop-in solution with React js.

Start integrating with Web Drop-in

1. Install Drop-in package

Use the Get Adyen Web guide to install the Drop-in package to your application.

Note
The examples shown in this guide are built using the 5.64.0 version of Adyen’s Drop-in library.

2. Import Drop-in Dependencies

import AdyenCheckout from '@adyen/adyen-web';
import DropinElement from '@adyen/adyen-web/dist/types/components/Dropin';
import UIElement from '@adyen/adyen-web/dist/types/components/UIElement';
import '@adyen/adyen-web/dist/adyen.css';

3. Create the Payment Form

The Adyen payment form creates a payment session using the POST /api/payment/adyen/create-session endpoint. After that, the session id and sessionData are used in the configuration object for the Drop-in component that then is mounted to the <div id="dropin-container"></div> DOM container element. When customer type the payment details and clicks Pay button, the new Payment is created in PaymentTransactionServices and the request to process checkout POST /api/cart-operations/checkout/${cartId}/process is sent.

NOTE: The Drop-in component returns the state object that contains data. This data contains the sensitive information that shouldn’t be stored but is required to execute the payment transaction. The process checkout request has an ability to send such data in the request body. For example:

await submitCart({
            sensitivePaymentMethodData: [
              {
                paymentId: paymentSummary.paymentId,
                paymentMethodProperties: {
                  ADYEN_PAYMENT_DATA: {
                      // data object returned by Drop-in component
                    ...data,
                    // customer email address
                    shopperEmail
                  },
                },
              },
            ],
          });

Adyen Payment Form Example

NOTE: The following example redirects the customer to the order confirmation page after receiving a successful 3DS authentication result. See Frontend Changes for details on how it can be implemented.

import { FC, useState, useEffect } from 'react';
import { isNil, join } from 'lodash';

import AdyenCheckout from '@adyen/adyen-web';
import DropinElement from '@adyen/adyen-web/dist/types/components/Dropin';
import {
  ShopperDetails,
  PaymentMethodOptions,
} from '@adyen/adyen-web/dist/types/types';
import UIElement from '@adyen/adyen-web/dist/types/components/UIElement';
import '@adyen/adyen-web/dist/adyen.css';

import {
  DefaultPaymentType,
  PaymentMethodOptionResponse,
  UpdatePaymentRequest,
} from '@broadleaf/commerce-cart';

import { useFormatMessage, usePaymentAuthState } from '@app/common/hooks';

import { AdyenConfig } from '@app/common/utils';

import {
  useAdyenPaymentServicesClient,
  usePaymentClient,
} from '@app/common/contexts';
import { useAdyenCreatePaymentSessionRequest } from '@broadleaf/adyen-payment-services-react';
import {
  CreatePaymentSessionRequest,
  CreatePaymentSessionResponse,
} from '@broadleaf/adyen-payment-services-api';

import {
  useFormatAmountInMinorUnits,
  useGetPaymentAmount,
  useGetPaymentCallbackUrl,
  useHandleMoneyAddition,
} from '@app/checkout/hooks';
import { useCartContext } from '@app/cart/contexts';

import {
  useHandleOnPaymentCompleted,
  useHandleSubmitAdyenPayment,
} from './adyen-hooks';

import { useSubmitPaymentRequest } from '@broadleaf/payment-react';
import { Address } from '@broadleaf/commerce-customer';

import messages from '@app/checkout/messages';

type Props = {
  paymentOptions: Array<PaymentMethodOptionResponse>;
};

type AdyenPaymentFormType = FC<Props> & {
  TYPE: 'ADYEN';
};

export const AdyenPaymentForm: AdyenPaymentFormType = () => {
  const formatMessage = useFormatMessage();

  const { cart } = useCartContext();

  const authState = usePaymentAuthState();

  const adyenPaymentServicesClient = useAdyenPaymentServicesClient();

  const { createPaymentSession, error: createPaymentSessionError } =
    useAdyenCreatePaymentSessionRequest({
      adyenPaymentServicesClient,
      authState,
    });

  const [session, setSession] = useState<CreatePaymentSessionResponse>();
  const [dropinComponent, setDropinComponent] = useState<DropinElement>();

  const paymentClient = usePaymentClient();
  const { handleUpdatePaymentInfo } = useSubmitPaymentRequest({
    authState,
    payments: undefined,
    ownerId: cart?.id,
    owningUserEmailAddress: cart.emailAddress,
    paymentClient,
    multiplePaymentsAllowed: false,
    rejectOnError: true,
  });

  const getPaymentAmount = useGetPaymentAmount({
    gatewayType: AdyenPaymentForm.TYPE,
  });

  const getPaymentCallbackUrl = useGetPaymentCallbackUrl();

  const [sessionIsCreating, setSessionIsCreating] = useState<boolean>(false);

  const formatAmountInMinorUnits = useFormatAmountInMinorUnits();
  const handleMoneyAddition = useHandleMoneyAddition();

  useEffect(() => {
    if (!session && cart && !sessionIsCreating) {
      setSessionIsCreating(true);
      const amount = getPaymentAmount();
      const returnUrl = getPaymentCallbackUrl({
        gatewayType: AdyenPaymentForm.TYPE,
      });

      const countryCode = cart.fulfillmentGroups[0]?.address?.country;
      const request = {
        amount,
        reference: cart.id,
        returnUrl,
        countryCode,
        //hardcode, backend will know what to do with that when customer logged in
        recurringProcessingModel: 'CardOnFile',
        storePaymentMethodMode: 'askForConsent',
        shopperEmail: authState.isAuthenticated
          ? authState.customerEmail
          : cart.emailAddress,
      } as CreatePaymentSessionRequest;

      createPaymentSession(request)
        .then(response => setSession(response))
        .finally(() => setSessionIsCreating(false));
    }
  }, [
    authState,
    cart,
    session,
    setSession,
    createPaymentSession,
    getPaymentAmount,
    getPaymentCallbackUrl,
    sessionIsCreating,
    setSessionIsCreating,
    formatAmountInMinorUnits,
    handleMoneyAddition,
  ]);

  const [error, setError] = useState();

  const handleOnSubmit = useHandleSubmitAdyenPayment({ setError });

  const handleOnPaymentCompleted = useHandleOnPaymentCompleted({
    setUIElement: setDropinComponent,
  });

  const [paymentData, setPaymentData] = useState();
  const [submitState, setSubmitState] = useState();

  const countryCode = cart?.fulfillmentGroups[0]?.address?.country || 'US';

  useEffect(() => {
    if (session && !dropinComponent && !sessionIsCreating) {
      const amount = getPaymentAmount();

      const adyenAmountInMinorUnits = {
        value: formatAmountInMinorUnits(amount),
        currency: amount.currency,
      };

      const configuration = {
        environment: 'test',
        clientKey: AdyenConfig.CLIENT_KEY,
        analytics: {
          enabled: false,
        },
        session: {
          id: session.id, // Unique identifier for the payment session.
          sessionData: session.sessionData, // The payment session data.
        },
        paymentMethodsConfiguration: {
          card: {
            hasHolderName: true,
            holderNameRequired: true,
            billingAddressRequired: true,
          },
        },
        onSubmit: (state, dropin) => {
          const paymentType = PaymentTypeHelper.getPaymentType();

          handleOnSubmit({
            state,
            paymentType: PaymentTypeHelper.getPaymentType(),
            component: dropin,
            sessionId: session.id,
          }).then(paymentSummary => {
            paymentSummaryStore.setValue(paymentSummary);
          });
        },
        onPaymentCompleted: handleOnPaymentCompleted,
        onError: (error, component) => {
          console.error(error.name, error.message, error.stack);

          setError(error);
        },
      };

      AdyenCheckout(configuration).then(checkout => {
        const dComponent = checkout
          .create('dropin', {
            showStoredPaymentMethods: true,
            onSelect: (dropin: UIElement) => {
              if (dropin.type.toLowerCase() === 'card') {
                PaymentTypeHelper.setPaymentType(
                  DefaultPaymentType.CREDIT_CARD
                );
              }
            },
          })
          .mount('#dropin-container');

        setDropinComponent(dComponent);
      });
    }

    return () => {
      if (dropinComponent) {
        dropinComponent.unmount();
        setDropinComponent(undefined);
      }
    };
    // eslint-disable-next-line
  }, [
    session,
    countryCode,
    dropinComponent,
    setDropinComponent,
    sessionIsCreating,
    handleOnSubmit,
    handleOnPaymentCompleted,
    getPaymentAmount,
    formatAmountInMinorUnits,
    PaymentTypeHelper,
  ]);

  return (
    <div>
      <h2 className="py-4 text-xl font-medium">
        {formatMessage(messages.paymentMethod)}
      </h2>
      <div id="dropin-container"></div>
      {(createPaymentSessionError || error) && (
        <strong className="block my-4 text-red-600 text-lg font-normal">
          {formatMessage(messages.genericError)}
        </strong>
      )}
    </div>
  );
};

const createPaymentSummaryStore = () => {
  let value = null;
  return {
    setValue(newValue: unknown) {
      value = newValue;
    },
    getValue() {
      return value;
    },
  };
};
const paymentSummaryStore = createPaymentSummaryStore();

const createPaymentTypeHolder = () => {
  let paymentType = 'CREDIT_CARD';

  return {
    getPaymentType() {
      return paymentType;
    },
    setPaymentType(type: string) {
      paymentType = type;
    },
  };
};
const PaymentTypeHelper = createPaymentTypeHolder();

AdyenPaymentForm.TYPE = 'ADYEN';
Common Hooks
export const useHandleSubmitAdyenPayment = (
  props: HandleSubmitProps = {}
): HandleSubmitResponse => {
  const { setError = noop } = props;
  const cartState = useCartContext();
  const { cart } = cartState;
  const paymentClient = usePaymentClient();
  const authState = usePaymentAuthState();
  const { handleSubmitPaymentInfo } = useSubmitPaymentRequest({
    authState,
    payments: undefined,
    ownerId: cart?.id,
    owningUserEmailAddress: cart.emailAddress,
    paymentClient,
    multiplePaymentsAllowed: false,
    rejectOnError: true,
  });

  const getPaymentAmount = useGetPaymentAmount({
    gatewayType: AdyenPaymentForm.TYPE,
  });

  const getPaymentCallbackUrl = useGetPaymentCallbackUrl();

  const { error, onSubmit: submitCart } = useHandleSubmitCart();

  const [adyenComponent, setAdyenComponent] = useState<UIElement>();

  useEffect(() => {
    if (!adyenComponent) {
      return;
    }

    const errorType = get(error, 'failureType');
    if (
      errorType === 'PAYMENT_REQUIRES_3DS_VERIFICATION' ||
      errorType === 'PAYMENT_REQUIRES_EXTERNAL_INTERACTION' ||
      errorType === 'PAYMENT_REQUIRES_HOSTED_PAYMENT_PAGE_INTERACTION'
    ) {
      const errorDetails = find(
        get(error, 'paymentTransactionFailureDetails'),
        ({ failureType }) =>
          failureType === 'REQUIRES_3DS_VERIFICATION' ||
          failureType === 'REQUIRES_EXTERNAL_INTERACTION' ||
          failureType === 'REQUIRES_HOSTED_PAYMENT_PAGE_INTERACTION'
      );

      const action = get(errorDetails, 'nextAction.attributes');

      adyenComponent.handleAction(action);
    } else if (errorType && error) {
      setError(error);
    }
  }, [error, adyenComponent, setError]);

  const getLineItems = useGetLineItems();

  return useEventCallback(
    async ({
      state,
      paymentType,
      component,
      sessionId,
      paymentData,
    }: HandleSubmitParameters) => {
      setError(undefined);
      const data = get(state, 'data');

      setAdyenComponent(component);

      const billingAddress = getBillingAddress(state, paymentType, paymentData);

      // these are required to pass to the backend as part of the payment
      const returnUrl = getPaymentCallbackUrl({
        gatewayType: AdyenPaymentForm.TYPE,
      });

      const amount = getPaymentAmount(cart);
      const paymentRequest = {
        name: AdyenPaymentForm.TYPE,
        type: paymentType,
        gatewayType: AdyenPaymentForm.TYPE,
        amount,
        subtotal: cart.cartPricing.subtotal,
        adjustmentsTotal: cart.cartPricing.adjustmentsTotal,
        fulfillmentTotal: cart.cartPricing.fulfillmentTotal,
        taxTotal: cart.cartPricing.totalTax,
        isSingleUsePaymentMethod: true,
        shouldArchiveExistingPayments: true,
        paymentMethodProperties: {
          sessionId,
          returnUrl,
        },
        billingAddress,
      } as PaymentRequest;

      let paymentSummary;

      try {
        paymentSummary = await handleSubmitPaymentInfo(paymentRequest);

        if (paymentSummary) {
          pushGtmAddPayment(cart, paymentSummary);

          const countryCode = cart.fulfillmentGroups[0]?.address?.country;
          const lineItems = getLineItems(cart);

          const checkoutResponse = await submitCart({
            sensitivePaymentMethodData: [
              {
                paymentId: paymentSummary.paymentId,
                paymentMethodProperties: {
                  ADYEN_PAYMENT_DATA: {
                    ...data,
                    shopperEmail: authState.isAuthenticated
                      ? authState.customerEmail
                      : cart.emailAddress,
                    lineItems,
                    countryCode: countryCode,
                  },
                },
              },
            ],
          });

          return checkoutResponse?.paymentSummaries[0];
        }
      } catch (err) {
        console.error('There was an error adding payment information', err);
        setError(err);
      }
    },
    []
  );
};

export const useHandleOnPaymentCompleted = (
  props: PaymentCompletedProps = {}
) => {
  const { setUIElement } = props;

  const router = useRouter();
  const { cart } = useCartContext();

  const authState = usePaymentAuthState();

  return useEventCallback(async (result, element: UIElement) => {
    const resultCode = get(result, 'resultCode');
    if ('Authorised' === resultCode) {
      const emailAddress = authState.isAuthenticated
        ? undefined
        : cart.emailAddress;
      await router.push({
        pathname: '/checkout/payment-confirmation',
        query: {
          cart_id: cart.id,
          email_address: emailAddress,
          payment_finalization_status: 'FINALIZED',
          payment_result_status: 'SUCCESS',
          gateway_type: AdyenPaymentForm.TYPE,
        },
      });
    } else {
      element.unmount();
      setUIElement && setUIElement(undefined);
      await router.push({
        pathname: '/checkout/payment',
        query: {
          payment_finalization_status: 'REQUIRES_PAYMENT_MODIFICATION',
          payment_result_status:
            'Cancelled' === resultCode ? 'PAYMENT_CANCELED' : 'PAYMENT_FAILED',
          gateway_type: AdyenPaymentForm.TYPE,
        },
      });
    }
  }, []);
};
Using a Custom Form for the Billing Address (Optional)

The form above uses the Adyen’s form for the billing address. If you need to use your own form, make the next changes:

const AdyenPaymentForm: AdyenPaymentFormType = () => {
    ...

  const [usingStoredPaymentMethod, setUsingStoredPaymentMethod] =
    useState<boolean>(false);
  const [isSubmitting, setIsSubmitting] = useState<boolean>(false);

  // the Ref is used to get the Formik form values
  const formikBillingAddressRef = useRef();

  useEffect(() => {
    if (session && !dropinComponent && !sessionIsCreating) {
      const configuration = {
        ...
        paymentMethodsConfiguration: {
          card: {
            hasHolderName: true,
            holderNameRequired: true,
          },
        },
        onSubmit: (state, dropin) => {
          setIsSubmitting(true);

          // get the billing address from the Formik
          const adyenBillingAddress = get(formikBillingAddressRef, 'current.values');
          handleOnSubmit(
            state,
            dropin,
            session.id,
            paymentType,
            adyenBillingAddress
          ).finally(() => {
            setIsSubmitting(false);
          });
        },
        ...
      };

      AdyenCheckout(configuration).then(checkout => {
        const dComponent = checkout
          .create('dropin', {
            showStoredPaymentMethods: true,
            onSelect: (dropin: UIElement) => {
              ...

              const storedPaymentMethodId = get(
                dropin.data,
                'paymentMethod.storedPaymentMethodId'
              );

              // determine if the stored payment method is selected
              setUsingStoredPaymentMethod(!!storedPaymentMethodId);
            },
          })
          .mount('#dropin-container');

        setDropinComponent(dComponent);
      });
    }

    ...
  }, [
    session,
    dropinComponent,
    setDropinComponent,
    sessionIsCreating,
    handleOnSubmit,
    handleOnPaymentCompleted,
    setIsSubmitting,
    formikBillingAddressRef,
    setUsingStoredPaymentMethod,
    paymentType,
    setPaymentType,
  ]);

  return (
    <div>
      ...
      {/* Add the Billing address form. When the saved payment method is used, the billing address is not required */}
      {!usingStoredPaymentMethod && (
        <BillingAddressForm
          innerRef={formikBillingAddressRef}
          isSubmitting={isSubmitting}
        />
      )}
      ...
    </div>
  );
};

const BillingAddressForm = ({ innerRef, isSubmitting }) => {

  return (
    <Formik
      innerRef={innerRef}
      initialValues={...}
      validateOnBlur
      validateOnChange={false}
      onSubmit={() => {
        //do nothing
      }}
    >
      {() => (
        <div>
          {/* Billing address form. */}
        </div>
      )}
    </Formik>
  );
};

type AddressWithStreetAndHouseNumber = {
    // Adyen billing address format
};

const useHandleOnSubmit = ({ setError }) => {
  ...
  return useEventCallback(
    async (
      state,
      dropin: DropinElement,
      sessionId: string,
      paymentType: string,
      adyenBillingAddress?: AddressWithStreetAndHouseNumber
    ) => {
      ...

      let blcBillingAddress: Address;

      if (!isEmpty(adyenBillingAddress)) {
        // convert "adyenBillingAddress" to the Address
      }

      const paymentRequest = {
        ...,
        billingAddress: blcBillingAddress,
      } as PaymentRequest;

      let paymentSummary;

      try {
        paymentSummary = await handleSubmitPaymentInfo(paymentRequest);

        if (paymentSummary) {
          await submitCart({
            sensitivePaymentMethodData: [
              {
                paymentId: paymentSummary.paymentId,
                paymentMethodProperties: {
                  ADYEN_PAYMENT_DATA: {
                    ...data,
                    billingAddress: adyenBillingAddress,
                    shopperEmail: authState.isAuthenticated
                      ? authState.customerEmail
                      : cart.emailAddress,
                  },
                },
              },
            ],
          });
        }
      } catch (err) {
        ...
      }
    },
    []
  );
};

Store a Payment Method for Future Use in Checkout

It is possible to save the payment method for future use during checkout or use a separate page where the authenticated customer can create/delete the payment methods.

We do not store the payment methods for Adyen as it has an ability to do so. We only have 2 API endpoints to read the payment methods - GET /api/payment/adyen/saved-payment-methods, and remove it - DELETE /api/payment/adyen/saved-payment-methods/{savedPaymentMethodId}.

You can use the SDK hooks for these endpoints. For example:

import { useAdyenPaymentServicesClient } from '@app/common/contexts';
import {
  useAdyenListSavedPaymentMethods,
  useAdyenDeleteSavedPaymentMethod,
} from '@broadleaf/adyen-payment-services-react';

...

  const authState = usePaymentAuthState();
  const adyenPaymentServicesClient = useAdyenPaymentServicesClient();

// list the saved payment methods
  const { adyenSavedPaymentMethods, listSavedPaymentMethods } =
    useAdyenListSavedPaymentMethods({
      adyenPaymentServicesClient,
      authState,
    });

// delete the saved payment method
  const { adyenDeleteSavedPaymentMethod } = useAdyenDeleteSavedPaymentMethod({
    adyenPaymentServicesClient,
    authState,
  });

Store a Payment Method for Future use in My Account

To store the new payment method (e.g. in a My Account context) we recommend using the Drop-in component with Sessions flow.

import { FC, useEffect, useState } from 'react';
import { useRouter } from 'next/router';
const { v4 } = require('uuid');

import { usePaymentAuthState } from '@app/common/hooks';

import {
  CreatePaymentSessionRequest,
  CreatePaymentSessionResponse,
} from '@broadleaf/adyen-payment-services-api';
import { useAdyenCreatePaymentSessionRequest } from '@broadleaf/adyen-payment-services-react';
import AdyenCheckout from '@adyen/adyen-web';
import DropinElement from '@adyen/adyen-web/dist/types/components/Dropin';
import '@adyen/adyen-web/dist/adyen.css';
import {
  useAdyenPaymentServicesClient,
  useCurrencyContext,
  useLocationContext,
} from '@app/common/contexts';

import { OnPaymentCompletedData } from '@adyen/adyen-web/dist/types/components/types';

export const AdyenSavedPaymentForm: FC = () => {
  const { replace } = useRouter();
  const { push } = useRouter();
  const authState = usePaymentAuthState();

  const {
    session,
    sessionIsCreating,
    error: createPaymentSessionError,
  } = useCreateSession();

  const [dropinComponent, setDropinComponent] = useState<DropinElement>();

  const [error, setError] = useState<boolean>(false);

  const { currentCurrency } = useCurrencyContext();

  useEffect(() => {
    if (
      authState.isAuthenticated &&
      session &&
      !dropinComponent &&
      !sessionIsCreating
    ) {
        // zero amount authorization
      const amount = {
        value: 0.0,
        currency: currentCurrency || 'USD',
      };
      const configuration = {
        setStatusAutomatically: true,
        environment: 'test',
        clientKey: '{Client Key}',
        analytics: {
          enabled: false,
        },
        session: {
          id: session.id, // Unique identifier for the payment session.
          sessionData: session.sessionData, // The payment session data.
        },
        paymentMethodsConfiguration: {
          card: {
            name: 'Credit or debit card',
            maskSecurityCode: true,
            amount: amount,
            hasHolderName: true,
            holderNameRequired: true,
            billingAddressRequired: true,
          },
        },
        onPaymentCompleted: (data: OnPaymentCompletedData) => {
          const resultCode = data.resultCode;

          if (
            resultCode === 'Authorised' ||
            resultCode === 'Received' ||
            resultCode === 'Pending'
          ) {
            push('/my-account/payments');
          } else {
            setError(true);
          }
        },
        onError: (err, component) => {
          console.error(err.name, err.message, err.stack, component);
          setError(true);
        },
      };

      AdyenCheckout(configuration).then(checkout => {
        const dComponent = checkout
          .create('dropin', { showStoredPaymentMethods: false })
          .mount('#dropin-container');
        setDropinComponent(dComponent);
      });
    }

    return () => {
      if (dropinComponent) {
        dropinComponent.unmount();
        setDropinComponent(undefined);
      }
    };
  }, [
    session,
    dropinComponent,
    setDropinComponent,
    sessionIsCreating,
    push,
    authState.isAuthenticated,
    currentCurrency,
  ]);

  if (!authState.isAuthenticated) {
    replace('my-account/sign-in');
    return null;
  }

  return (
    <div className="container flex flex-col mx-auto px-4 py-8 lg:flex-row xl:px-0">
      <div className="flex-1 px-4 lg:px-0">
        <h2 className="text-4xl font-bold">Add new Payment Method</h2>
        <div id="dropin-container"></div>
        {(createPaymentSessionError || error) && (
          <strong className="block my-4 text-red-600 text-lg font-normal">
            An unexpected error occurred. Please try again later.
          </strong>
        )}
      </div>
    </div>
  );
};

const useCreateSession = () => {
  const authState = usePaymentAuthState();

  const [session, setSession] = useState<CreatePaymentSessionResponse>();
  const [sessionIsCreating, setSessionIsCreating] = useState<boolean>(false);

  const { protocol, host } = useLocationContext();

  const returnUrl = `${protocol}://${host}/my-account/payments`;

  const { currentCurrency } = useCurrencyContext();

  const adyenPaymentServicesClient = useAdyenPaymentServicesClient();

  const { createPaymentSession, error } = useAdyenCreatePaymentSessionRequest({
    adyenPaymentServicesClient,
    authState,
  });

  useEffect(() => {
    if (authState.isAuthenticated && !session && !sessionIsCreating) {
      setSessionIsCreating(true);
      const amount = {
        amount: 0,
        currency: currentCurrency || 'USD',
      };

      const countryCode = 'US';

      const request = {
        amount: amount,
        reference: v4(),
        returnUrl,
        countryCode,
        shopperEmail: authState.customerEmail,
        //hardcode, backend will know what to do with that
        recurringProcessingModel: 'CardOnFile',
        storePaymentMethodMode: 'enabled',
      } as CreatePaymentSessionRequest;

      createPaymentSession(request)
        .then(response => setSession(response))
        .finally(() => setSessionIsCreating(false));
    }
  }, [
    authState,
    session,
    setSession,
    createPaymentSession,
    sessionIsCreating,
    setSessionIsCreating,
    returnUrl,
    currentCurrency,
  ]);

  return {
    session,
    sessionIsCreating,
    error,
  };
};
Using a Custom Form for the Billing Address (Optional)

The form above uses the Adyen’s form for the billing address. If you need to use your own form, make the next changes:

export const AdyenSavedPaymentForm: FC<Props> = props => {
 ...

  useEffect(() => {
    if (
      authState.isAuthenticated &&
      session &&
      !dropinComponent &&
      !sessionIsCreating
    ) {
      const amount = {
        value: 0.0,
        currency: currentCurrency || 'USD',
      };
      const configuration = {
        ...
        paymentMethodsConfiguration: {
          card: {
            name: 'Credit or debit card',
            maskSecurityCode: true,
            amount: amount,
            hasHolderName: true,
            holderNameRequired: true,
          },
        },
        beforeSubmit: (data, dropin, actions) => {
          // add billing address from your custom form
          return actions.resolve({
            ...data,
            billingAddress: {
              street: 'Some Street',
              houseNumberOrName: '3',
              postalCode: 'A9A9A9',
              city: 'City',
              stateOrProvince: 'NY',
            },
          });
        },
        ...
      };

      ...
  }, [
    session,
    dropinComponent,
    setDropinComponent,
    sessionIsCreating,
    push,
    authState.isAuthenticated,
    currentCurrency,
  ]);

  ...
};