Card Form Component with 3DSecure

Overview

The Card Form Javascript component and 3DSecure Session function provide a PCI-compliant way for you to collect customer card details during checkout for use when creating a booking.
This guide is only relevant to you if your customers use their credit or debit cards to pay airlines and accommodation providers directly for their bookings.
For more information on how to use these components within your checkout experience, please read our Paying with Customer Cards implementation guide.
Continue reading for further information to help with your integration, including interface documentation, styling the components to match your brand, examples and sample code.
This guide covers the Card Form component and the 3DSecure Session function along with common getting started guidance.

Getting Started

These are common steps required to use the Card Form component and creating 3DSecure Sessions.

Installing

The functionality described in this document is available from version 3.7.22. Install it with:

Shell

npm i @duffel/components
# -- or --
yarn add @duffel/components

Client key

The components can only be initiated with a client key. To create one, use the endpoint to create a Component client key.

Card Form Component

The Card Form Javascript component provides a PCI-compliant way for you to collect customer card details during checkout for use when creating a booking.
Logo for undefined

See it on GitHub

You can find the source code and release notes for this component on GitHub.

Logo for undefined

See it on npm

This component is available to be installed with npm.

Demo

Use cases

The DuffelCardForm component supports three checkout flows:

Using a card for single transaction

Steps:
  • Users fill out a form with their card and cardholder address information.

  • The card is safely stored and the component will return an ID, along with some identifiers for the card (i.e. last 4 digits, brand, card holder name).

  • You should use the ID in the createThreeDSecureSession function or creating orders or booking endpoints.

Saving a card for multiple future transactions

Typically used in scenarios such as storing a card on a traveller profile for faster checkout.
Steps:
  • Users fill out a form with their card and cardholder address information. The Card Verification Code (CVC) will not be asked for in the form. Once a card is saved, the user need to enter their CVC for ever future transaction.

  • The card is safely stored and the component will return an ID, along with some identifiers for the card (i.e. last 4 digits, brand, card holder name). This card will be stored in our system until you delete it, or the expiry date lapses.

  • You store the card ID along with any required identifiers in your system and associate it with a user. You can then present the card as a payment option when users want to create an order or booking.

Using a saved card for a single transaction

Steps:
  • You supply a saved card ID when initiating the component. The component will only render the CVC input.

  • User fills out the CVC. A new single use card ID is returned for this specific transaction, along with some identifiers for the card (i.e. last 4 digits, brand, card holder name).

  • You will then use the ID in the createThreeDSecureSession function or creating orders or booking endpoints.

Intents

Each of the use cases above requires a different UI. To tell the component which use case you are rendering it for, you must provide an intent property:
Use caseIntent value
Using a card for single transactionto-create-card-for-temporary-use
Saving a card for multiple future transactionsto-save-card
Using a saved card for a single transactionto-use-saved-card

Actions

Actions are made available through an imperative interface. The component ref exposes two actions that will issue request to Duffel to handle the user’s card data. You may choose to manage the ref yourself but we recommend using the useDuffelCardFormActions hook:

React

function YourComponent() {
const { ref, saveCard, createCardForTemporaryUse } = useDuffelCardFormActions()
return <>
<DuffelCardForm ref={ref} ... />
<button onClick={saveCard}>Save</button>
<button onClick={createCardForTemporaryUse}>Pay</button>
</>
}

Event handlers

Once an action is triggered, it will issue a request to Duffel and you must setup event handlers as props on the component for the success and failure cases.

Form validation

This will happen automatically. It doesn’t require you to trigger a ref action. You should use the success handler to enable the following steps on your UI.
To handle this action use:
  • onValidateSuccess : The card data input is valid.

  • onValidateFailure : The card data input is no longer valid after it has been successfully validated at least once before.

Calling saveCard

When the to-save-card intent is supplied, use saveCard to initiate the request to Duffel. When successful, you will have the card ID, along with some identifiers for the card (i.e. last 4 digits, brand, card holder name) available for storing in your system.
To handle this action use:
  • onSaveCardSuccess : Returns the Card record.

  • onSaveCardFailure : Returns information about the error.

