Broadleaf Microservices
  • v1.0.0-latest-prod

Upgrade to Adyen Web v6 from v5

Step 1: Use the new package

yarn add @adyen/adyen-web@6.34.1 --exact

Step 2: Upgrade import statements

From Drop-in v6, it is possible to import individual payment methods or all at once. We use importing of the individual payment methods in our example.

For example

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

For the styles use import '@adyen/adyen-web/styles/adyen.css';

Step 3: Upgrade creating an instance

From v6 onwards, creating an instance of Drop-in is as follows:

const checkout = await AdyenCheckout({
  // configuration
});

const dropin = new Dropin(checkout, {
  paymentMethodComponents: [Card, PayPal, GooglePay], // payment method classes that are going to be used as part of the Drop-in
  paymentMethodsConfiguration: { /** Configurations **/ } // configure each payment method displayed on the Drop-in
}).mount('#dropin-container')
Note
paymentMethodsConfiguration is moved to the Drop-in configuration

Step 4: Upgrade your event handlers

onSubmit and onAdditionalDetails

These events handlers now have access to the new actions parameter, which lets you continue the payment flow actions.resolve() or stop it actions.reject().

For example:
  const checkout = await AdyenCheckout({
  // Insert your global configuration here
  onSubmit: async (state: SubmitData, component: UIElement, actions: SubmitActions) => {
    try {
      // create payment
      const paymentSummary = await handleSubmitPaymentInfo(paymentRequest);
      // submit the cart
      const checkoutResponse = await submitCart({
            sensitivePaymentMethodData: [
              {
                paymentId: paymentSummary.paymentId,
                paymentMethodProperties: {
                  ADYEN_PAYMENT_DATA: {
                    ...state.data,
                    shopperEmail: authState.isAuthenticated
                      ? authState.customerEmail
                      : cart.emailAddress,
                    lineItems,
                    countryCode: countryCode,
                  },
                },
              },
            ],
          });

      if (checkoutResponse?.success) {
          // At this point, the payment is successful
          actions.resolve({
            resultCode: 'Authorised',
          });
      }
    } catch (error) {
      actions.reject();
    }
  },
});
Error handling example:
const [submitActions, setSubmitActions] = useState<SubmitActions>();

  useEffect(() => {
    if (!submitActions) {
      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'
      );

      // get result code from the action
      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 (errorType && error) {
      setError(error);
      submitActions.reject();
    }
  }, [error, submitActions, setError]);

onPaymentCompleted

This event now works both in the Sessions Flow and Advanced Flow.

This event is no longer triggered for failed payments. For failed payments, we now use the onPaymentFailed event handler instead. Once this event is triggered, initiate the successful transaction flow on your web page.

onPaymentFailed

This is a new event, which is triggered when a payment fails. Previously, this event was part of onPaymentCompleted.

This event is triggered when the resultCode of a payment is Cancelled, Error, or Refused.

Once this event is triggered, initiate the failed transaction flow on your web page.

Example
const onPaymentFailed = async (data?: PaymentFailedData, component?: UIElement) => {
  const resultCode = data?.resultCode;
  component?.unmount();

  // redirect to
  await router.push({
    pathname: '/checkout/payment',
    query: {
      payment_finalization_status: 'REQUIRES_PAYMENT_MODIFICATION',
      payment_result_status:
        'Cancelled' === resultCode ? 'PAYMENT_CANCELED' : 'PAYMENT_FAILED',
      gateway_type:  'ADYEN',
    },
  });
}

Step 5: (Optional) upgrade your digital wallet and express payment methods

Create AdyenCheckout and initialize express checkout

To align with the latest changes, a new generic hook useAdyenGetAvailablePaymentMethods was added to read the available payment methods. This new hook allows you to specify the generic response type from Adyen.

Example
import {
  AdyenCheckout,
  PaymentMethodsResponse,
  GooglePay,
  ApplePay,
  PayPal
} from '@adyen/adyen-web';

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

  const {
    getAvailablePaymentMethods,
  } = useAdyenGetAvailablePaymentMethods<PaymentMethodsResponse>({
    authState,
    adyenPaymentServicesClient,
  });

  ...
  const shopperReference = authState.isAuthenticated
    ? authState.customerId
    : undefined;

  const paymentMethodsResponse = await getAvailablePaymentMethods({
    amount,
    shopperLocale: locale,
    countryCode: 'US',
    shopperReference,
  });

  const checkout = await AdyenCheckout({
    environment: 'test',
    ...
    paymentMethodsResponse,
  });

  // Add this ref to the component where the express checkout button will be rendered.
  const container = useRef(null);

  // get the payment type to mount
  const type = 'applepay'; // googlepay or paypal

  // get configuration for the payment type
  const configuration = {};

  if (checkout.paymentMethodsResponse?.has(type)) {
      if (type === 'googlepay' || type === 'applepay') {
        const expressCheckoutComponent =
          type === 'googlepay'
            ? new GooglePay(checkout, configuration)
            : new ApplePay(checkout, configuration);

        expressCheckoutComponent
          .isAvailable()
          .then(() => {
            expressCheckoutComponent.mount(container.current);
          })
          .catch(() => {
            // handle error
          });
      } else if (type === 'paypal') {
        new PayPal(checkout, configuration).mount(container.current);
      }
    }

Apple Pay

The event handlers onShippingContactSelected() and onShippingMethodSelected() now work only when the Apple Pay Component configuration isExpress is set to true

onAuthorized

This event is now triggered before the onSubmit event, and must be resolved or rejected to continue the payment flow. onAuthorized returns an object with the authorizedEvent (raw data from Apple Pay) along with the formatted billingAddress and deliveryAddress.

Example
import {
  AddressData,
  ApplePay,
} from '@adyen/adyen-web';
const applepay = new ApplePay(checkout, {
  onAuthorized: (paymentData: {
        authorizedEvent: ApplePayJS.ApplePayPaymentAuthorizedEvent;
        billingAddress?: Partial<AddressData>;
        deliveryAddress?: Partial<AddressData>;
    }, actions: {
        resolve: () => void;
        reject: (error?: ApplePayJS.ApplePayError) => void;
    }) => {
   setPaymentData(paymentData);
   actions.resolve();
  },
});

Google Pay

The event handler onPaymentDataChanged() now works only when the Google Pay Component configuration isExpress is set to true.

onAuthorized

This event is now triggered before the onSubmit event, and must be resolved or rejected to continue the payment flow. onAuthorized returns an object with the authorizedEvent (raw data from Google Pay) along with the formatted billingAddress and deliveryAddress.

Example
import {
  AddressData,
  GooglePay,
} from '@adyen/adyen-web';
const googlepay = new GooglePay(checkout, {
  onAuthorized: (paymentData: {
        authorizedEvent: google.payments.api.PaymentData;
        billingAddress?: Partial<AddressData>;
        deliveryAddress?: Partial<AddressData>;
    }, actions: {
        resolve: () => void;
        reject: (error?: google.payments.api.PaymentDataError | string) => void;
    }) => {
   setPaymentData(paymentData);
   actions.resolve();
  },
});

PayPal

The onShopperDetails(shopperDetails, rawData, actions) event handler is renamed to onAuthorized({authorizedEvent, billingAddress, deliveryAddress}, actions). The `onShippingChange() event handler is no longer supported. Implement onShippingAddressChange() and onShippingOptionsChange() instead

Example
import {
  AddressData,
  PayPal,
} from '@adyen/adyen-web';
import { OrderResponseBody as PayPalOrderResponseBody } from '@paypal/paypal-js';

const payPal = new PayPal(checkout, {
  onAuthorized: (paymentData: {
        authorizedEvent: PayPalOrderResponseBody;
        billingAddress?: Partial<AddressData>;
        deliveryAddress?: Partial<AddressData>;
    }, actions: {
        resolve: () => void;
        reject: () => void;
    }) => {
   setPaymentData(paymentData);
   actions.resolve();
  },
});

Express Checkout Update Payment

In the express checkout flow, when the PayPal button is clicked, we create a payment for a specific amount. When the shipping address/option changes, the Cart can be repriced, and the new price is reallocated to the payment. Due to this, the payment version can be changed and when you update the payment you need to use the latest payment version. In CartOperationServices, we added the CartAlert in the response to notify the caller about the updated payment. See CartOperationServices 3.0.0 Release Notes for information about this change.

Example of using this:
type PaymentSummaryStoreValueType = {
  paymentId: string;
  version: number;
};

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

const useHandleShippingAddressChange = ({ setErrorMsg }) => {
  const { cart } = useCartContext();
  const updateFulfillmentAddress = useUpdateFulfillmentAddress({
    buildFulfillmentAddress,
  });

  const buildShippingOptionParameters = useBuildShippingOptionParameters();

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

  const authState = usePaymentAuthState();
  const paymentClient = usePaymentClient();

  const { handleUpdatePaymentInfo } = useSubmitPaymentRequest({
    authState,
    // set "undefined" to prevent updating existing payments
    payments: undefined,
    ownerId: cart?.id,
    owningUserEmailAddress: cart?.emailAddress,
    paymentClient,
    multiplePaymentsAllowed: false,
    rejectOnError: true,
  });

  return useEventCallback(async (data, actions, component: PayPal) => {
    const { fulfillmentOptions, cart } = await updateFulfillmentAddress(
      get(data, 'shippingAddress')
    );
    const shippingOptions = buildShippingOptionParameters(
      fulfillmentOptions,
      cart
    );

    const paymentAmount = getPaymentAmount(cart);

    const { paymentId, version } = paymentSummaryStore.getValue();

    const paymentUpdateRequest = {
      paymentId,
      amount: paymentAmount,
      taxTotal: cart.cartPricing.totalTax,
      includedTaxTotal: cart.cartPricing.includedTaxAmount,
      gatewayUpdateParams: {
        paymentData: component.paymentData,
        deliveryMethods: shippingOptions,
      },
    } as UpdatePaymentRequest;

    try {
      const paymentVersionToUpdate = getLatestPaymentVersionByPaymentId(
        paymentId,
        version,
        cart.cartAlerts
      );

      const updatedPaymentSummary = await handleUpdatePaymentInfo(
        paymentUpdateRequest,
        paymentVersionToUpdate
      );

      paymentSummaryStore.setValue({
        paymentId: updatedPaymentSummary.paymentId,
        version: updatedPaymentSummary.version,
      });

      component.updatePaymentData(
        get(updatedPaymentSummary.gatewayUpdateResponse, 'paymentData')
      );
    } catch (err) {
      console.error('EXCEPTION FOR PAYMENT UPDATE:', err);
      component.setStatus('error');
      component.unmount();
    }
  }, []);
};

const getLatestPaymentVersionByPaymentId = (
  paymentId: string,
  currentVersion: number,
  alerts?: CartAlert[]
): number => {
  if (isEmpty(alerts)) {
    return currentVersion;
  }

  let latestVersion = currentVersion;

  for (const alert of alerts) {
    const updates = alert.additionalAttributes?.['UPDATED_PAYMENTS'];

    if (alert.type !== 'PRICING_CHANGE' || isEmpty(updates)) {
      continue;
    }

    const updatedPayment = find(updates, update => {
      return update.paymentId === paymentId;
    });

    if (updatedPayment?.version > latestVersion) {
      latestVersion = updatedPayment.version;
    }
  }

  return latestVersion;
};