Broadleaf Microservices
  • v1.0.0-latest-prod

Adyen Drop-in Web v6 Integration

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.

This guide shows how to add Adyen Web Drop-in 6.34.1 to a checkout page using the Sessions flow with additional methods. If you already integrated with v5 and need to upgrade it to v6 see Adyen Web v6 upgrade guid. To see outdated integration example see Adyen Drop-in Web v5 Integration

The checkout flow has four main parts:

  1. Install and import Adyen Web.

  2. Create a payment session.

  3. Initialize and mount the Drop-in component.

  4. Handle completion, failure, and cart submission.

Prerequisites

To successfully integrate with Adyen Web v6, the next versions of the services are required: broadleaf-adyen 2.0.0, broadleaf-payment-transaction-services 3.0.0 and broadleaf-cart-operation-services 3.0.0. If you are using Payment JS SDK, the @broadleaf/adyen-payment-services-react 1.5.1 should be used.

1. Install Adyen Web

Follow the Adyen guide to install the Drop-in package:

2. Import dependencies

import {
  AdyenCheckout,
  Dropin,
  Card,
  GooglePay,
  ApplePay,
  PayPal,
  Klarna,
} from '@adyen/adyen-web';

import '@adyen/adyen-web/styles/adyen.css';

Import only the payment methods that you plan to support.

3. Create an Adyen payment session

Create the session before mounting Drop-in. The session request should include the payment amount, reference, country, return URL, shopper information, and line items when available.

import { useAdyenCreatePaymentSessionRequest } from '@broadleaf/adyen-payment-services-react';

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

const createSession = async () => {
  const amount = {
    amount: 900,
    currency: 'USD',
  };

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

  const session = await createPaymentSession(request);
  setSession(session);
};

Keep the session creation logic separate from Drop-in initialization. This makes retries, loading states, and error handling easier to manage.

4. Configure Adyen Checkout

Use the returned session to initialize AdyenCheckout.

const checkoutConfiguration = {
  environment: 'test',
  clientKey: AdyenConfig.CLIENT_KEY,
  analytics: {
    enabled: false,
  },
  session: {
    id: session.id,
    sessionData: session.sessionData,
  },
  onSubmit: (state: SubmitData, component: UIElement, actions: SubmitActions) => {
      // handle submit, see step 7
  },
  onPaymentCompleted,
  onPaymentFailed,
};

Use environment: 'live' only after production configuration is complete.

5. Configure payment methods

Define method-specific configuration separately from the main checkout configuration.

const paymentMethodsConfiguration = {
  card: {
    hasHolderName: true,
    holderNameRequired: true,
    billingAddressRequired: true,
  },
  googlepay: {
    amount: adyenAmountInMinorUnits,
    countryCode: countryCode,
    billingAddressRequired: true,
    billingAddressParameters: {
      format: 'FULL' as google.payments.api.BillingAddressFormat,
    },
    onAuthorized: (paymentData, actions) => {
      // store payment data to use it later when submitting
      setPaymentData(paymentData);
      actions.resolve();
    },
  } as GooglePayConfiguration,
  applepay: {
    amount: adyenAmountInMinorUnits,
    requiredBillingContactFields: [
      'name',
      'postalAddress',
    ] as ApplePayJS.ApplePayContactField[],
    onAuthorized: (paymentData, actions) => {
      // store payment data to use it later when submitting
      setPaymentData(paymentData);
      // resolve to continue
      actions.resolve();
    },
  } as ApplePayConfiguration,
  paypal: {
    intent: 'authorize',
    amount: adyenAmountInMinorUnits,
    blockPayPalCreditButton: true,
    blockPayPalPayLaterButton: true,
    blockPayPalVenmoButton: true,
    // The buyer country is only used in the sandbox. Don't pass this query parameter in production.
    countryCode: countryCode,
    onAuthorized: async (paymentData, actions) => {
      const paymentUpdateRequest = {
        paymentId,
        billingAddress: mapBillingAddress(paymentData),
      } as UpdatePaymentRequest;

      try {
        // update payment with billing address
        const updatedPaymentSummary = await handleUpdatePaymentInfo(
          paymentUpdateRequest,
          paymentVersion
        );

        actions.resolve();
      } catch (err) {
        actions.reject();
      }
    },
  },
} as PaymentMethodsConfiguration;

6. Mount Drop-in

Mount Drop-in after the session is available and only once per checkout page lifecycle.

