Spreedly 3DS2 Global Integration Guide for Web

Spreedly 3DS2 Global is currently in beta. Documentation and API usage are subject to change before final release. Please contact Spreedly support if you encounter any problems with your Spreedly 3DS2 Global integration.

To prepare your payment application for the Payment Services Directive and 3DS2 Requirements with Spreedly, you will need to implement and test each of the flows outlined below. If you have questions not addressed in the integration guide, please contact Spreedly support for assistance.

For general PSD2 Compliance information, please visit our PSD2 Compliance Guide.

Prerequisites

Before starting, make sure you have completed your Merchant Profile and SCA Provider set up using our Spreedly 3DS2 Global guide

  1. You will need a Spreedly Test gateway token to interact with the simulated 3DS2 flows. If you already have a Spreedly Test gateway setup you are likely good to go. If you don’t have a Spreedly Test gateway setup or want to spin up a new one for 3DS2 testing, follow the testing guide.

    Note: Alternatively, you may test against a specific gateway by creating one in sandbox mode.

  2. Verify your test gateway can successfully make a purchase without 3DS. This will help limit future troubleshooting to 3DS specific changes.

Guide companion sample app

Your app probably uses a framework (e.g. Angular, React, Vue, etc) or custom project layout; the interaction between your backend and frontend, and handling of our events will vary, depending on which.

We’ve created a sample Spreedly 3DS Reference Implementation which is a pure HTML/JS/CSS app that illustrates all the concepts laid out in this guide, simulating 2 simple checkout experiences using our IFrame and Express API.

Prepare Your App

Spreedly provides helpers in our Express and iFrame libraries for handling asynchronous 3DS2 transactions in the browser. The method is the same as it was with Gateway Specific 3DS2 integrations, so if you have integrated with any of those then you should be ready to start processing transactions with a Spreedly SCA Provider. If not, below is a guide to using the 3DS functionality of our iFrame and Express libraries.

  1. Include Spreedly iFrame javascript on your checkout page

    <head>
      <script src="https://core.spreedly.com/iframe/iframe-v1.min.js"></script>
    </head>
    

    or include Spreedly Express javascript

    <head>
      <script src="https://core.spreedly.com/iframe/express-2.min.js"></script>
    </head>
    
  2. Generate or use an existing payment method token. e.g.: using our IFrame, Express or API calls

    More details available at Spreedly API Payment Method

Start the Transaction

In addition to the standard information required for a non-3DS2 transaction, additional browser information is required to help assess the risk of a given transaction. Spreedly’s iFrame and Express libraries provide a helper function that captures this information and allows you to relay it to our backend as part of the gateway transaction.

  // Choose a browser size for your application.
  // This will be the size of the challenge iframe that will be presented
  // to a user.
  //
  // Note: If you're creating a modal, you should make the surrounding DOM node a
  // little larger than the option selected below.
  //
  // '01' - 250px x 400px
  // '02' - 390px x 300px
  // '03' - 500px x 600px
  // '04' - 600px x 400px
  // '05' - fullscreen
  var browser_size = '04';

  // The accept header from your server side rendered page.
  // You'll need to inject it into the page. Below is an example.
  var acceptHeader = 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.8'

  // Capture browser data by using `Spreedly.ThreeDS.serialize().
  let browser_info = Spreedly.ThreeDS.serialize(
    browser_size,
    acceptHeader
  );

An example request to your backend API may look like:

  fetch('https://your-backend.test/do-purchase.json', {
    method: 'POST',
    body: JSON.stringify({
      your_param1: 'your_value1',
      your_param2: 'your_value2',
      // ... more params
      your_paramN: 'your_value1'
    })
  });

In the example, your_param[1..N] will be the parameters to the backend, that you control and define, e.g.:

  • order_id, or something that helps you determine what amount to pass.
  • token, may represent the value for our payment_method_token below
  • client_browser, our browser_info, generated above

In your backend, create an authorize or purchase request to the Spreedly API.

  POST /v1/gateways/<gateway_token>/purchase.json HTTPS/1.1
  Host: core.spreedly.com
  Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
  Content-Type: application/<format>

  {
    "transaction": {
      "sca_provider_key": "<sca_provider_key>",
      "payment_method_token": "<payment_method_token>",
      "amount": 10000,
      "currency_code": "EUR",
      "callback_url": "<callback_url (optional)>",
      "browser_info": "<value from Spreedly.ThreeDS.serialize()>"
    }
  }

The communication between your frontend and backend is entirely up to you, we recommend you keep track of the value used in payment_method_token value.

