Card Form Component with 3DSecure
Overview
Getting Started
Installing
npm i @duffel/components# -- or --yarn add @duffel/components
Client key
Card Form Component
See it on GitHub →
You can find the source code and release notes for this component on GitHub.
See it on npm →
This component is available to be installed with npm.
Demo
Use cases
DuffelCardForm
component supports three checkout flows:Using a card for single transaction
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
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
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
intent
property:Use case | Intent value |
---|---|
Using a card for single transaction | to-create-card-for-temporary-use |
Saving a card for multiple future transactions | to-save-card |
Using a saved card for a single transaction | to-use-saved-card |
Actions
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:function YourComponent() {const { ref, saveCard, createCardForTemporaryUse } = useDuffelCardFormActions()return <><DuffelCardForm ref={ref} ... /><button onClick={saveCard}>Save</button><button onClick={createCardForTemporaryUse}>Pay</button></>}
Event handlers
Form validation
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
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.onSaveCardSuccess
: Returns theCard
record.onSaveCardFailure
: Returns information about the error.
Calling createCardForTemporaryUse
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.onCreateCardForTemporaryUseSuccess
: Returns theCard
record.onCreateCardForTemporaryUseFailure
: Return information about the error.
Tip
Saving and using card during checkout
saveCard
and createCardForTemporaryUse
intents in conjunction. Actions work independently and can be triggered asynchronously based on different events you may capture on your UI.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.saveCard
along with createCardForTemporaryUse
when the form submitted and with confirmation from the user that they’d like their card to be saved.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 typesexport interface CreateCardForTemporaryUseData {id: stringlive_mode: boolean}export type CreateCardForTemporaryUseError = {status: numbermessage: string}export type SaveCardError = {status: numbermessage: 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
/*** 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?: StylesMaphover?: StylesMapactive?: StylesMapfocus?: StylesMap}export interface DuffelCardFormStyles {input?: InteractiveElementStylesselect?: InteractiveElementStyleslabel?: StylesMapinputErrorMessage?: StylesMapsectionTitle?: StylesMaplayoutGrid?: StylesMap}
3DSecure Session function
Example of a 3DSecure challenge
Use Cases
createThreeDSecureSession
to create and render the challenge.Step 1: Collect the customers card details
Step 2: Initiate 3DSecure Session
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
Step 4: Handle 3DSecure Session result
Step 5: Make a payment
Caution
Integrating 3DSecure Session in your checkout page
Step 2: Initiate 3DSecure Session
createThreeDSecureSession
function provided in @duffel/components
.Step 4: Handle 3DSecure Session result
createThreeDSecureSession
functions returns a promise which results in one of two states:ready_for_payment
. The object will also include a three_d_secure_session_id
which can then be used to pay for a booking.Step 5: Make a payment
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
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
createThreeDSecureSession
to get the three_d_secure_session_id
required to create an order.A card ID
The resource ID you’d like to book, in this case an offer ID and its related services.
const clientKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiaWN1XzAwMDBBZ1ppdHBPblF0ZDNOUXhqd08iLCJvcmRlcl9pZCI6Im9yZF8wMDAwQUJkWm5nZ1NjdDdCb3JhVTFvIiwiaWF0IjoxNTE2MjM5MDIyfQ.GtJDKrfum7aLlNaXmUj-RtQIbx0-Opwjdid0fiJk6DE' // See the Client Key section above in the documentconst 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 flightsconst threeDSecureSession = createThreeDSecureSession(clientKey,cardId,offerId,offerServices,true,).then(async (threeDSecureSession) => {if (threeDSecureSession.status === 'ready_for_payment') {createOrder({... // plus passenger and other order creation informationselected_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);});