How 3D Secure Authentication Works in the Adyen Salesforce Commerce Cloud Cartridge

The Adyen Salesforce Commerce Cloud cartridge implements both 3D Secure 1 (redirect-based) and 3D Secure 2 (native) authentication flows by injecting 3DS2 data into payment requests, processing action-based responses from Adyen's API, and completing verification through dedicated controller endpoints that finalize transactions based on the authentication result.

The adyen/adyen-salesforce-commerce-cloud repository provides a comprehensive integration for Salesforce Commerce Cloud (SFCC) that handles complex payment authentication protocols. Understanding how 3D Secure authentication is implemented within this cartridge is essential for developers customizing payment flows or troubleshooting authentication failures in SFRA (Storefront Reference Architecture) implementations.

3D Secure Authentication Flow Overview

The cartridge supports both 3D Secure 1 (full-page redirect) and 3D Secure 2 (native frictionless/challenge) flows. The implementation follows a three-stage server-side process:

  1. Build the payment request – Injects 3DS2 authentication data into the Adyen Checkout request
  2. Handle the Adyen response – Processes action-based responses (redirect for 3DS1, native for 3DS2)
  3. Complete the authentication – Finalizes the transaction after shopper verification via /payments/details calls

Stage 1: Building the Payment Request with 3DS2 Data

When a shopper clicks Place Order, the cartridge constructs a Checkout request in adyenCheckout.createPaymentRequest. After building the basic request object, the system calls AdyenHelper.add3DS2Data to inject the 3D Secure 2 payload.

Injecting 3DS2 Payload in adyenHelper.js

The add3DS2Data function in src/cartridges/int_adyen_SFRA/cartridge/adyen/utils/adyenHelper.js adds the mandatory authentication fields that signal Adyen to use native 3DS2 when available:

// adyenHelper.add3DS2Data – inserts the mandatory fields
jsonObject.authenticationData = {
  threeDSRequestData: {
    nativeThreeDS: 'preferred',   // ask Adyen to use native 3‑DS 2 if possible
  },
};
jsonObject.channel = 'web';
jsonObject.origin = `${request.getHttpProtocol()}://${request.getHttpHost()}`;
return jsonObject;

This configuration prioritizes 3D Secure 2 while maintaining fallback compatibility with 3D Secure 1 for issuers that do not support the newer protocol.

Creating the Checkout Request

In src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/adyenCheckout.js, the createPaymentRequest function orchestrates the request building:

let paymentRequest = AdyenHelper.createAdyenRequestObject(...);
AdyenHelper.setPaymentInstrumentFields(paymentInstrument, paymentRequest);
paymentRequest = AdyenHelper.add3DS2Data(paymentRequest); // ← 3‑DS 2 injection
// ...additional data, risk data, etc.
return doPaymentsCall(order, paymentInstrument, paymentRequest);

Stage 2: Handling the Adyen Response

After sending the request to Adyen's /payments endpoint via adyenCheckout.doPaymentsCall, the cartridge examines the response for authentication actions stored in paymentResult.action.

Processing Redirect Actions for 3DS1

If Adyen returns an action.type === 'redirect', the cartridge stores the action JSON on the payment instrument:

// Stored in paymentInstrument.custom.adyenAction
// This triggers the redirect to Adyen's hosted 3DS1 page

This redirect action indicates the transaction requires 3D Secure 1 authentication through an external hosted page, typically when the issuer does not support 3DS2 or when the transaction risk profile requires it.

Handling Native 3DS2 Challenges

For action.type === 'threeDS2', the cartridge similarly stores the action in paymentInstrument.custom.adyenAction. However, instead of a full page redirect, the SFRA front-end component (AdyenComponent) renders the 3D Secure 2 challenge automatically within an iframe or modal, providing a frictionless user experience.

Stage 3: Completing the Authentication

The final stage differs based on whether the flow used 3D Secure 1 or 3D Secure 2, but both ultimately call doPaymentsDetailsCall to finalize the transaction.

Finalizing 3DS1 Redirects

After the shopper authenticates on Adyen's hosted page, Adyen redirects back to the storefront with a redirectResult query parameter. The redirect3ds1Response controller in src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/payments/redirect3ds1Response.js handles this:

function redirect(req, res, next) {
  try {
    const redirectResult = req.httpParameterMap.get('redirectResult').stringValue;
    const jsonRequest = { details: { redirectResult } };
    const result = adyenCheckout.doPaymentsDetailsCall(jsonRequest);

    if (result.resultCode === constants.RESULTCODES.AUTHORISED) {
      res.redirect(URLUtils.url('PaymentInstruments-List'));
    } else {
      res.redirect(URLUtils.url('PaymentInstruments-AddPayment', 'isAuthorised', 'false'));
    }
  } catch (error) {
    // error handling omitted for brevity
  }
  return next();
}