Calling createCardForTemporaryUse

When supplying the to-create-card-for-temporary-use or to-use-saved-card intents, you should call createCardForTemporaryUse to make the request to Duffel to create a card that can be used for creating order, bookings and 3DS sessions.
To handle this action use:
  • onCreateCardForTemporaryUseSuccess : Returns the Card record.

  • onCreateCardForTemporaryUseFailure : Return information about the error.

Tip

Saving and using card during checkout

A fourth common consumer use case is to offer the option to save a card during checkout for future bookings. This can be achieved using the saveCard and createCardForTemporaryUse intents in conjunction. Actions work independently and can be triggered asynchronously based on different events you may capture on your UI.
To support both the saveCard and createCardForTemporaryUse actions use the to-create-card-for-temporary-use intent and set up the success and failure handlers for both of these actions.
You may call saveCard along with createCardForTemporaryUse when the form submitted and with confirmation from the user that they’d like their card to be saved.
Please note: You will receive a different Card object from each action. You need to make sure to save the card information returned from onSaveCardSuccess is stored for future use, and use the card information from onCreateCardForTemporaryUseFailure for the current transaction.

Component Interface

export interface DuffelCardFormProps {
/**
* The client key retrieved from the Duffel API.
*/
clientKey: string
/**
* The styles to apply to the iframe input elements.
*/
styles?: DuffelCardFormStyles
/**
* The card intent defines what the form is meant to look like.
* It can be one of:
*
* - `to-create-card-for-temporary-use`: The full form will be shown. You may also use this intent for the use case of saving and using the card.
* - `to-use-saved-card`: Only a CVC field will be shown. When using this intent a saved card ID is required.
* - `to-save-card`: The form will be shown without the CVC field. This only allows you to save a card for future use,
* but not create an ID for immediate, temporary use. For the use case of saving and using during checkout, use the `to-create-card-for-temporary-use` intent.
*/
intent: DuffelCardFormIntent
/**
* When using the `use-saved-card` intent, you must provide the card ID.
*/
cardId?: string
/**
* This function is called when the card form validation has been successful.
*/
onValidateSuccess?: () => void
/**
* This function is called if the card form validation is successful but data is changed afterwards,
* making it invalid.
*/
onValidateFailure?: () => void
/**
* This function is called when the card has been created for temporary, single use.
*
* This callback is triggered if the `create-card-for-temporary-use`
* action is present in the `actions` prop. Alternatively,
* you may use the `triggerCreateCardForTemporaryUse` function from the
* `useDuffelCardFormActions` hook.
*/
onCreateCardForTemporaryUseSuccess?: (
data: CreateCardForTemporaryUseData
) => void
/**
* This function is called when the component has failed to create the card for temporary use.
*
* This callback is triggered if the `create-card-for-temporary-use`
* action is present in the `actions` prop. Alternatively,
* you may use the `triggerCreateCardForTemporaryUse` function from the
* `useDuffelCardFormActions` hook.
*/
onCreateCardForTemporaryUseFailure?: (
error: CreateCardForTemporaryUseError
) => void
/**
* This function is called when the card has been saved for multi-use.
*
* This callback is triggered if the `save-card`
* action is present in the `actions` prop. Alternatively,
* you may use the `triggerSaveCard` function from the
* `useDuffelCardFormActions` hook.
*/
onSaveCardSuccess?: (data: SaveCardData) => void
/**
* This function is called when saving the card has failed.
*
* This callback is triggered if the `save-card`
* action is present in the `actions` prop. Alternatively,
* you may use the `triggerSaveCard` function from the
* `useDuffelCardFormActions` hook.
*/
onSaveCardFailure?: (error: SaveCardError) => void
}
// Supporting types
export interface CreateCardForTemporaryUseData {
id: string
live_mode: boolean
}
export type CreateCardForTemporaryUseError = {
status: number
message: string
}
export type SaveCardError = {
status: number
message: string
}
export interface SaveCardData {
/**
* Duffel's unique identifier for the resource
*/
id: string
/**
* Whether the card was created in live mode. This field will be set to true
* if the card was created in live mode, or false if it was created in test mode.
*/
live_mode: boolean
/**
* Last 4 digits of the card number.
*/
last_4_digits: string
/**
* The card expiry month as an integer with two digits.
*/
expiry_month: number
/**
* The card expiry year as an integer with two digits.
*/
expiry_year: number
/**
* Card brand name.
*/
brand:
| 'visa'
| 'mastercard'
| 'uatp'
| 'american_express'
| 'diners_club'
| 'jcb'
| 'discover'
/**
* The ISO 8601 datetime at which the card will be automatically deleted.
*/
unavailable_at: string | null
}