A quick and easy way to do this is passing back your frontend the token from the Spreedly API call you made, as part of your own response. For details see our Spreedly API purchase docs. Note: The callback(URL) is an additional, optional way of receiving notification directly from our server to your backend. You can find more information about callbacks in our 3DS1 guide. After the purchase or authorize call, there are three possible scenarios you will need to handle:

  • If response.state == "failed", handle the error response and display feedback to your user.
  • If response.state == "succeeded", the purchase has completed and you can direct your user to a confirmation page.
  • If response.state == "pending", then a challenge is required to finish the authentication. Please see the following section on how to finish a pending transaction.

Let Spreedly 3DS handle your customer authentication

If the transaction is in a pending state, the Spreedly Lifecycle object can be used to help simplify your frontend integration. The Lifecycle object polls the Spreedly backend and emits events when the transaction changes status or times out. If a challenge needs to be displayed, it will create an iFrame element and inject it with the form from the acquiring bank. A Lifecycle object needs the following parameters during initialization:

var lifecycle = new Spreedly.ThreeDS.Lifecycle({
  // The environmentKey field is required, but if omitted,
  // you will receive a console warning message and the transaction
  // will still succeed.
  environmentKey: '...',

  // The DOM node that you'd like to inject hidden iframes
  hiddenIframeLocation: 'device-fingerprint', // (required)

  // The DOM node that you'd like to inject the challenge flow
  challengeIframeLocation: 'challenge', // (required)

  // The token for the transaction - used to poll for state
  transactionToken: '...', // (required)

  // The css classes that you'd like to apply to the challenge iframe.
  // e.g. 'red-border left-positioned custom-styles'
  challengeIframeClasses: '...', // (optional)
})
  <head>
    <style>
      .hidden {
        display: none;
      }
      #challenge-modal {
        /* style your modal here */
      }
    </style>
  </head>
  <body>
    <div id="device-fingerprint" class="hidden">
      <!-- Spreedly injects content into this div,
      do not nest the challenge div inside of it -->
    </div>
    <div id="challenge-modal" class="hidden">
      <div id="challenge"></div>
    </div>
  </body>

Lifecycle allows users to attach event handlers that will receive updates about a transaction via events. In your callback function, the following event.action values should be handled:

  • succeeded - occurs when the transaction has finished and it’s time to move your user away from your checkout page.
  • error - occurs when there was an error with the transaction and you should either present an error to the user or cancel the transaction. Transactions that have failed cannot be updated or used to challenge the cardholder again; if you would like to present a cardholder with a new challenge upon failure, a new transaction should be used.
  • challenge - occurs when it’s time to pop open the challenge flow. It’s recommended that you put the challengeIframeLocation inside of another containing DIV that is hidden and show it at this time.
  • finalization-timeout - your customer authentication could not be completed within the expected window. This gets triggered 10-15 minutes after presenting a challenge without the transaction state changing. See our Flow Descriptions for more details.

To attach an event handler, declare it as a function and use the Spreedly.on function as shown below:

  var on3DSstatusUpdatesFn = function(threeDsStatusEvent) {
    if (threeDsStatusEvent.action === 'succeeded') {

      // finish your checkout and redirect to success page

    } else if (threeDsStatusEvent.action === 'error') {
      // present an error to the user to retry
    } else if (threeDsStatusEvent.action === 'finalization-timeout') {
      // present an error to the user to retry
    } else if (threeDsStatusEvent.action === 'challenge') {
      // show the challenge-modal
      document.getElementById('challenge-modal').classList.remove('hidden');
    }
  }

  // Setup event handling and kickoff 3DSecure lifecycle
  Spreedly.on('3ds:status', on3DSstatusUpdatesFn)

Note: If you plan to run multiple 3DS2 authentications without a full page reload or redirect, the Spreedly.on('3ds:status', on3DSstatusUpdatesFn) function should only be invoked once. Invoking it multiple times will register the event handlers multiple times leading to duplicate events being received.

Finally, start the polling process by calling the Lifecycle’s start function:

lifecycle.start()

End to end flow diagram

Test Data for Spreedly 3DS2 Global

Credit Cards

Card Number CVV Expiration Type
5555555555554444 123 10/2029 3D Secure Enrolled Card

Note: Flow scenarios are available for any test MasterCard starting with 555555, but using the same card over and over will automatically switch your request to the challenge flow. To mitigate this, you can change the card number by changing the last two numbers and verifying it with a Luhn calculator.

Test Scenarios

Setting the amount of the transaction to a certain value will simulate certain authentication results.