This function extracts the redirect result, calls doPaymentsDetailsCall to verify the authentication with Adyen, and redirects the shopper to either the saved cards list (on AUTHORISED status) or back to the add-payment page (on failure).

Resolving 3DS2 Challenges

For 3D Secure 2, the stored adyenAction is consumed by the front-end JavaScript component. Once the shopper completes the challenge, the component posts the details back to the /payments/details endpoint. The showConfirmation script processes this:

// showConfirmation.js – after the /payments call returns an action
if (paymentResult.action && paymentResult.action.type === 'threeDS2') {
  // Store the action on the payment instrument (already done server‑side)
  // The SFRA front‑end component reads paymentInstrument.custom.adyenAction
  // and renders the 3‑DS 2 challenge automatically.
}

The server-side logic then calls doPaymentsDetailsCall to finalize the transaction, using the same result handling logic as the 3DS1 flow to determine the final redirect destination.

Key Implementation Files

The 3D Secure authentication implementation spans several critical files within the src/cartridges/int_adyen_SFRA/cartridge/adyen/ directory:

File Role Direct Link
adyenHelper.jsadd3DS2Data Injects native 3‑DS 2 fields into the Checkout request. adyenHelper.js
adyenCheckout.jscreatePaymentRequest Builds the full payment payload and calls add3DS2Data. adyenCheckout.js
redirect3ds1Response.js Handles the redirectResult from a 3‑DS 1 flow and finalises the order. redirect3ds1Response.js
showConfirmation.js / handlePayment.js Consumes the adyenAction for a 3‑DS 2 challenge and drives the UI component. showConfirmation.js
controllers/Adyen.js (comments) Documents the overall payment‑status‑after‑redirect flow. Adyen controller

Summary

  • The Adyen SFCC cartridge supports both 3D Secure 1 (redirect-based) and 3D Secure 2 (native frictionless/challenge) authentication methods through a unified implementation.
  • 3DS2 data injection occurs in adyenHelper.js via add3DS2Data, which sets nativeThreeDS: 'preferred' to prioritize modern authentication protocols.
  • Action handling differentiates flows: redirect actions trigger 3DS1 external authentication, while threeDS2 actions enable native challenge rendering via the front-end component.
  • Completion logic unifies both flows through doPaymentsDetailsCall, with redirect3ds1Response.js handling 3DS1 returns and showConfirmation.js managing 3DS2 resolutions.
  • All authentication state persists in paymentInstrument.custom.adyenAction, enabling seamless handoff between server-side logic and client-side challenge rendering.

Frequently Asked Questions

What is the difference between 3D Secure 1 and 3D Secure 2 in the Adyen SFCC cartridge?

3D Secure 1 uses a full-page redirect to an Adyen-hosted authentication page, handled by redirect3ds1Response.js, while 3D Secure 2 uses a native challenge rendered within an iframe or modal via the front-end AdyenComponent, managed through showConfirmation.js. The cartridge prioritizes 3DS2 by setting nativeThreeDS: 'preferred' in the payment request, falling back to 3DS1 only when the issuer does not support the newer protocol.

How does the cartridge decide whether to use 3D Secure 1 or 3D Secure 2?

The decision is primarily driven by Adyen's risk engine and issuer capabilities, but the cartridge signals its preference through AdyenHelper.add3DS2Data, which injects authenticationData with nativeThreeDS: 'preferred'. If the issuer supports 3DS2, Adyen returns a threeDS2 action; otherwise, it returns a redirect action for 3DS1. Both actions are stored in paymentInstrument.custom.adyenAction for processing.

Where is the 3D Secure authentication state stored during the checkout process?

The authentication action—whether a redirect URL for 3DS1 or challenge data for 3DS2—is stored in the paymentInstrument.custom.adyenAction custom attribute. This server-side storage enables the front-end component to retrieve the action and render the appropriate challenge or redirect, while also allowing server-side scripts like redirect3ds1Response.js to access the original transaction context when processing return redirects.

What happens if a 3D Secure 1 authentication fails or is cancelled?

When the shopper returns from the 3DS1 redirect without successful authentication, the redirect3ds1Response controller extracts the redirectResult parameter and calls doPaymentsDetailsCall to verify the status with Adyen. If the result code is not AUTHORISED (defined in constants.RESULTCODES), the controller redirects the shopper to PaymentInstruments-AddPayment with isAuthorised=false, allowing the merchant to display an error message and prompt for alternative payment methods or retry the transaction.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →