Search

Are you looking for test card numbers?

Would you like to contact support?

Online-payment icon

Drop-in tutorial: Node.js + Express

Build a complete integration for accepting payments on your website.

  Check out the full project in our Drop-in and Node.js example repository on GitHub.

This tutorial explains the server- and client-side steps for integrating Drop-in, our complete UI solution for accepting payments on your website. Integrating Drop-in is the quickest way to offer multiple payment methods, without having to build each payment method individually.

After completing this tutorial, you'll have a base integration that supports all payment flows, and can be quickly extended to support most payment methods.

To learn more about our available integration options, refer to Online payments.

How it works

When your shopper visits your checkout page, your server makes an API call to Adyen to get a list of available payment methods for the shopper. The shopper then selects a payment method from that list, and enters their payment details in the input fields rendered by Drop-in.

When the shopper selects the Pay button, your server uses the data returned by the client to submit a payment request to Adyen. If the response requires additional action from the shopper, Drop-in can handle these additional actions. In such cases, your server will make another request to Adyen to complete or verify the payment. When no additional actions are required, the payment result can be presented to the shopper.

Overview of the build process

After following this tutorial, you'll have a complete checkout solution including both server- and client-side implementations.

Server implementation

You'll begin by exposing a few endpoints on your server to handle all shopper interactions at checkout. The names of your server endpoints in this tutorial match those in Adyen's example integrations. You can name these endpoints however you like.

HTTP method Your server endpoint Role Related Adyen API endpoint
GET /getPaymentMethods Gets the available payment methods. /paymentMethods
POST /initiatePayment Sends payment parameters together with the input details collected from the shopper. /payments
POST, GET /handleShopperRedirect Sends payment details during a page redirect and determines the outcome for the shopper.

Used for all payment methods that require a redirect, for example iDEAL or Klarna.
/payments/details
POST /submitAdditionalDetails Sends payment details when a shopper is required to perform additional actions without leaving your website.

Examples: Native 3D Secure 2 authentication, native QR (WeChat Pay), and overlays (PayPal)
/payments/details

These endpoints are custom routes (URI patterns) that interact with the Adyen API at specific points in the checkout process, and are accessed by your client through fetch() requests. With the exception of /handleShopperRedirect, each endpoint returns a JSON response data back to the client.

Client implementation

After implementing the back end, you'll implement the front end with a configurable, ready-to-use Drop-in UI component.

You'll first add the necessary library and styles to your checkout page. Then, you'll create fetch() requests and event handlers to facilitate the app's client-server interactions. Finally, you'll configure and instantiate Drop-in mounted to your checkout page. You'll then be ready to start testing transactions with hundreds of payment methods from around the world.

Before you begin

This tutorial shows both server- and client-side integration steps for Drop-in. To be well prepared, you should have experience working with Node.js, Express, and a template system (such as Handlebars).

Before you begin your integration, make sure you have the following tools and credentials:

Item Description
Adyen test account This gives you access to the test Customer Area.
API key Private API key used to authenticate API calls to the Adyen payments platform.
Client key Public key used to authenticate requests from your web environment.
Adyen server-side library for Node.js API library that allows you to easily work with Adyen's APIs.

Build the server

You'll first implement the back end to make sure your server is ready for the checkout process.

Install dependencies

The integration on this page uses Node.js and Express, with Handlebars templating. For a smoother integration experience, the examples on this page use the following libraries as well:

npm package Description
express-handlebars Handlebars template (view) engine for Express.
cookie-parser Middleware to help parse cookie headers, as well as to get and set cookies.

In the root of your project, install the dependencies:

npm install --save express @adyen/api-library express-handlebars cookie-parser

Configure the server

After installing the dependencies, import the modules and configure the Adyen client in your server file (we use index.js in this example). Provide your API key as a string in the boilerplate code.

index.js
const express = require("express");
const path = require("path");
const hbs = require("express-handlebars");
const cookieParser = require("cookie-parser");
const { Client, Config, CheckoutAPI } = require("@adyen/api-library");
const app = express();

// Parse JSON bodies
app.use(express.json());
// Parse URL-encoded bodies
app.use(express.urlencoded({ extended: true }));
// Parse cookie bodies, and allow setting/getting cookies
app.use(cookieParser());

app.use(express.static(path.join(__dirname, "/public")));

// Adyen Node.js API library boilerplate (configuration, etc.)
const config = new Config();
config.apiKey = "YOUR_API_KEY_HERE";
const client = new Client({ config });
client.setEnvironment("TEST");
const checkout = new CheckoutAPI(client);

// Use Handlebars as the view engine
app.set("view engine", "handlebars");

