3DS Card Capture Integration
3DS Card Capture allows you to have 3DS Authentication performed prior to saving the card into the Customer Wallet for future use.
Steps involved in performing a card capture include:
- Create a 3DS enabled card capture action
- Perform initial card capture
- Check card capture response
3.1 If we require 3DS, the the action will fail with 3DS_001
3.1.1 Perform 3DS Card Capture
In order to provide the 3DS evidence to the tokenisation process, we need to capture 3DS
3.1.2 If 3DS capture was successful we will have a challenge response,
3.1.3 If 3DS capture was unsuccessful there is no point trying to tokenise again.
3.2 If we have anpaymentInstrumentId
then the capture was successful - Perform 3DS Card Capture
4.1 Control the visibility of the spinner and 3DS challenge response modal
4.2 Show the 3DS Card Capture challenge response
1. Create a 3DS enabled card capture action
To initialise a 3DS card capture for a non-frictionless card you specify a threeDS
option when creating the CaptureCard
action via the Frames SDK as shown in the code snippet below:
import * as FRAMES from '@wpay/frames';
const framesSDK = new FRAMES.FramesSDK(
{
apiKey: process.env.VUE_APP_API_KEY,
authToken: process.env.VUE_APP_ACCESS_TOKEN,
apiBase: `${process.env.VUE_APP_BASE_URL}/instore`,
logLevel: FRAMES.LogLevel.DEBUG,
},
);
const captureCardAction = framesSDK.createAction(
FRAMES.ActionTypes.CaptureCard,
{
verify: false,
save: true,
threeDS: {
requires3DS: true,
},
},
);
import * as FRAMES from '@wpay/frames';
const framesSDK = new FRAMES.FramesSDK(
{
apiKey: process.env.VUE_APP_API_KEY,
authToken: process.env.VUE_APP_ACCESS_TOKEN,
apiBase: `${process.env.VUE_APP_BASE_URL}/instore`,
logLevel: FRAMES.LogLevel.DEBUG,
},
);
const captureCardAction = framesSDK.createAction(
FRAMES.ActionTypes.CaptureCard,
({
verify: false,
save: true,
threeDS: {
requires3DS: true,
},
} as any),
) as CaptureCard;
2. Perform initial card capture
The card capture and iFrame validation of card fields follow the same steps as documented in our iFrames integration guide: Host the Frames
The difference is that the response from the cardCaptureAction.complete()
is tested to see whether a 3DS card capture is required:
<div class="container">
<!-- Card Capture iFrame place holder -->
<div id="cardGroupPlaceholder"></div>
</div
/* Card Capture iFram place holder styling */
#cardGroupPlaceholder {
border: 1px solid;
padding: 10px;
min-height: 22px;
margin-bottom: 15px;
}
await captureCardAction.start();
captureCardAction.createFramesControl('CardGroup', 'cardGroupPlaceholder');
// When the Card Caputure iFrame gets focus change the
// state variable paymentMethod to 'enterCardDetails'
document.getElementById('cardGroupPlaceholder') &&
document.getElementById('cardGroupPlaceholder').addEventListener(
FRAMES.FramesEventType.OnFocus, () => {
this.paymentMethod = 'enterCardDetails';
});
// Log any Card Capture Validation errors
document.getElementById('cardGroupPlaceholder') &&
document.getElementById('cardGroupPlaceholder').addEventListener(
FRAMES.FramesEventType.OnValidated, () => {
console.log(captureCardAction.errors());
});
// Submit the action
await captureCardAction.submit();
// Call complete for first attempt at tokenisation
let cardCaptureResponse = await captureCardAction.complete();
// Set the selected intrument and stepUp token from the inital card capture
let selectedInstrument =
(cardCaptureResponse.paymentInstrument && cardCaptureResponse.paymentInstrument.itemId) ||
cardCaptureResponse.itemId;
let stepUpToken = cardCaptureResponse.stepUpToken;
await captureCardAction.start();
captureCardAction.createFramesControl('CardGroup', 'cardGroupPlaceholder');
// When the Card Caputure iFrame gets focus change the
// state variable paymentMethod to 'enterCardDetails'
document.getElementById('cardGroupPlaceholder')?.addEventListener(
FRAMES.FramesEventType.OnFocus, () => {
this.paymentMethod = 'enterCardDetails';
});
// Log any Card Capture Validation errors
document.getElementById('cardGroupPlaceholder')?.addEventListener(
FRAMES.FramesEventType.OnValidated, () => {
console.log(captureCardAction.errors());
});
// Submit the action
await captureCardAction.submit();
// Call complete for first attempt at tokenisation
let cardCaptureResponse = await captureCardAction.complete();
// Set the selected intrument and stepUp token from the inital card capture
let selectedInstrument =
cardCaptureResponse.paymentInstrument?.itemId || cardCaptureResponse.itemId;
let stepUpToken = cardCaptureResponse.stepUpToken;
3. Check card capture response
// Intialise value of the card capture success return variable
let preconditionsMet = false;
// 3.1 If we require 3DS, the the action will fail with 3DS_001
if (cardCaptureResponse.errorCode === '3DS_001') {
// 3.1.1 Perform 3DS Card Capture
// In order to provide the 3DS evidence to the tokenisation process, we need to capture 3DS
const authorizationResponse =
await capture3DS(
cardCaptureResponse.token,
selectedInstrument,
FRAMES.ActionTypes.ValidateCard);
// 3.1.2 If 3DS capture was successful we will have a challenge response,
if (authorizationResponse.challengeResponse) {
cardCaptureResponse =
await captureCardAction.complete(
saveCard, [authorizationResponse.challengeResponse]);
preconditionsMet = true;
} else {
// 3.1.3 If 3DS capture was unsuccessful there is no point trying to tokenise again.
paymentStatus = 'Payment Failed - There was an issue during card capture';
}
} else if (cardCaptureResponse.itemId || cardCaptureResponse.paymentInstrument.itemId) {
// 3.2 If we have an paymentInstrumentId then the capture was successful
preconditionsMet = true;
}
// Intialise value of the card capture success return variable
let preconditionsMet = false;
// 3.1 If we require 3DS, the the action will fail with 3DS_001
if (cardCaptureResponse.errorCode === '3DS_001') {
// 3.1.1 Perform 3DS Card Capture
// In order to provide the 3DS evidence to the tokenisation process, we need to capture 3DS
const authorizationResponse =
await this.capture3DS(
cardCaptureResponse.token,
selectedInstrument,
FRAMES.ActionTypes.ValidateCard);
// 3.1.2 If 3DS capture was successful we will have a challenge response,
if (authorizationResponse.challengeResponse) {
cardCaptureResponse =
await captureCardAction.complete(
this.saveCard, [authorizationResponse.challengeResponse]);
preconditionsMet = true;
} else {
// 3.1.3 If 3DS capture was unsuccessful there is no point trying to tokenise again.
this.paymentStatus = 'Payment Failed - There was an issue during card capture';
}
} else if (cardCaptureResponse.itemId || cardCaptureResponse.paymentInstrument.itemId) {
// 3.2 If we have an paymentInstrumentId then the capture was successful
preconditionsMet = true;
}
Where:
cardCaptureResponse.errorCode
has values as defined in the section 3DS error codessaveCard
is a boolean value oftrue
to save the card,false
to not save the card orundefined
if the we want to use the value set when initialising the card caption action
4. Perform 3DS Card Capture
4.1 Control the visibility of the spinner and 3DS challenge response modal
Controlling the visibility of the 3DS Card Capture Challenge Response Model. After validation of the card entered by the user the showSpinner
is set to try showing the spinner.
<div class="container">
<!--
3DS iFrame Challenge response placeholer.
Includes a Vue.js class which controls the visible of this model,
based on the value of the show3DS variable.
-->
<div
id="overlay"
class="overlay"
style=""
v-bind:class="{ hidden: !this.show3DS }">
</div>
<!-- Card Capture iFrame place holder -->
<div id="cardGroupPlaceholder"></div>
<!--
Display the Spinner component, when
the paymentDisabled variable is true or
the showSpinner variable is true.
-->
<div class="processing"
v-bind:class="{ hidden: !this.paymentDisabled || this.showSpinner === false }">
<Spinner/>
</div>
</div>
/*
You provide can style the 3DS Challenge reponse iframe model
with custom css as shown below:
*/
.overlay {
position: fixed; top: 0; bottom: 0; left: 0; right: 0; background: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
z-index: 1;
justify-content: center;
}
.overlay iframe {
background: white;
padding: 5px;
border-radius: 5px;
border: 1px solid black;
}
/* Positioning the Spinner component. */
.processing {
text-align: center;
}
this.showSpinner = true;
private async capture3DS(sessionId, paymentInstrumentId, actionType) {
const enrollmentRequest = {
sessionId,
paymentInstrumentId,
threeDS: {
consumerAuthenticationInformation: {
acsWindowSize: settings.customer.acsWindowSize,
},
},
};
// Create a new payment validation frames action
const action = framesSDK.createAction(actionType, enrollmentRequest);
// Start the action, creating a new JWT and initialising cardinal
await action.start();
// Set the placeholder for the challenge IFrame to be injected
action.createFramesControl('3DSValidation', 'overlay');
const elementHandle = document.getElementById('overlay');
const renderEventListener = () => {
this.showSpinner = false;
this.show3DS = true;
};
const closeEventListener = () => {
this.showSpinner = true;
this.show3DS = false;
};
// Add the event listeners for OnRender and OnClose for the 3DS challenge response
elementHandle.addEventListener(FRAMES.FramesCardinalEventType.OnRender, renderEventListener);
elementHandle.addEventListener(FRAMES.FramesCardinalEventType.OnClose, closeEventListener);
// Check card enrolment, allowing cardinal show issuer challenge
const authorizationResponse = await action.complete();
// Romove the event listeners for OnRender and OnClose for the 3DS challenge response
elementHandle.removeEventListener(FRAMES.FramesCardinalEventType.OnRender, renderEventListener);
elementHandle.removeEventListener(FRAMES.FramesCardinalEventType.OnClose, closeEventListener);
// 3DS check complete, use returned information to provide a challenge response within the payment endpoint
console.log(`3DS authorization complete: ${JSON.stringify(authorizationResponse)}`);
if (actionType === FRAMES.ActionTypes.ValidatePayment) {
paymentAuthentication = authorizationResponse;
} else if (actionType === FRAMES.ActionTypes.ValidateCard) {
cardValidation = authorizationResponse;
}
return authorizationResponse;
}
this.showSpinner = true;
private async capture3DS(sessionId: string, paymentInstrumentId: string, actionType: symbol) {
const enrollmentRequest: any = {
sessionId,
paymentInstrumentId,
threeDS: {
consumerAuthenticationInformation: {
acsWindowSize: this.settings.customer.acsWindowSize,
},
},
};
// Create a new payment validation frames action
const action = framesSDK.createAction(actionType, enrollmentRequest) as ValidatePayment;
// Start the action, creating a new JWT and initialising cardinal
await action.start();
// Set the placeholder for the challenge IFrame to be injected
action.createFramesControl('3DSValidation', 'overlay');
const elementHandle = document.getElementById('overlay') as HTMLElement;
const renderEventListener = () => {
this.showSpinner = false;
this.show3DS = true;
};
const closeEventListener = () => {
this.showSpinner = true;
this.show3DS = false;
};
// Add the event listeners for OnRender and OnClose for the 3DS challenge response
elementHandle.addEventListener(FRAMES.FramesCardinalEventType.OnRender, renderEventListener);
elementHandle.addEventListener(FRAMES.FramesCardinalEventType.OnClose, closeEventListener);
// Check card enrolment, allowing cardinal show issuer challenge
const authorizationResponse = await action.complete();
// Romove the event listeners for OnRender and OnClose for the 3DS challenge response
elementHandle.removeEventListener(FRAMES.FramesCardinalEventType.OnRender, renderEventListener);
elementHandle.removeEventListener(FRAMES.FramesCardinalEventType.OnClose, closeEventListener);
// 3DS check complete, use returned information to provide a challenge response within the payment endpoint
console.log(`3DS authorization complete: ${JSON.stringify(authorizationResponse)}`);
if (actionType === FRAMES.ActionTypes.ValidatePayment) {
paymentAuthentication = authorizationResponse;
} else if (actionType === FRAMES.ActionTypes.ValidateCard) {
cardValidation = authorizationResponse;
}
return authorizationResponse;
}
where:
acsWindowSize
has values defined in What are the 3DS Model challenge response screen size values ?
4.2 Show the 3DS Card Capture challenge response modal
When action.complete
is called and the renderEventListener
is fired, causing:
- the
showSpinner
to be set tofalse
, causing this spinner to be hidden and - the
show3DS
variable will be set totrue
, causing the Challenge Response Modal to be displayed like the one shown below:

Sample 3DS2 OTP screen for card capture (hence the $0.00)
Once the challenge is validated (in the above example this means the one time password that has been texted to the customer is entered and submitted) , the closeEventListener
is fired, setting the show3DS
variable to false
hiding the challenge response iFrame.
Additional Information
- A set of 3DS specific test cards are available to validate the full range of results available from issuers
- The list of 3DS error codes are listed here
- 3DS information (e.g. frame sizing) is included on our FAQs
Updated over 1 year ago