Styling

You can customise the component styles to match your brand. We support system fonts for font customisation.
You can customise any styles for a few different aspects of the component. The style customisation interface is below:

React

/**
* An object where each key value pair is a style to be applied.
* e.g. { 'background-image': 'red', 'color': '#000', 'margin-inline': '8px' }
*
* Note: If you rely on css variables these will not work as they are
* defined on a stylesheet the component does not have access to.
*/
type StylesMap = Record<string, string>
export interface InteractiveElementStyles {
default?: StylesMap
hover?: StylesMap
active?: StylesMap
focus?: StylesMap
}
export interface DuffelCardFormStyles {
input?: InteractiveElementStyles
select?: InteractiveElementStyles
label?: StylesMap
inputErrorMessage?: StylesMap
sectionTitle?: StylesMap
layoutGrid?: StylesMap
}

3DSecure Session function

Card payments must be authenticated before authorisation of a payment can be given. In some circumstances, you will be required to perform that authentication, in the form of a 3D Secure (3DS) challenge, on behalf of the airline or accommodation provider.
You must call the create3DSecureSession function prior to attempting to pay with a customer card to minimise the risk of payment declines.
If the card does not require authentication then the create3DSecureSession function will instruct you to go straight to payment.
For more information on 3D Secure, please read the callout in the Pay with customer card guide.
This section will now guide you through how to present the 3D Secure challenge screen and handle the possible responses.
Example of a 3DSecure challenge

Example of a 3DSecure challenge

Use Cases

During an online payment checkout, the user may need to complete a challenge provided by their card issuer. You’ll use the createThreeDSecureSession to create and render the challenge.
The challenge flow starts on your checkout page and includes the following steps:

Step 1: Collect the customers card details

You need a valid card details to start this process. Collect card details from your user with the Card Form outlined above.

Step 2: Initiate 3DSecure Session

Calling the createThreeDSecureSession function initiates the following process with the cardholder’s issuing bank:
  • A request to the cardholder's issuing bank to authenticate a payment amount for a payment to a specific merchant.

  • The issuing bank might require fingerprinting of the cardholder's browser and requiring a verification challenge during checkout. The issuing bank determines the challenge flow, which usually involves the cardholder confirming the transaction using the method set up with their issuing bank (SMS, banking app, or email).

  • If the issuing bank deems a challenge is required, the function will inform you a challenge UI is required to be presented to the user.

Step 3: Present Authentication Challenge

The authentication challenge interface is rendered if required. Your user enters the code received from their card issuer.

Step 4: Handle 3DSecure Session result

The function output informs you whether the authentication was successful and you can proceed to payment with a 3DS Session ID.

Step 5: Make a payment

You proceed to creating the booking with the 3DS Session ID.

Caution

Integrating 3DSecure Session in your checkout page

This section outlines the actions required to integrate the process described above into your checkout page:

Step 2: Initiate 3DSecure Session

Using the createThreeDSecureSession function provided in @duffel/components.
To create the 3DS Session you will need a client key, resource information (i.e. its ID and any associated services), and a tokenised card ID you created using the [Card Form](link to section above).
If a 3DS challenge is required, the function will open a modal for the user to action the challenge.

Step 4: Handle 3DSecure Session result

The createThreeDSecureSession functions returns a promise which results in one of two states:
Authentication successful: The promise will resolve to a 3DS session object with the status ready_for_payment. The object will also include a three_d_secure_session_id which can then be used to pay for a booking.
Authentication failed: The promise will reject with an error or a 3DS session with a different status (the possible statuses can be found in the interface below). Any other status means the session is not ready for payment - either the user failed the challenge, or there was an error during the challenge, so the challenge should be retried. You can retry the authentication by calling the function again.

