broadleaf:
cartoperation:
service:
checkout:
checkout-lookup-3ds-transactions-enabled:
MY_FATOORAH: true
From the 2.1.1-GA
ReleaseTrain version, the pattern to execute the 3DS interaction was improved. See the Improved 3DS/HPP pattern integration example for more information.
Before getting started…
Familiarize yourself with Broadleaf’s 3DS patterns.
Familiarize yourself with the MyFatoorah Embedded Payment pattern & webhooks.
When executing an Authorize or AuthorizeAndCapture transaction with the Embedded Payment pattern, MyFatoorah will always prompt for 3DS verification.
This means that when using MyFatoorah, you can expect the initial attempt to checkout to return a CheckoutResponse#failureType
of PAYMENT_REQUIRES_3DS_VERIFICATION
& the related payment transaction failure detail (CheckoutResponse#paymentTransactionFailureDetails
) will have a failureType
of PAYMENT_REQUIRES_3DS_VERIFICATION
& a populated threeDSecureVerificationUrl
. The user’s browser should be redirected to this url to complete the verification process.
Once the verification process is completed, the checkout attempt can be resubmitted where we’ll attempt to lookup the 3DS transaction results & continue the checkout process if the transaction was successful. Simultaneously, we also leverage webhook events as a secondary means of gathering & recording transaction results.
Note
|
MyFatoorah does not support webhook notifications for Authorize transactions. Due to the risk of the Broadleaf ecosystem not being aware of transaction results, we suggest using AuthorizeAndCapture transactions. By default, MyFatoorah is configured to use separate Authorize and Capture interactions (MyFatoorah docs here). If you wish to enable AuthorizeAndCapture transactions (i.e. Authorize and Capture in a single request), then you must request this configuration via the MyFatoorah team. Keep in mind that if this change is made, you won’t be able to execute Authorize transactions. |
As mentioned in the Broadleaf 3DS docs, one of the primary ways for understanding transaction results after 3DS verification is successfully completed.
To enable this transaction lookup in the checkout workflow, add the following required property:
broadleaf:
cartoperation:
service:
checkout:
checkout-lookup-3ds-transactions-enabled:
MY_FATOORAH: true
When the customer finishes the authentication process, the redirect sends them back to the declared return url.
An example of how to build this return URL from your frontend application:
const protocol = window.location.protocol.slice(0, -1);
const host = window.location.host;
const callBackUrl = `${protocol}://${host}/checkout/3ds-verification?gatewayType=MY_FATOORAH`
const errorUrl = `${protocol}://${host}/checkout/3ds-verification?3ds-verification-success=false`
The return & error urls should be declared when creating the payment in PaymentTransactionServices, using the callBackUrl
& errorUrl
keys in the paymentMethodProperties
map.
{
paymentMethodProperties: {
...
callBackUrl: "https://{your_host}/checkout/3ds-verification?gatewayType=MY_FATOORAH",
errorUrl: "https://{your_host}/checkout/3ds-verification?3ds-verification-success=false",
...
}
}
Note
|
In our demo application, Heat Clinic, the frontend application handles the request to this return url, so that it becomes aware of the verification result & can progress to the next step in the checkout process. We recommend creating a separate page to check the 3DS verification result. In our example below, we use /checkout/3ds-verification page to do so.
|
When attempting a checkout, if you receive a CheckoutResponse with failureType = PAYMENT_REQUIRES_3DS_VERIFICATION
, then you’ll need to gather the threeDSecureVerificationUrl
from the CheckoutResponse#paymentTransactionFailureDetails
& redirect to the 3DS verification page.
Once the verification step is completed, the browser will be redirected back to the callBackUrl
specified in the paymentMethodProperties
. If the verification was successful, then we suggest automatically submitting checkout. If the verification was not successful, then we suggest providing a message to the customer highlighting the failure & requesting a different form of payment.
Note
|
The /checkout/3ds-verification page is used to check the status of the 3DS authentication.
|
import React, { FC, useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { isEmpty, set } from 'lodash';
import { useHandleTransactionResult } from '@broadleaf/payment-react';
const ThreeDSecureVerification: FC = () => {
const router = useRouter();
const { query } = router;
// the PaymentClient from '@broadleaf/commerce-cart'
const paymentClient = ...;
// the AuthState from '@broadleaf/payment-js'
const authState = ...;
const [transactionResult, setTransactionResult] =
useState<Record<string, unknown>>();
const { handleTransactionResult, error } = useHandleTransactionResult({
paymentClient,
authState,
});
const protocol = window.location.protocol.slice(0, -1);
const host = window.location.host;
const search = window.location.search;
// Redirect to this page if the 3DS authentication is successful and this page is not opened in the iframe
const verificationSuccessRedirectUrl = `${protocol}://${host}/checkout/review?3ds-verification-success=true`;
// Redirect to this page if the 3DS authentication is failed
const verificationFailureRedirectUrl = `${protocol}://${host}/checkout/review?3ds-verification-success=false`;
// The 3DS verification can be successful, however, the 3DS result transaction itself (AUTHORIZE) can fail
const transactionFailureRedirectUrl = `${protocol}://${host}/checkout/review?$3ds-transaction-success=false`;
const threeDSVerificationResult = query['3ds-verification-success'];
useEffect(() => {
if (threeDSVerificationResult !== 'false') {
const { gatewayType, ...request } = parseParams(search);
handleTransactionResult(gatewayType, requestParams).then(response => {
setTransactionResult(response);
});
} else {
redirectTo(verificationFailureRedirectUrl);
}
}, [
verificationFailureRedirectUrl,
handleTransactionResult,
router,
search,
threeDSVerificationResult,
]);
const transactionStatus =
transactionResult?.transactionExecutionDetails[0]?.transactionStatus;
if (
transactionResult &&
transactionStatus !== 'SUCCESS' &&
transactionStatus !== 'AWAITING_ASYNC_RESULTS'
) {
redirectTo(transactionFailureRedirectUrl);
}
if (error) {
/*
if there's an error, we'll still submit the order after the timeout to leverage other 3DS
verification fall back options, e.g. webhook & 3DS result lookup. The timeout is to ensure
webhooks would be processed by the time we re-submit the order
*/
console.error(
'Error encountered while verifying 3DS transaction result: ',
error
);
setTimeout(
() => {
// submit the order on the review page if '3ds-verification-success=true'
redirectTo(verificationSuccessRedirectUrl);
},
3000
);
}
if (
transactionStatus === 'SUCCESS' ||
transactionStatus === 'AWAITING_ASYNC_RESULTS'
) {
// submit the order on the review page if '3ds-verification-success=true'
redirectTo(verificationSuccessRedirectUrl);
}
// show any loader
return <PageLoader loading={true} />;
};
const parseParams = (search?: string): Record<string, never> => {
if (isEmpty(search)) {
return {};
}
search = search?.startsWith('?') ? search.slice(1) : search;
return search
?.split('&')
.map(param => {
// eslint-disable-next-line prefer-const
let [key, value] = param.split('=');
key = decodeURIComponent(key);
return { key, value };
})
.reduce((accumulator, { key, value }) => {
set(accumulator, key, value);
return accumulator;
}, {});
};
const redirectTo = redirectUrl => {
window.location.href = redirectUrl;
};
This page is used to review and submit the order. If the payment requires 3DS authentication, the submission will fail and the response will contain the bank’s page URL to verify 3DS. This URL can be used to redirect the customer.
import { FC, useEffect } from 'react';
import { useRouter } from 'next/router';
import { get, find } from 'lodash';
const CheckoutReview: FC = () => {
const router = useRouter();
const { query } = router;
// resolve cart
const { cart, resolving } = ...;
// useHandleSubmitCart - your hook implementation to submit the order
const { error, submitting, onSubmit } = useHandleSubmitCart();
const threeDSVerificationResult = query['3ds-verification-success'];
useEffect(() => {
if (threeDSVerificationResult === 'true') {
if (!resolving && cart) {
// submit the order if the 3DS verification result is successful
onSubmit();
}
if (error) {
// delete '3ds-verification-success' parameter from the URL
delete router.query['3ds-verification-success'];
router.replace(router, undefined, { shallow: true });
}
}
}, [threeDSVerificationResult, router, onSubmit, cart, error, resolving]);
useEffect(() => {
if ( get(error, 'failureType') === 'PAYMENT_REQUIRES_3DS_VERIFICATION') {
const threeDSDetails = find(
get(error, 'paymentTransactionFailureDetails'),
({ failureType }) => failureType === 'REQUIRES_3DS_VERIFICATION'
);
const verificationUrl = get(
threeDSDetails,
'threeDSecureVerificationUrl'
);
if (verificationUrl) {
// redirect to the 3DS verification page
router.push(verificationUrl);
}
}
}, [error, router]);
const threeDSVerificationFailedError =
router.query['3ds-verification-success'] === 'false'
? '3DS verification failed'
: undefined;
const threeDSTransactionFailedError =
router.query['3ds-transaction-success'] === 'false'
? 'We are unable to process your payment'
: undefined;
return (
<div>
{threeDSVerificationFailedError && (
<strong className="block my-4 text-red-600 text-lg font-normal">
{threeDSVerificationFailedError}
</strong>
)}
{threeDSTransactionFailedError && (
<strong className="block my-4 text-red-600 text-lg font-normal">
{threeDSTransactionFailedError}
</strong>
)}
...
<SubmitButton
id="submit-order-button"
disabled={submitting}
label='Submit Order'
onClick={onSubmit}
type="button"
/>
</div>
);
};
As part of the MyFatoorah 3DS integration, we listen to TransactionsStatusChanged
webhook events to gather transaction results once the customer verifies their ownership of the payment method.
For more information on how to configure these required webhooks in your environment, see the webhook setup notes in the environment setup guide.
Important
|
The TransactionsStatusChanged is only triggered for AuthorizeAndCapture transactions. When Authorize transactions are used during checkout, MyFatoorah doesn’t send a webhook event notifying us of the transaction results. Because of this, we recommend using AuthorizeAndCapture transactions during checkout.
|
Note
|
For more details on how Broadleaf makes use of webhooks as part of our overall 3DS pattern, take a look at our gateway-agnostic 3DS documentation. |