Check out the full project in our Drop-in and Node.js example repository on GitHub.
Drop-in is our pre-built UI solution for accepting payments on your website. Drop-in shows all payment methods as a list, in the same block. This integration requires you to make API requests to /paymentMethods, /payments, and /payments/details endpoints.
Adding new payment methods usually doesn't require more development work. Drop-in supports cards, wallets, and most local payment methods.
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
Prefer to watch a video tutorial?
Check out our Drop-in tutorial: Node.js + Express on YouTube.
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 |
/api/getPaymentMethods
|
Gets the available payment methods. | /paymentMethods |
POST |
/api/initiatePayment
|
Sends payment parameters together with the input details collected from the shopper. | /payments |
POST , GET |
/api/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 |
/api/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. |
Make sure you have the latest versions of the Checkout API and the Node.js library.
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. |
uuid | Creates unique identifiers in RFC4122 UUID format. |
In the root of your project, install the dependencies:
npm install --save express @adyen/api-library express-handlebars uuid
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 or fetch it from the environment. You can use a library like dotenv to load environment variables when the application loads.
const express = require("express");
const path = require("path");
const hbs = require("express-handlebars");
const { v4: uuidv4 } = require('uuid');
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 }));
// Serve client from build folder
app.use(express.static(path.join(__dirname, "/public")));
// Adyen Node.js API library boilerplate (configuration, etc.)
const config = new Config();
config.apiKey = process.env.ADYEN_API_KEY;
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"
})
);
/* ################# API ENDPOINTS ###################### */
/* ################# CLIENT SIDE ENDPOINTS ###################### */
// Start server
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
Next, you'll expose a few API 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 /api/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 response from your server back to your client.
app.post("/api/getPaymentMethods", async (req, res) => {
try {
const response = await checkout.paymentMethods({
channel: "Web",
merchantAccount: process.env.MERCHANT_ACCOUNT,
});
res.json(response);
} catch (err) {
console.error(`Error: ${err.message}, error code: ${err.errorCode}`);
res.status(err.statusCode).json(err.message);
}
});
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.
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 /api/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
, and browserInfo
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:
// A temporary store to keep payment data to be sent in additional payment details and redirects.
// This is more secure than a cookie. In a real application this should be in a database.
const paymentDataStore = {};
app.post("/api/initiatePayment", async (req, res) => {
try {
// unique ref for the transaction
const orderRef = uuidv4();
// Ideally the data passed here should be computed based on business logic
const response = await checkout.payments({
amount: { currency: "EUR", value: 1000 }, // value is 10€ in minor units
reference: orderRef, // required
merchantAccount: process.env.MERCHANT_ACCOUNT, // required
channel: "Web", // required
// we pass the orderRef in return URL to get paymentData during redirects
returnUrl: `http://localhost:8080/api/handleShopperRedirect?orderRef=${orderRef}`, // required for redirect flow
browserInfo: req.body.browserInfo,
paymentMethod: req.body.paymentMethod // required
});
const { action } = response;
if (action) {
paymentDataStore[orderRef] = action.paymentData;
}
res.json(response);
} catch (err) {
console.error(`Error: ${err.message}, error code: ${err.errorCode}`);
res.status(err.statusCode).json(err.message);
}
});
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 the 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 cache as shown in this example). You'll need to include the paymentData
in your next API request to check the payment result in case of a page redirection.
{
"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 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 /api/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.
// handle both POST & GET requests
app.all("/api/handleShopperRedirect", async (req, res) => {
// Create the payload for submitting payment details
const orderRef = req.query.orderRef;
const redirect = req.method === "GET" ? req.query : req.body;
const details = {};
if (redirect.redirectResult) {
details.redirectResult = redirect.redirectResult;
} else {
details.MD = redirect.MD;
details.PaRes = redirect.PaRes;
}
const payload = {
details,
paymentData: paymentDataStore[orderRef],
};
try {
const response = await checkout.paymentsDetails(payload);
// Conditionally handle different result codes for the shopper
switch (response.resultCode) {
case "Authorised":
res.redirect("/result/success");
break;
case "Pending":
case "Received":
res.redirect("/result/pending");
break;
case "Refused":
res.redirect("/result/failed");
break;
default:
res.redirect("/result/error");
break;
}
} catch (err) {
console.error(`Error: ${err.message}, error code: ${err.errorCode}`);
res.redirect("/result/error");
}
});
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": "NC6HT9CRT65ZGN82",
"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 /api/submitAdditionalDetails
. Similar to /api/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.
app.post("/api/submitAdditionalDetails", async (req, res) => {
// Create the payload for submitting payment details
const payload = {
details: req.body.details,
paymentData: req.body.paymentData,
};
try {
// Return the response back to client (for further action handling or presenting result to shopper)
const response = await checkout.paymentsDetails(payload);
res.json(response);
} catch (err) {
console.error(`Error: ${err.message}, error code: ${err.errorCode}`);
res.status(err.statusCode).json(err.message);
}
});
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-side 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:
-
Install the Adyen Web Node package:
npm install @adyen/adyen-web --save
-
Import Adyen Web into your application:
import AdyenCheckout from '@adyen/adyen-web'; import '@adyen/adyen-web/dist/adyen.css';
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 uses a JavaScript fetch()
request to communicate with the server. We'll later pass the url
parameter based on which endpoint is requested, for example /api/initiatePayment
or /api/submitAdditionalDetails
.
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
object in the API response, which Drop-in will handle. If there is no action
object 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.
function handleServerResponse(res, component) {
if (res.action) {
component.handleAction(res.action);
} else {
switch (res.resultCode) {
case "Authorised":
window.location.href = "/result/success";
break;
case "Pending":
case "Received":
window.location.href = "/result/pending";
break;
case "Refused":
window.location.href = "/result/failed";
break;
default:
window.location.href = "/result/error";
break;
}
}
}
We'll use these functions 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 clicks 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.
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 |
![]() |
The response from your /api/getPaymentMethods request. |
clientKey |
![]() |
Public key used to authenticate requests from your web environment. For more information, refer to client side authentication. |
locale |
![]() |
The shopper's locale. This is used to set the language rendered in the UI. For a list of supported locales, see Localization. |
environment |
![]() |
Use test. When you're ready to accept live payments, change the value to one of our live environments. |
onSubmit |
![]() |
Assign the reusable event handler handleSubmission with your /api/initiatePayment endpoint. The onSubmit event is called when the shopper selects the Pay button. |
onAdditionalDetails |
![]() |
Assign the reusable event handler handleSubmission with your /api/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.
Let us create this inside an async
function so that we can call other async
functions with the await
syntax.
async function initCheckout() {
try {
const paymentMethodsResponse = await callServer("/api/getPaymentMethods");
const configuration = {
paymentMethodsResponse: paymentMethodsResponse,
clientKey: "YOUR_CLIENT_KEY",
locale: "en_US",
environment: "test",
paymentMethodsConfiguration: {
card: {
showPayButton: true,
hasHolderName: true,
holderNameRequired: true,
name: "Credit or debit card",
amount: {
value: 1000,
currency: "EUR"
}
}
},
onSubmit: (state, component) => {
if (state.isValid) {
handleSubmission(state, component, "/api/initiatePayment");
}
},
onAdditionalDetails: (state, component) => {
handleSubmission(state, component, "/api/submitAdditionalDetails");
},
};
} catch (error) {
console.error(error);
}
}
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. Finally invoke the initCheckout
function and we are done.
async function initCheckout() {
try {
// ...
const configuration = // ...
const checkout = new AdyenCheckout(configuration);
checkout.create("dropin").mount(document.getElementById("#dropin-container"));
} catch (error) {
// ...
}
}
initCheckout();
You can checkout the full Node.js example integration on our GitHub examples repository.