Step 5: Make a payment

To complete the users checkout and make the payment you pass the three_d_secure_session_id value in the payment object when creating an order, confirming an order change or paying for a hold order.

Function Interface

React

createThreeDSecureSession: (
clientKey: string, // The client key used to authenticate with the Duffel API.
cardId: string, // The card ID used for the 3DS session.
resourceId: string, // The resource (offer, order, order change) ID that the 3DS session is for.
services: Array<{ id: string; quantity: number }>, // Optional. Include all services that are being added, empty if no services are being added. This is required when services are also being purchased to ensure an accurate total amount to be authorised.
cardholderPresent: boolean // Whether the cardholder was present when the 3DS session was created. If you are collecting card details offline, for example an agent interface for entering card details received from the traveller over the phone, then you must specify the cardholder as not present
) => Promise<ThreeDSecureSession>
interface ThreeDSecureSession {
/**
* The card ID used for the 3DS session.
*/
id: string;
/**
* Whether the 3DS session was created in live mode. This field will be set to `true` if the card was created in live mode, or `false` if it was created in test mode.
*/
live_mode: boolean;
/**
* The resource (offer, order, order change..) ID that the 3DS session is for.
*/
resource_id: string;
/**
* Whether the cardholder was present when the 3DS session was created.
*/
cardholder_present: boolean;
/**
* The status of the 3DS session.
* - `client_action_required` - The 3DS session requires the UI Component to be initailised. This is the initial state when the payment is eligible for SCA and requires a 3DS challenge.
* - `ready_for_payment` - The 3DS session is ready to be used on a payment object as part of a order creation/payment request. This is the initial state if the card or the supplier does not support 3DS.
* - `failed` - The 3DS session was not authenticated to proceed with the payment. Payment should not be attempted. Cardholder should try again, possibly with a different card. Additionally, this is the initial state if the cardholder details are invalid.
* - `expired` - The 3DS session has expired. A new session should be created if needed.
*/
status: "client_action_required" | "ready_for_payment" | "expired" | "failed";
/**
* Used to initiate the UI component when `status` is `challenge_required`.
*/
client_id: string | null;
}

Example

In the below example, you can see how to use createThreeDSecureSession to get the three_d_secure_session_id required to create an order.
The prerequisites to working with this example are:
  • A component client key

  • A card ID

  • The resource ID you’d like to book, in this case an offer ID and its related services.

React

const clientKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiaWN1XzAwMDBBZ1ppdHBPblF0ZDNOUXhqd08iLCJvcmRlcl9pZCI6Im9yZF8wMDAwQUJkWm5nZ1NjdDdCb3JhVTFvIiwiaWF0IjoxNTE2MjM5MDIyfQ.GtJDKrfum7aLlNaXmUj-RtQIbx0-Opwjdid0fiJk6DE' // See the Client Key section above in the document
const cardId = 'tcd_00009hthhsUZ8W4LxQgkjb' // You can use the Duffel card component or API to get the card ID.
const offerId = 'off_0000AJyeTUCEoY5PhVPN8k_0'
const offerServices = [{id: 'ase_00009UhD4ongolulWAAA1A', quantity: 1}] // This can be bags or seats when booking flights
const threeDSecureSession = createThreeDSecureSession(
clientKey,
cardId,
offerId,
offerServices,
true,
)
.then(async (threeDSecureSession) => {
if (threeDSecureSession.status === 'ready_for_payment') {
createOrder({
... // plus passenger and other order creation information
selected_offers: [offerId],
services: offerServices,
payments: [
{
type: 'card',
currency: offerCurrency,
amount: offerAmount,
three_d_secure_session_id: threeDSecureSession.id,
},
]})
} else {
console.warn('3DS session status is not ready_for_payment, please try again', {
threeDSecureSession,
});
}
})
.catch((error) => {
console.error('Error creating 3DS session', error);
});

Testing your integration

In test mode we provide test card details to trigger different outcomes. These test details can be found in our Paying with customer cards integration guide.