// Specify where to find view files
app.engine(
  "handlebars",
  hbs({
    defaultLayout: "main",
    layoutsDir: __dirname + "/views/layouts",
    partialsDir: __dirname + "/views/partials"
  })
);

Next, you'll expose a few endpoints on your server to support different points in the checkout process.

Get available payment methods

The first point in the checkout process: the shopper is ready to pay and looks to select a payment method.

To get the available payment methods for the shopper, you'll expose an endpoint on your server by creating a GET route to /getPaymentMethods. When this endpoint is accessed, the instance of checkout will make a call to Adyen's /paymentMethods endpoint.

Pass in your merchantAccount and channel:Web. You can also specify amount and countryCode parameters— we'll use these to tailor the list of available payment methods to your shopper.

Next, you'll pass the entire JSON-stringified response from your server back to your client. In the example below, you'll pass it back to your checkout page, shown as the rendered payment template.

getPaymentMethods
app.get("getPaymentMethods", (req, res) => {
  checkout
    .paymentMethods({
      channel: "Web",
      merchantAccount: config.merchantAccount
    })
    .then(response => {
      // Adyen API response is passed to the client
      res.render("payment", {
        response: JSON.stringify(response)
      });
    });
});

An array of available payment methods is returned in the response, each with a name and type. Drop-in uses the response to show the available payment methods to the shopper.

/paymentMethods response
{
  "paymentMethods":[
    {
      "details":[...],
      "name":"Credit Card",
      "type":"scheme"
      // ...
    },
    // ...
  ]
}

We'll revisit this response object later on the client side when we configure Drop-in.

Initiate a payment

At this point in the checkout process, the shopper selects a payment method, enters their payment details, and selects the Pay button.

To initiate the payment, you'll expose an endpoint on your server by creating a POST route to /initiatePayment. When this endpoint is accessed, a call will be made to Adyen's /payments endpoint.

In our example, the shopper selects to pay with their card. Specify the amount, reference, merchantAccount, paymentMethod, channel, returnUrl, browserInfo, and shopperIP in the request body. Note that the values of certain parameters, such as browserInfo and paymentMethod, represent data passed from the client when performing the fetch() request.

A sample request for a credit card payment (with 3D Secure redirect authentication) of 10 EUR:

initiatePayment
// Note the jsonParser instance to help read the bodies of incoming JSON data
app.post("/initiatePayment", (req, res) => {
  checkout
    .payments({
      amount: { currency: "EUR", value: 1000 },
      reference: "12345",
      merchantAccount: config.merchantAccount,
      channel: "Web",
      returnUrl: "http://localhost:8080/handleShopperRedirect",
      browserInfo: req.body.browserInfo,
      paymentMethod: req.body.paymentMethod
    })
    .then(response => {
      const { resultCode, action } = response;

      if (action.paymentData) {
        res.cookie("paymentData", action.paymentData);
      }

      res.json({ resultCode, action });
    });
});

If the response contains an action object, this means that additional client-side actions are required to complete the payment. As shown above, you'll pass this action object (not the entire response) from the server to the client, where it will be handled by Drop-in. You'll also temporarily save paymentData (for example, in a database, or in a cookie as shown in this example). You'll need to include the paymentData in your next API request to check the payment result after a page redirection.

/payments response with an action object
{
   "resultCode":"RedirectShopper",
   "action":{
      "data":{
         "MD":"OEVudmZVMUlkWjd0MDNwUWs2bmhSdz09...",
         "PaReq":"eNpVUttygjAQ/RXbDyAXBYRZ00HpTH3wUosPfe...",
         "TermUrl":"https://example.com/checkout?shopperOrder=12xy.."
      },
      "method":"POST",
      "paymentData":"Ab02b4c0!BQABAgCJN1wRZuGJmq8dMncmypvknj9s7l5Tj...",
      "paymentMethodType":"scheme",
      "type":"redirect",
      "url":"https://test.adyen.com/hpp/3d/validate.shtml"
   },
   "details":[
      {
         "key":"MD",
         "type":"text"
      },
      {
         "key":"PaRes",
         "type":"text"
      }
   ],
   // ...
}

We'll revisit this action object later on the client side when we configure Drop-in to handle the action.

If there's no action object in the response, no additional actions are needed to complete the payment. This means that the transaction has reached a final status (for example, in a 3D Secure scenario, the transaction was either exempted or out-of-scope for 3D Secure authentication). Pass the resultCode included in the response to your client — you'll use this to present the payment result to your shopper.

Handle the redirect

At this point in the checkout process, the shopper has been redirected to another site to complete the payment, and back to your returnUrl using either an HTTP POST or GET. This redirection is a result of Drop-in performing the action.type: redirect specified in the /payments response.