useEffect(() => {
  if (!session || dropinComponent) {
    return;
  }

  const mountDropin = async () => {
    const checkout = await AdyenCheckout(checkoutConfiguration);

    const dropin = new Dropin(checkout, {
      paymentMethodComponents: [Card, GooglePay, ApplePay, PayPal, Klarna],
      paymentMethodsConfiguration,
      showStoredPaymentMethods: true,
      onSelect: (paymentMethod: UIElement) => {
        // Store the current selected payment type to get it when submitting
        const type = paymentMethod.type.toLowerCase();
        if (type === 'googlepay') {
            PaymentTypeHelper.setPaymentType('GOOGLE_PAY');
          } else if (type === 'applepay') {
            PaymentTypeHelper.setPaymentType('APPLE_PAY');
          } else if (type === 'paypal') {
            PaymentTypeHelper.setPaymentType('PAYPAL');
          } else if (type.includes('klarna')) {
            PaymentTypeHelper.setPaymentType('KLARNA');
          } else {
            // default to credit card
            PaymentTypeHelper.setPaymentType('CREDIT_CARD');
          }
      },
    });

    dropin.mount('#adyen-dropin-container');
    setDropinComponent(dropin);
  };

  mountDropin();
}, [session, dropinComponent]);

Render the Drop-in container in your component:

return <div id="adyen-dropin-container" />;

7. Handle submit

When Drop-in calls onSubmit, create the payment, then submit the cart.

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.

const handleSubmitAdyenPayment = async ({
  state,
  actions,
  sessionId,
  paymentData,
}) => {
  // store the actions for later use if needed
  setSubmitActions(actions);

  // get selected payment type
  const paymentType = PaymentTypeHelper.getPaymentType();

  const paymentRequest = {
    name: 'ADYEN',
    type: paymentType,
    gatewayType: 'ADYEN',
    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;

  const paymentSummary = await handleSubmitPaymentInfo(paymentRequest);

  const lineItems = getLineItems(cart);

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

  if (checkoutResponse?.success) {
    // At this point, the payment is successful
    actions.resolve({
      resultCode: 'Authorised',
    });
  }

  return checkoutResponse?.paymentSummaries[0];
};

8. Handle additional actions

If the payment requires additional actions, for example, 3DS interaction, the submit result will contain the error with the nextAction that should be handled by Adyen actions.

For example
    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'
      );

      // get result code
      const resultCode = get(errorDetails, 'nextAction.attributes.resultCode');
      const action = omit(
        get(errorDetails, 'nextAction.attributes'),
        'resultCode'
      );

      if (!resultCode) {
        submitActions.reject();
      } else {
        //
        submitActions.resolve({
          resultCode,
          action,
        });
      }
    } else if (error) {
      submitActions.reject();
    }

9. Handle payment result callbacks

Use callback handlers to handle payment result.

const onPaymentCompleted = async (data: PaymentCompletedData, component?: UIElement) => {
  //Redirect to success page or refresh checkout state
};

const onPaymentFailed = async (data?: PaymentFailedData, component?: UIElement) => {
  // handle failed payment.
};

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

Use this flow when a shopper adds a payment method from an account page, outside of checkout.

1. Create a zero-amount session

For storing a card without an immediate payment, create a session with amount 0 and enable payment method storage.

const request = {
  amount: {
    amount: 0,
    currency: 'USD',
  },
  reference: 'uniquely identify a payment',
  returnUrl: '...',
  countryCode: 'US',
  shopperEmail,
  //hardcode, backend will know what to do with that
  recurringProcessingModel: 'CardOnFile',
  storePaymentMethodMode: 'enabled',
};

const session = await createPaymentSession(request);

2. Mount Drop-in for storing the payment method

const checkout = await AdyenCheckout({
  environment: 'test',
  clientKey: AdyenConfig.CLIENT_KEY,
  analytics: {
    enabled: false,
  },
  session: {
    id: session.id,
    sessionData: session.sessionData,
  },
  onPaymentCompleted: (data: PaymentCompletedData, component?: UIElement) => {
      // handle success payment
  },
  onPaymentFailed: (data?: PaymentFailedData, component?: UIElement) => {
      // handle failed payment
  },
});

const dropin = new Dropin(checkout, {
  showStoredPaymentMethods: false,
  paymentMethodComponents: [Card],
  paymentMethodsConfiguration: {
    card: {
      name: 'Credit or debit card',
      maskSecurityCode: true,
      amount: amount,
      hasHolderName: true,
      holderNameRequired: true,
      billingAddressRequired: true,
    },
  },
});

dropin.mount('#adyen-saved-payment-container');