import {
AdyenCheckout,
Dropin,
Card,
GooglePay,
ApplePay,
PayPal,
Klarna,
} from '@adyen/adyen-web';
import '@adyen/adyen-web/styles/adyen.css';
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.
See Start integrating with Web Drop-in to learn more.
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:
Install and import Adyen Web.
Create a payment session.
Initialize and mount the Drop-in component.
Handle completion, failure, and cart submission.
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.
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.
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.
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.
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;
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" />;
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];
};
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.
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();
}
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.
};
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,
});
Use this flow when a shopper adds a payment method from an account page, outside of checkout.
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);
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');