After the shopper is redirected back, you need to verify or complete the payment using data from the redirected page. You'll do that by exposing two more endpoints on your server, representing a GET and POST route to /handleShopperRedirect.

When either endpoint is accessed, a call will be made to Adyen's /payments/details endpoint. In the request, specify:

  • paymentData: the value from the /payments response.
  • details: data returned from redirected page.
handleShopperRedirect
app.get("/handleShopperRedirect", (req, res) => {
  // Create the payload for submitting payment details
  let payload = {};
  payload["details"] = req.query;
  payload["paymentData"] = req.cookies["paymentData"];

  checkout.paymentsDetails(payload).then(response => {
    // Clear paymentData cookie after initially setting it in the /initiatePayment endpoint
    res.clearCookie("paymentData");
    // Conditionally handle different result codes for the shopper
    switch (response.resultCode) {
      case "Authorised":
        res.redirect("/success");
        break;
      case "Pending":
        res.redirect("/pending");
        break;
      case "Refused":
        res.redirect("/failed");
        break;
      default:
        res.redirect("/error");
        break;
    }
  });
});
app.post("/handleShopperRedirect", (req, res) => {
  let payload = {};
  payload["details"] = req.body;
  payload["paymentData"] = req.cookies["paymentData"];

  checkout.paymentsDetails(payload).then(response => {
    res.clearCookie("paymentData");
    switch (response.resultCode) {
      case "Authorised":
        res.redirect("/success");
        break;
      case "Pending":
        res.redirect("/pending");
        break;
      case "Refused":
        res.redirect("/failed");
        break;
      default:
        res.redirect("/error");
        break;
    }
  });
});

You'll receive a response with a pspReference and a resultCode, the latter of which can be used to present the payment result to the shopper.

Successful response

{
 "pspReference": "88154795347618C",
 "resultCode": "Authorised"
}

If you receive another action object in the response, pass this back to the client again.

We'll configure the Drop-in to handle additional actions in case you receive more action objects in the response.

Handle other additional details

For some payment flows, the shopper needs to perform additional steps without leaving your website. For example, this can mean scanning a QR code, or completing a challenge in case of native 3D Secure 2 authentication.

To handle submitting these additional details, you'll expose another endpoint on your server by creating a POST route to /submitAdditionalDetails. Similar to /handleShopperRedirect above, specify details and paymentData parameters in the call to Adyen's /payments/details endpoint. Note that this time, the shopper stays in your website so there is no view redirect logic in the server. Instead, any additional action objects are passed back to the client. The result code is also passed to the client, and this will be used to present the payment result to the shopper.

submitAdditionalDetails
app.post("/submitAdditionalDetails", (req, res) => {
  // Create the payload for submitting payment details
  let payload = {};
  payload["details"] = req.body.details;
  payload["paymentData"] = req.body.paymentData;
  // Return the response back to client
  // (for further action handling or presenting result to shopper)
  checkout.paymentsDetails(payload).then(response => {
    let resultCode = response.resultCode;
    let action = response.action || null;
    res.json({ action, resultCode });
  });
});

We'll configure the Drop-in to handle additional actions in case you receive more action objects in the response.

Handle result codes

When working with payment methods involving a page redirect, we handled the redirect logic in the server. In all other cases, however, result codes will be handled in the client, where you can present the payment outcome for your shopper. We'll take a look at the logic for handling result codes next when we build the client.

Build the client

After implementing the server code, we'll now implement an interactive UI component on the front end of your application.

Add the Drop-in script and styles

To add Drop-in to your checkout page, install the Adyen Web Node package or use a <script> tag:

  1. Include the following script in the <body> above any other JavaScript in your checkout page: 

    We recommend that you also validate the Subresource Integrity (SRI) hash, which we provide for our JavaScript and CSS files.

    <script src="https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/3.17.0/adyen.js"
    integrity="sha384-G9jkTAyCOIVdksXbtrNgdur2DyRi85ujYLXbqe5AvvH23rN21d7xiU7cRIqvsEaH"
    crossorigin="anonymous"></script>
    <!-- Adyen provides the SRI hash that you include as the integrity attribute. Refer to our release notes to get the SRI hash for the specific version. https://docs.adyen.com/checkout/release-notes -->
  2. Use the following CSS file:

    <link rel="stylesheet" href="https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/3.17.0/adyen.css"
    integrity="sha384-gbQyzqV1xX+snFEMrubEm0IpmYSufBjUnHzTaJZ+dcWCyxl0j94IZhnfC2gbxgeu"
    crossorigin="anonymous">
    <!-- Adyen provides the SRI hash that you include as the integrity attribute. Refer to our release notes to get the SRI hash for the specific version. https://docs.adyen.com/checkout/release-notes -->

    You can add your own styling by overriding the rules in this CSS file.

Create the container element

Next, create a DOM element on your checkout page with a descriptive id value. Place this element where you want Drop-in to be rendered on the page.

<!-- payment.handlebars -->

<div id="dropin-container"></div>

Later on, we'll mount Drop-in to this container.

Connect to your server

At this point, your server is already built to make the necessary API calls to Adyen corresponding to all shopper interactions at checkout. You'll handle this on the front end by building functions for any client-server interactions.

First, define a function that calls your server endpoints and passes JSON-stringified data from the client. We'll call this function callServer. The function is a fetch() request. We'll later change the url parameter based on which endpoint is requested, for example /initiatePayment or /submitAdditionalDetails.

callServer
function callServer(url, data) {
  return fetch(url, {
    method: "POST",
    body: JSON.stringify(data),
    headers: {
      "Content-Type": "application/json"
    }
  }).then(res => res.json());
}

Next, we need a function that determines how to handle the responses sent from the server to the client. We'll call this function handleServerResponse. The function first checks for an action in the API response, which Drop-in will handle. If there is no action in the API response, use the resultCode to present the payment result to your shopper. For a full list of result codes, refer to Result codes.

handleServerResponse
function handleServerResponse(res, dropin) {
  if (res.action) {
    dropin.handleAction(res.action);
  } else {
    switch (res.resultCode) {
      case "Authorised":
        window.location.assign = "/success";
        break;
      case "Pending":
        window.location.assign = "/pending";
        break;
      case "Refused":
        window.location.assign = "/failed";
        break;
      default:
        window.location.assign = "/error";
        break;
    }
  }
}

We'll use these functions next to create a flexible event handler on the page.

Create the event handler

During the checkout process, we need to handle certain events such as when the shopper selects the Pay button, or when additional information is required to complete the payment.

We can wrap the above client-server functions nicely in a single, reusable function. We'll call this function handleSubmission(). The function calls the server at the provided endpoint and passes the payment data.

handleSubmission
function handleSubmission(state, component, url) {
  callServer(url, state.data)
    .then(res => handleServerResponse(res, component))
    .catch(error => {
      throw Error(error);
    });
}

After the request is resolved, Drop-in handles the data returned in the Adyen API response.

Configure Drop-in

After building the client-server interaction, we'll now configure and instantiate Drop-in.

Create a configuration object with the following parameters:

Parameter name Required Description
paymentMethodsResponse -white_check_mark- The response from your /paymentMethods request passed to the client.
clientKey -white_check_mark- Public key used to authenticate requests from your web environment.
For more information, refer to client side authentication.
locale -white_check_mark- The shopper's locale. This is used to set the language rendered in the UI. For a list of supported locales, see Localization.
environment -white_check_mark- Use test. When you're ready to accept live payments, change the value to one of our live environments
onSubmit -white_check_mark- Assign the reusable event handlerhandleSubmission with your /initiatePayment endpoint. The onSubmit event is called when the shopper selects the Pay button.
onAdditionalDetails -white_check_mark- Assign the reusable event handler handleSubmission with your /submitAdditionalDetails endpoint. The onAdditionalDetails event is called if no final state was reached, or additional details are required.
paymentMethodsConfiguration Required or optional configuration for specific payment methods. For more information about payment method-specific configurations, refer to our payment method guides.

For our cards example, we'll include optional configuration - such as showPayButton, hasHolderName, holderNameRequired, name, and amount. For a full list of configurable fields, refer to optional Card configuration.

Drop-in configuration
const configuration = {
  paymentMethodsResponse: { response },
  clientKey: "YOUR_CLIENT_KEY",
  locale: "en_US",
  environment: "test",
  onSubmit: (state, component) => {
    handleSubmission(state, component, "/initiatePayment");
  },
  onAdditionalDetails: (state, component) => {
    handleSubmission(state, component, "/submitAdditionalDetails");
  },
  paymentMethodsConfiguration: {
    card: {
      showPayButton: true,
      hasHolderName: true,
      holderNameRequired: true,
      name: "Credit or debit card",
      amount: {
        value: 1000,
        currency: "EUR"
      }
    }
  }
};

Instantiate and mount Drop-in

After all the configuration are set, you are ready to instantiate your Drop-in. Create an instance of AdyenCheckout with your newly-created configuration object. You'll use the returned value to create and mount the instance of your UI Drop-in:

const checkout = new AdyenCheckout(configuration);
const integration = checkout.create("dropin").mount("#dropin-container");

Next steps

If you followed this tutorial, you now have an integration built to support all payment flows.

Adding more payment methods to Drop-in does not require building an entirely new integration. Your existing Drop-in code can be quickly extended to support your shoppers' preferred payment methods.