For these test amounts, currency_code should be set to EUR.

Note: Currently in beta, not all potential flows can be tested. We are working to support testing of a broader variety of situations.

Amount in cents Response Status Flow Description
4 Y Frictionless Authentication/account verified
9996 A Frictionless ACS could not be reached, but Authentication/account verification performed by issuing bank
9997 U Denied Authentication/account verification could not be performed, transaction fails
9998 N Denied Not authenticated/account not verified, transaction fails
9999 R Denied Authentication/account verification rejected, transaction fails
100000 C Challenge Authentication requires Challenge by issuing bank, handled by Lifecycle

Challenge Values

When presented with a challenge, entering the code 123456 will result in a successful authentication.

Note: Our 3DS2 solution partners are based in Spain, so the example ACS challenge page may be in Spanish. This will only occur in the sandbox; for real 3DS2 authentications, the acquiring bank’s page will be displayed instead.

Simulating a 3DS1 fallback

In Production, fallback to 3DS1 will automatically happen for your customers, however during your integration tests triggering conditions can be simulated by passing a custom Window object as a third parameter to our Spreedly.ThreeDS.serialize function.

Following the DOM specification for the Window interface, your object should provide screen and navigator properties. Make one or more of the returned properties be incorrect, such as window height of zero, and our API will interpret that as suspicious, prompting a 3DS1 fallback challenge to the user.

For a complete example, see /public/javascripts/your-code.js in our Spreedly 3DS Reference Implementation.

DO NOT use the third parameter in your production code, else ALL your customers will experience unwanted fallbacks and challenges.

      // Capture browser data by using `Spreedly.ThreeDS.serialize().
      let browser_info = Spreedly.ThreeDS.serialize(
        browser_size,
        acceptHeader,
        new MockFallbackBrowser()
      );

Here’s what MockFallbackBrowser could look like:

/**
 * Wrapper around the regular `window` object.
 *
 */
class MockFallbackBrowser {
  screen = {
    width: "0",
    height: "0",
    colorDepth: "-1"
  };

  navigator = {
    javaEnabled: function () {
      return window.navigator.javaEnabled()
    },
    userAgent: "Mozilla/5.0 (MacOS; Intel Mac OS X 10_15_6) Spreedly 3DS Debugging",
    language: window.navigator.language
  }
}

Spreedly 3DS2 Global Flow Descriptions

Detailed below are the flows possible for a Spreedly 3DS2 Global transaction. Please note the behavior can differ from Gateway Specific 3DS flows.

3DS2 Fully Frictionless

This flow represents the smoothest path through to transaction success. During the authorize or purchase flow the transaction, along with collected browser data, is deemed enough to verify the purchaser. No further action is required.

3DS2 Direct Challenge

This occurs when a transaction is deemed risky. The customer will be presented with an authentication form from the issuing bank, rendered in an iFrame, typically a modal.

3DS2 Denied

If Authentication fails, is rejected or denied, Spreedly fails the transaction and no call is made to the gateway.

3DS Not Enrolled/Supported

Spreedly fails the transaction and no call is made to the gateway. You may wish to re-attempt the transaction without requesting Spreedly 3DS2.

Fallback from 3DS2 to 3DS1

The Spreedly Javascript will present a challenge form to the user within the browser window, just as with a regular challenge, but the version of 3DS will resolve to v1.

Troubleshooting

  1. For security reasons you should never make api requests from your frontend application directly to Spreedly. For this reason the authorize/purchase and complete transaction requests need to be made from your backend application.
  2. Ensure that the Spreedly Javascript library has loaded properly. You can do so by looking at your browser developer tools and looking at the network traffic.
  3. Ensure that your data in the purchase and completion requests is being passed to the Javascript library correctly. console.dir the object that you’re passing to the javascript library right after the purchase request (before lifecycle.start()) and before event.finalize.
  4. Ensure that you’re collecting the accept header from your server side rendered page correctly (console.log). It might be best to inject the accept header as a hidden form field and grab it with javascript.
  5. Ensure that the ordering of interactions with the Spreedly Javascript library are correct. Create a function to listen to events, then register that event handler with Spreedly and issue your purchase request (with browser info). Finally, issue start on the lifecycle object you’ve created.
  6. You can also check where things are at by doing a console.dir on the event object in the statusUpdate event handler.
  7. If all else fails, please reach out! Contact us at support@spreedly.com - we’d love to help.

Want to learn more about 3DS2?

You can read more about the regulations behind these changes on our blog or see the full specification details at the source, EMVco.