2

I'm trying to implement the Adyen dropin payment UI using NextJS but I'm having trouble initializing the Adyen dropin component.

I'm need to dynamically import Adyen web or I get the error window is not defined however, after reading through the NextJS docs, dynamic import creates a component which I can't figure out how to use as a constructor.

I tried the code below but receive the error TypeError: AdyenCheckout is not a constructor

I'm new to NextJS and am at a total loss as to how I should import and initialize Adyen.

Can anyone point me in the right direction?

import Head from 'next/head';
import { useRef, useEffect, useState } from 'react';
import {callServer, handleSubmission} from '../util/serverHelpers';
//dynamic import below. Imports as a component 
//import dynamic from 'next/dynamic';
//const AdyenCheckout = dynamic(() => import('@adyen/adyen-web'), {ssr: false});
import '@adyen/adyen-web/dist/adyen.css';

export default function Dropin(){

  const dropinContainer = useRef(null);
  const [paymentMethods, setPaymentMethods] = useState();
  //const [dropinHolder, setDropinHolder] = useState();

  //Get payment methods after page render
  useEffect( async () => {
    const response = await callServer(`${process.env.BASE_URL}/api/getPaymentMethods`);
    setPaymentMethods(prev => prev = response);
  },[]);
   
  //Adyen config object to be passed to AdyenCheckout
  const configuration = {
    paymentMethodsResponse: paymentMethods,
    clientKey: process.env.CLIENT_KEY,
    locale: "en_AU",
    environment: "test",
    paymentMethodsConfiguration: {
      card: {
        showPayButton: true,
        hasHolderName: true,
        holderNameRequired: true,
        name: "Credit or debit card",
        amount: {
          value: 2000,
          currency: "AUD"
        }
      }
    },
    onSubmit: (state, component) => {
      if (state.isValid) {
        handleSubmission(state, component, "/api/initiatePayment");
      }
    },
    onAdditionalDetails: (state, component) => {
      handleSubmission(state, component, "/api/submitAdditionalDetails");
    },
  };

  //const checkout = new AdyenCheckout(configuration);
  const AdyenCheckout = import('@adyen/adyen-web').default;
  const adyenCheckout = new AdyenCheckout(configuration);
  const dropin = adyenCheckout.create('dropin').mount(dropinContainer.current);

  return (
      <div>
        <Head>
        <title>Dropin</title>
        
        </Head>
        <div ref={dropin}></div>
      </div>
  )
}
Darragh
  • 55
  • 7
  • Did you try a plain and simple `import AdyenCheckout from '@adyen/adyen-web';` at the top of the page, above the CSS import? – Blundering Philosopher Mar 01 '21 at 10:40
  • 1
    Yes, I should have mentioned that. When I use the standard import, I just get the `window is not defined` error. – Darragh Mar 01 '21 at 10:49
  • Dynamic `import` returns a promise, you need to `await` for it. I'd also recommend moving it inside the `useEffect` since its code seems to be dependent on `window` being present. – juliomalves Mar 01 '21 at 18:53
  • Also, does this help answer your question when using `next/dynamic`: [Why am I getting ReferenceError: self is not defined in Next.js when I try to import a client-side library](https://stackoverflow.com/questions/66096260/webpack-why-am-i-getting-referenceerror-self-is-not-defined-in-next-js-when-i/66100185#66100185)? – juliomalves Mar 01 '21 at 19:01
  • So I've moved everything inside the `useEffect` and put the import inside an `async` function but still get the `AdyenCheckout is not a contructor` error. @juliomalves I originally thought the dynamic import would solve my problem but it seems that dynamic import returns a loadable component and not the actual module – Darragh Mar 02 '21 at 05:56

1 Answers1

3

I was able to resolve the issue by importing the module using the default value inside an async function nested in the useEffect function.

import Head from 'next/head';
import { useRef, useEffect, useState } from 'react';
import {callServer, handleSubmission} from '../util/serverHelpers'; 
import '@adyen/adyen-web/dist/adyen.css';

export default function Dropin(){

  const dropinContainer = useRef();
  const [paymentMethods, setPaymentMethods] = useState({});

  useEffect(() => {
    const init = async () => {
      const response = await callServer(`${process.env.BASE_URL}/api/getPaymentMethods`)
      .then(setPaymentMethods(response));

      console.log(paymentMethods);

      const configuration = {
        paymentMethodsResponse: paymentMethods,
        clientKey: process.env.CLIENT_KEY,
        locale: "en_AU",
        environment: "test",
        paymentMethodsConfiguration: {
          card: {
            showPayButton: true,
            hasHolderName: true,
            holderNameRequired: true,
            name: "Credit or debit card",
            amount: {
              value: 2000,
              currency: "AUD"
            }
          }
        },
        onSubmit: (state, component) => {
          if (state.isValid) {
            handleSubmission(state, component, "/api/initiatePayment");
          }
        },
        onAdditionalDetails: (state, component) => {
          handleSubmission(state, component, "/api/submitAdditionalDetails");
        },
      };

      console.log(configuration.paymentMethodsResponse);
      const AdyenCheckout = (await import('@adyen/adyen-web')).default;
      const checkout = new AdyenCheckout(configuration);
      checkout.create('dropin').mount(dropinContainer.current);
    }
    init();
  },[]);

  return (
      <div>
        <Head>
        <title>Dropin</title>
        </Head>
        <div ref={dropinContainer}></div>
      </div>
  )
}
Darragh
  • 55
  • 7
  • Interesting, by doing the same there is some improvement on my side and it solves the original issue. However I don't understand why, could you explain why that work? Also in my case (using Vue JS) it looks as if I import twice the library, not sure that's the best way of doing it though, especially considering its size. – Seb Mar 04 '21 at 06:57