Bug: [FEATURE] [Bankofamerica] Add 3DS flow for cards



### Feature Description

Add 3DS flow for cards for connector BankOfAmerica.

### Possible Implementation

Add 3DS flow for cards for connector BankOfAmerica.

### Have you spent some time checking if this feature request has been raised before?

- [X] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [X] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/config/config.example.toml b/config/config.example.toml
index e20f9c1b65d..d4e11964192 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -351,6 +351,7 @@ stripe = { payment_method = "bank_transfer" }
 nuvei = { payment_method = "card" }
 shift4 = { payment_method = "card" }
 bluesnap = { payment_method = "card" }
+bankofamerica = {payment_method = "card"}
 cybersource = {payment_method = "card"}
 nmi = {payment_method = "card"}
 
diff --git a/config/development.toml b/config/development.toml
index 5732d5f0d1d..91269005a0f 100644
--- a/config/development.toml
+++ b/config/development.toml
@@ -428,6 +428,7 @@ stripe = {payment_method = "bank_transfer"}
 nuvei = {payment_method = "card"}
 shift4 = {payment_method = "card"}
 bluesnap = {payment_method = "card"}
+bankofamerica = {payment_method = "card"}
 cybersource = {payment_method = "card"}
 nmi = {payment_method = "card"}
 
diff --git a/config/docker_compose.toml b/config/docker_compose.toml
index c6934a64671..450fe106a31 100644
--- a/config/docker_compose.toml
+++ b/config/docker_compose.toml
@@ -241,6 +241,7 @@ stripe = {payment_method = "bank_transfer"}
 nuvei = {payment_method = "card"}
 shift4 = {payment_method = "card"}
 bluesnap = {payment_method = "card"}
+bankofamerica = {payment_method = "card"}
 cybersource = {payment_method = "card"}
 nmi = {payment_method = "card"}
 
diff --git a/crates/router/src/connector/bankofamerica.rs b/crates/router/src/connector/bankofamerica.rs
index aeb3dafcfa2..0d901b99078 100644
--- a/crates/router/src/connector/bankofamerica.rs
+++ b/crates/router/src/connector/bankofamerica.rs
@@ -12,6 +12,7 @@ use time::OffsetDateTime;
 use transformers as bankofamerica;
 use url::Url;
 
+use super::utils::{PaymentsAuthorizeRequestData, RouterData};
 use crate::{
     configs::settings,
     connector::{utils as connector_utils, utils::RefundsRequestData},
@@ -48,6 +49,8 @@ impl api::Refund for Bankofamerica {}
 impl api::RefundExecute for Bankofamerica {}
 impl api::RefundSync for Bankofamerica {}
 impl api::PaymentToken for Bankofamerica {}
+impl api::PaymentsPreProcessing for Bankofamerica {}
+impl api::PaymentsCompleteAuthorize for Bankofamerica {}
 
 impl Bankofamerica {
     pub fn generate_digest(&self, payload: &[u8]) -> String {
@@ -299,6 +302,113 @@ impl
     }
 }
 
+impl
+    ConnectorIntegration<
+        api::PreProcessing,
+        types::PaymentsPreProcessingData,
+        types::PaymentsResponseData,
+    > for Bankofamerica
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsPreProcessingRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+    fn get_url(
+        &self,
+        req: &types::PaymentsPreProcessingRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        let redirect_response = req.request.redirect_response.clone().ok_or(
+            errors::ConnectorError::MissingRequiredField {
+                field_name: "redirect_response",
+            },
+        )?;
+        match redirect_response.params {
+            Some(param) if !param.clone().peek().is_empty() => Ok(format!(
+                "{}risk/v1/authentications",
+                self.base_url(connectors)
+            )),
+            Some(_) | None => Ok(format!(
+                "{}risk/v1/authentication-results",
+                self.base_url(connectors)
+            )),
+        }
+    }
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsPreProcessingRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<RequestContent, errors::ConnectorError> {
+        let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from((
+            &self.get_currency_unit(),
+            req.request
+                .currency
+                .ok_or(errors::ConnectorError::MissingRequiredField {
+                    field_name: "currency",
+                })?,
+            req.request
+                .amount
+                .ok_or(errors::ConnectorError::MissingRequiredField {
+                    field_name: "amount",
+                })?,
+            req,
+        ))?;
+        let connector_req =
+            bankofamerica::BankOfAmericaPreProcessingRequest::try_from(&connector_router_data)?;
+        Ok(RequestContent::Json(Box::new(connector_req)))
+    }
+    fn build_request(
+        &self,
+        req: &types::PaymentsPreProcessingRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Post)
+                .url(&types::PaymentsPreProcessingType::get_url(
+                    self, req, connectors,
+                )?)
+                .attach_default_headers()
+                .headers(types::PaymentsPreProcessingType::get_headers(
+                    self, req, connectors,
+                )?)
+                .set_body(types::PaymentsPreProcessingType::get_request_body(
+                    self, req, connectors,
+                )?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsPreProcessingRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsPreProcessingRouterData, errors::ConnectorError> {
+        let response: bankofamerica::BankOfAmericaPreProcessingResponse = res
+            .response
+            .parse_struct("BankOfAmerica AuthEnrollmentResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+    }
+
+    fn get_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
 impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
     for Bankofamerica
 {
@@ -316,13 +426,17 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
 
     fn get_url(
         &self,
-        _req: &types::PaymentsAuthorizeRouterData,
+        req: &types::PaymentsAuthorizeRouterData,
         connectors: &settings::Connectors,
     ) -> CustomResult<String, errors::ConnectorError> {
-        Ok(format!(
-            "{}pts/v2/payments/",
-            api::ConnectorCommon::base_url(self, connectors)
-        ))
+        if req.is_three_ds() && req.request.is_card() {
+            Ok(format!(
+                "{}risk/v1/authentication-setups",
+                self.base_url(connectors)
+            ))
+        } else {
+            Ok(format!("{}pts/v2/payments/", self.base_url(connectors)))
+        }
     }
 
     fn get_request_body(
@@ -336,9 +450,15 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
             req.request.amount,
             req,
         ))?;
-        let connector_req =
-            bankofamerica::BankOfAmericaPaymentsRequest::try_from(&connector_router_data)?;
-        Ok(RequestContent::Json(Box::new(connector_req)))
+        if req.is_three_ds() && req.request.is_card() {
+            let connector_req =
+                bankofamerica::BankOfAmericaAuthSetupRequest::try_from(&connector_router_data)?;
+            Ok(RequestContent::Json(Box::new(connector_req)))
+        } else {
+            let connector_req =
+                bankofamerica::BankOfAmericaPaymentsRequest::try_from(&connector_router_data)?;
+            Ok(RequestContent::Json(Box::new(connector_req)))
+        }
     }
 
     fn build_request(
@@ -368,6 +488,130 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
         data: &types::PaymentsAuthorizeRouterData,
         res: Response,
     ) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
+        if data.is_three_ds() && data.request.is_card() {
+            let response: bankofamerica::BankOfAmericaAuthSetupResponse = res
+                .response
+                .parse_struct("Bankofamerica AuthSetupResponse")
+                .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+            types::RouterData::try_from(types::ResponseRouterData {
+                response,
+                data: data.clone(),
+                http_code: res.status_code,
+            })
+        } else {
+            let response: bankofamerica::BankOfAmericaPaymentsResponse = res
+                .response
+                .parse_struct("Bankofamerica PaymentResponse")
+                .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+            types::RouterData::try_from(types::ResponseRouterData {
+                response,
+                data: data.clone(),
+                http_code: res.status_code,
+            })
+        }
+    }
+
+    fn get_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+
+    fn get_5xx_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        let response: bankofamerica::BankOfAmericaServerErrorResponse = res
+            .response
+            .parse_struct("BankOfAmericaServerErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        let attempt_status = match response.reason {
+            Some(reason) => match reason {
+                transformers::Reason::SystemError => Some(enums::AttemptStatus::Failure),
+                transformers::Reason::ServerTimeout | transformers::Reason::ServiceTimeout => None,
+            },
+            None => None,
+        };
+        Ok(ErrorResponse {
+            status_code: res.status_code,
+            reason: response.status.clone(),
+            code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()),
+            message: response
+                .message
+                .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
+            attempt_status,
+            connector_transaction_id: None,
+        })
+    }
+}
+
+impl
+    ConnectorIntegration<
+        api::CompleteAuthorize,
+        types::CompleteAuthorizeData,
+        types::PaymentsResponseData,
+    > for Bankofamerica
+{
+    fn get_headers(
+        &self,
+        req: &types::PaymentsCompleteAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+    fn get_url(
+        &self,
+        _req: &types::PaymentsCompleteAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!("{}pts/v2/payments/", self.base_url(connectors)))
+    }
+    fn get_request_body(
+        &self,
+        req: &types::PaymentsCompleteAuthorizeRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<RequestContent, errors::ConnectorError> {
+        let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from((
+            &self.get_currency_unit(),
+            req.request.currency,
+            req.request.amount,
+            req,
+        ))?;
+        let connector_req =
+            bankofamerica::BankOfAmericaPaymentsRequest::try_from(&connector_router_data)?;
+        Ok(RequestContent::Json(Box::new(connector_req)))
+    }
+    fn build_request(
+        &self,
+        req: &types::PaymentsCompleteAuthorizeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Post)
+                .url(&types::PaymentsCompleteAuthorizeType::get_url(
+                    self, req, connectors,
+                )?)
+                .attach_default_headers()
+                .headers(types::PaymentsCompleteAuthorizeType::get_headers(
+                    self, req, connectors,
+                )?)
+                .set_body(types::PaymentsCompleteAuthorizeType::get_request_body(
+                    self, req, connectors,
+                )?)
+                .build(),
+        ))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::PaymentsCompleteAuthorizeRouterData,
+        res: Response,
+    ) -> CustomResult<types::PaymentsCompleteAuthorizeRouterData, errors::ConnectorError> {
         let response: bankofamerica::BankOfAmericaPaymentsResponse = res
             .response
             .parse_struct("BankOfAmerica PaymentResponse")
diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs
index 6abe1b634df..72e3de0bf77 100644
--- a/crates/router/src/connector/bankofamerica/transformers.rs
+++ b/crates/router/src/connector/bankofamerica/transformers.rs
@@ -1,6 +1,7 @@
 use api_models::payments;
 use base64::Engine;
-use common_utils::pii;
+use common_utils::{ext_traits::ValueExt, pii};
+use error_stack::{IntoReport, ResultExt};
 use masking::{PeekInterface, Secret};
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
@@ -8,10 +9,12 @@ use serde_json::Value;
 use crate::{
     connector::utils::{
         self, AddressDetailsData, ApplePayDecrypt, CardData, CardIssuer,
-        PaymentsAuthorizeRequestData, PaymentsSyncRequestData, RouterData,
+        PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData,
+        PaymentsPreProcessingData, PaymentsSyncRequestData, RouterData,
     },
     consts,
     core::errors,
+    services,
     types::{
         self,
         api::{self, enums as api_enums},
@@ -85,14 +88,17 @@ pub struct BankOfAmericaPaymentsRequest {
     order_information: OrderInformationWithBill,
     client_reference_information: ClientReferenceInformation,
     #[serde(skip_serializing_if = "Option::is_none")]
+    consumer_authentication_information: Option<BankOfAmericaConsumerAuthInformation>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     merchant_defined_information: Option<Vec<MerchantDefinedInformation>>,
 }
 
 #[derive(Debug, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct ProcessingInformation {
-    capture: bool,
+    capture: Option<bool>,
     payment_solution: Option<String>,
+    commerce_indicator: String,
 }
 
 #[derive(Debug, Serialize)]
@@ -102,6 +108,17 @@ pub struct MerchantDefinedInformation {
     value: String,
 }
 
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BankOfAmericaConsumerAuthInformation {
+    ucaf_collection_indicator: Option<String>,
+    cavv: Option<String>,
+    ucaf_authentication_data: Option<String>,
+    xid: Option<String>,
+    directory_server_transaction_id: Option<String>,
+    specification_version: Option<String>,
+}
+
 #[derive(Debug, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct CaptureOptions {
@@ -287,6 +304,28 @@ impl
     }
 }
 
+impl
+    From<(
+        &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
+        BillTo,
+    )> for OrderInformationWithBill
+{
+    fn from(
+        (item, bill_to): (
+            &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
+            BillTo,
+        ),
+    ) -> Self {
+        Self {
+            amount_details: Amount {
+                total_amount: item.amount.to_owned(),
+                currency: item.router_data.request.currency,
+            },
+            bill_to,
+        }
+    }
+}
+
 impl
     From<(
         &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>,
@@ -300,11 +339,40 @@ impl
         ),
     ) -> Self {
         Self {
-            capture: matches!(
+            capture: Some(matches!(
                 item.router_data.request.capture_method,
                 Some(enums::CaptureMethod::Automatic) | None
-            ),
+            )),
             payment_solution: solution.map(String::from),
+            commerce_indicator: String::from("internet"),
+        }
+    }
+}
+
+impl
+    From<(
+        &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
+        Option<PaymentSolution>,
+        &BankOfAmericaConsumerAuthValidateResponse,
+    )> for ProcessingInformation
+{
+    fn from(
+        (item, solution, three_ds_data): (
+            &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
+            Option<PaymentSolution>,
+            &BankOfAmericaConsumerAuthValidateResponse,
+        ),
+    ) -> Self {
+        Self {
+            capture: Some(matches!(
+                item.router_data.request.capture_method,
+                Some(enums::CaptureMethod::Automatic) | None
+            )),
+            payment_solution: solution.map(String::from),
+            commerce_indicator: three_ds_data
+                .indicator
+                .to_owned()
+                .unwrap_or(String::from("internet")),
         }
     }
 }
@@ -319,6 +387,16 @@ impl From<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>>
     }
 }
 
+impl From<&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>>
+    for ClientReferenceInformation
+{
+    fn from(item: &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>) -> Self {
+        Self {
+            code: Some(item.router_data.connector_request_reference_id.clone()),
+        }
+    }
+}
+
 impl ForeignFrom<Value> for Vec<MerchantDefinedInformation> {
     fn foreign_from(metadata: Value) -> Self {
         let hashmap: std::collections::BTreeMap<String, Value> =
@@ -367,6 +445,83 @@ pub struct Avs {
     code_raw: String,
 }
 
+impl
+    TryFrom<(
+        &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
+        payments::Card,
+    )> for BankOfAmericaPaymentsRequest
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        (item, ccard): (
+            &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
+            payments::Card,
+        ),
+    ) -> Result<Self, Self::Error> {
+        let email = item.router_data.request.get_email()?;
+        let bill_to = build_bill_to(item.router_data.get_billing()?, email)?;
+        let order_information = OrderInformationWithBill::from((item, bill_to));
+
+        let card_issuer = ccard.get_card_issuer();
+        let card_type = match card_issuer {
+            Ok(issuer) => Some(String::from(issuer)),
+            Err(_) => None,
+        };
+
+        let payment_information = PaymentInformation::Cards(CardPaymentInformation {
+            card: Card {
+                number: ccard.card_number,
+                expiration_month: ccard.card_exp_month,
+                expiration_year: ccard.card_exp_year,
+                security_code: ccard.card_cvc,
+                card_type,
+            },
+        });
+        let client_reference_information = ClientReferenceInformation::from(item);
+
+        let three_ds_info: BankOfAmericaThreeDSMetadata = item
+            .router_data
+            .request
+            .connector_meta
+            .clone()
+            .ok_or(errors::ConnectorError::MissingRequiredField {
+                field_name: "connector_meta",
+            })?
+            .parse_value("BankOfAmericaThreeDSMetadata")
+            .change_context(errors::ConnectorError::InvalidConnectorConfig {
+                config: "Merchant connector account metadata",
+            })?;
+
+        let processing_information =
+            ProcessingInformation::from((item, None, &three_ds_info.three_ds_data));
+
+        let consumer_authentication_information = Some(BankOfAmericaConsumerAuthInformation {
+            ucaf_collection_indicator: three_ds_info.three_ds_data.ucaf_collection_indicator,
+            cavv: three_ds_info.three_ds_data.cavv,
+            ucaf_authentication_data: three_ds_info.three_ds_data.ucaf_authentication_data,
+            xid: three_ds_info.three_ds_data.xid,
+            directory_server_transaction_id: three_ds_info
+                .three_ds_data
+                .directory_server_transaction_id,
+            specification_version: three_ds_info.three_ds_data.specification_version,
+        });
+
+        let merchant_defined_information =
+            item.router_data.request.metadata.clone().map(|metadata| {
+                Vec::<MerchantDefinedInformation>::foreign_from(metadata.peek().to_owned())
+            });
+
+        Ok(Self {
+            processing_information,
+            payment_information,
+            order_information,
+            client_reference_information,
+            consumer_authentication_information,
+            merchant_defined_information,
+        })
+    }
+}
+
 impl
     TryFrom<(
         &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>,
@@ -410,6 +565,7 @@ impl
             order_information,
             client_reference_information,
             merchant_defined_information,
+            consumer_authentication_information: None,
         })
     }
 }
@@ -455,6 +611,7 @@ impl
             order_information,
             client_reference_information,
             merchant_defined_information,
+            consumer_authentication_information: None,
         })
     }
 }
@@ -496,6 +653,7 @@ impl
             order_information,
             client_reference_information,
             merchant_defined_information,
+            consumer_authentication_information: None,
         })
     }
 }
@@ -552,6 +710,7 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>>
                                 order_information,
                                 merchant_defined_information,
                                 client_reference_information,
+                                consumer_authentication_information: None,
                             })
                         }
                     }
@@ -608,6 +767,64 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>>
     }
 }
 
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BankOfAmericaAuthSetupRequest {
+    payment_information: PaymentInformation,
+    client_reference_information: ClientReferenceInformation,
+}
+
+impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>>
+    for BankOfAmericaAuthSetupRequest
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>,
+    ) -> Result<Self, Self::Error> {
+        match item.router_data.request.payment_method_data.clone() {
+            payments::PaymentMethodData::Card(ccard) => {
+                let card_issuer = ccard.get_card_issuer();
+                let card_type = match card_issuer {
+                    Ok(issuer) => Some(String::from(issuer)),
+                    Err(_) => None,
+                };
+                let payment_information = PaymentInformation::Cards(CardPaymentInformation {
+                    card: Card {
+                        number: ccard.card_number,
+                        expiration_month: ccard.card_exp_month,
+                        expiration_year: ccard.card_exp_year,
+                        security_code: ccard.card_cvc,
+                        card_type,
+                    },
+                });
+                let client_reference_information = ClientReferenceInformation::from(item);
+                Ok(Self {
+                    payment_information,
+                    client_reference_information,
+                })
+            }
+            payments::PaymentMethodData::Wallet(_)
+            | payments::PaymentMethodData::CardRedirect(_)
+            | payments::PaymentMethodData::PayLater(_)
+            | payments::PaymentMethodData::BankRedirect(_)
+            | payments::PaymentMethodData::BankDebit(_)
+            | payments::PaymentMethodData::BankTransfer(_)
+            | payments::PaymentMethodData::Crypto(_)
+            | payments::PaymentMethodData::MandatePayment
+            | payments::PaymentMethodData::Reward
+            | payments::PaymentMethodData::Upi(_)
+            | payments::PaymentMethodData::Voucher(_)
+            | payments::PaymentMethodData::GiftCard(_)
+            | payments::PaymentMethodData::CardToken(_) => {
+                Err(errors::ConnectorError::NotImplemented(
+                    utils::get_unimplemented_payment_method_error_message("BankOfAmerica"),
+                )
+                .into())
+            }
+        }
+    }
+}
+
 #[derive(Clone, Debug, Deserialize)]
 #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
 pub enum BankofamericaPaymentStatus {
@@ -669,6 +886,29 @@ impl ForeignFrom<(BankofamericaPaymentStatus, bool)> for enums::AttemptStatus {
     }
 }
 
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BankOfAmericaConsumerAuthInformationResponse {
+    access_token: String,
+    device_data_collection_url: String,
+    reference_id: String,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ClientAuthSetupInfoResponse {
+    id: String,
+    client_reference_information: ClientReferenceInformation,
+    consumer_authentication_information: BankOfAmericaConsumerAuthInformationResponse,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(untagged)]
+pub enum BankOfAmericaAuthSetupResponse {
+    ClientAuthSetupInfo(ClientAuthSetupInfoResponse),
+    ErrorInformation(BankOfAmericaErrorInformationResponse),
+}
+
 #[derive(Debug, Deserialize)]
 #[serde(untagged)]
 pub enum BankOfAmericaPaymentsResponse {
@@ -799,6 +1039,494 @@ fn get_payment_response(
     }
 }
 
+impl<F>
+    TryFrom<
+        types::ResponseRouterData<
+            F,
+            BankOfAmericaAuthSetupResponse,
+            types::PaymentsAuthorizeData,
+            types::PaymentsResponseData,
+        >,
+    > for types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::ResponseRouterData<
+            F,
+            BankOfAmericaAuthSetupResponse,
+            types::PaymentsAuthorizeData,
+            types::PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        match item.response {
+            BankOfAmericaAuthSetupResponse::ClientAuthSetupInfo(info_response) => Ok(Self {
+                status: enums::AttemptStatus::AuthenticationPending,
+                response: Ok(types::PaymentsResponseData::TransactionResponse {
+                    resource_id: types::ResponseId::NoResponseId,
+                    redirection_data: Some(services::RedirectForm::CybersourceAuthSetup {
+                        access_token: info_response
+                            .consumer_authentication_information
+                            .access_token,
+                        ddc_url: info_response
+                            .consumer_authentication_information
+                            .device_data_collection_url,
+                        reference_id: info_response
+                            .consumer_authentication_information
+                            .reference_id,
+                    }),
+                    mandate_reference: None,
+                    connector_metadata: None,
+                    network_txn_id: None,
+                    connector_response_reference_id: Some(
+                        info_response
+                            .client_reference_information
+                            .code
+                            .unwrap_or(info_response.id.clone()),
+                    ),
+                    incremental_authorization_allowed: None,
+                }),
+                ..item.data
+            }),
+            BankOfAmericaAuthSetupResponse::ErrorInformation(error_response) => {
+                let error_reason = error_response
+                    .error_information
+                    .message
+                    .unwrap_or(consts::NO_ERROR_MESSAGE.to_string());
+                let error_message = error_response.error_information.reason;
+                Ok(Self {
+                    response: Err(types::ErrorResponse {
+                        code: error_message
+                            .clone()
+                            .unwrap_or(consts::NO_ERROR_CODE.to_string()),
+                        message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
+                        reason: Some(error_reason),
+                        status_code: item.http_code,
+                        attempt_status: None,
+                        connector_transaction_id: Some(error_response.id.clone()),
+                    }),
+                    status: enums::AttemptStatus::AuthenticationFailed,
+                    ..item.data
+                })
+            }
+        }
+    }
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BankOfAmericaConsumerAuthInformationRequest {
+    return_url: String,
+    reference_id: String,
+}
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BankOfAmericaAuthEnrollmentRequest {
+    payment_information: PaymentInformation,
+    client_reference_information: ClientReferenceInformation,
+    consumer_authentication_information: BankOfAmericaConsumerAuthInformationRequest,
+    order_information: OrderInformationWithBill,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+pub struct BankOfAmericaRedirectionAuthResponse {
+    pub transaction_id: String,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BankOfAmericaConsumerAuthInformationValidateRequest {
+    authentication_transaction_id: String,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BankOfAmericaAuthValidateRequest {
+    payment_information: PaymentInformation,
+    client_reference_information: ClientReferenceInformation,
+    consumer_authentication_information: BankOfAmericaConsumerAuthInformationValidateRequest,
+    order_information: OrderInformation,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(untagged)]
+pub enum BankOfAmericaPreProcessingRequest {
+    AuthEnrollment(BankOfAmericaAuthEnrollmentRequest),
+    AuthValidate(BankOfAmericaAuthValidateRequest),
+}
+
+impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsPreProcessingRouterData>>
+    for BankOfAmericaPreProcessingRequest
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: &BankOfAmericaRouterData<&types::PaymentsPreProcessingRouterData>,
+    ) -> Result<Self, Self::Error> {
+        let client_reference_information = ClientReferenceInformation {
+            code: Some(item.router_data.connector_request_reference_id.clone()),
+        };
+        let payment_method_data = item.router_data.request.payment_method_data.clone().ok_or(
+            errors::ConnectorError::MissingConnectorRedirectionPayload {
+                field_name: "payment_method_data",
+            },
+        )?;
+        let payment_information = match payment_method_data {
+            payments::PaymentMethodData::Card(ccard) => {
+                let card_issuer = ccard.get_card_issuer();
+                let card_type = match card_issuer {
+                    Ok(issuer) => Some(String::from(issuer)),
+                    Err(_) => None,
+                };
+                Ok(PaymentInformation::Cards(CardPaymentInformation {
+                    card: Card {
+                        number: ccard.card_number,
+                        expiration_month: ccard.card_exp_month,
+                        expiration_year: ccard.card_exp_year,
+                        security_code: ccard.card_cvc,
+                        card_type,
+                    },
+                }))
+            }
+            payments::PaymentMethodData::Wallet(_)
+            | payments::PaymentMethodData::CardRedirect(_)
+            | payments::PaymentMethodData::PayLater(_)
+            | payments::PaymentMethodData::BankRedirect(_)
+            | payments::PaymentMethodData::BankDebit(_)
+            | payments::PaymentMethodData::BankTransfer(_)
+            | payments::PaymentMethodData::Crypto(_)
+            | payments::PaymentMethodData::MandatePayment
+            | payments::PaymentMethodData::Reward
+            | payments::PaymentMethodData::Upi(_)
+            | payments::PaymentMethodData::Voucher(_)
+            | payments::PaymentMethodData::GiftCard(_)
+            | payments::PaymentMethodData::CardToken(_) => {
+                Err(errors::ConnectorError::NotImplemented(
+                    utils::get_unimplemented_payment_method_error_message("BankOfAmerica"),
+                ))
+            }
+        }?;
+
+        let redirect_response = item.router_data.request.redirect_response.clone().ok_or(
+            errors::ConnectorError::MissingRequiredField {
+                field_name: "redirect_response",
+            },
+        )?;
+
+        let amount_details = Amount {
+            total_amount: item.amount.clone(),
+            currency: item.router_data.request.currency.ok_or(
+                errors::ConnectorError::MissingRequiredField {
+                    field_name: "currency",
+                },
+            )?,
+        };
+
+        match redirect_response.params {
+            Some(param) if !param.clone().peek().is_empty() => {
+                let reference_id = param
+                    .clone()
+                    .peek()
+                    .split_once('=')
+                    .ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload {
+                        field_name: "request.redirect_response.params.reference_id",
+                    })?
+                    .1
+                    .to_string();
+                let email = item.router_data.request.get_email()?;
+                let bill_to = build_bill_to(item.router_data.get_billing()?, email)?;
+                let order_information = OrderInformationWithBill {
+                    amount_details,
+                    bill_to,
+                };
+                Ok(Self::AuthEnrollment(BankOfAmericaAuthEnrollmentRequest {
+                    payment_information,
+                    client_reference_information,
+                    consumer_authentication_information:
+                        BankOfAmericaConsumerAuthInformationRequest {
+                            return_url: item.router_data.request.get_complete_authorize_url()?,
+                            reference_id,
+                        },
+                    order_information,
+                }))
+            }
+            Some(_) | None => {
+                let redirect_payload: BankOfAmericaRedirectionAuthResponse = redirect_response
+                    .payload
+                    .ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload {
+                        field_name: "request.redirect_response.payload",
+                    })?
+                    .peek()
+                    .clone()
+                    .parse_value("BankOfAmericaRedirectionAuthResponse")
+                    .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+                let order_information = OrderInformation { amount_details };
+                Ok(Self::AuthValidate(BankOfAmericaAuthValidateRequest {
+                    payment_information,
+                    client_reference_information,
+                    consumer_authentication_information:
+                        BankOfAmericaConsumerAuthInformationValidateRequest {
+                            authentication_transaction_id: redirect_payload.transaction_id,
+                        },
+                    order_information,
+                }))
+            }
+        }
+    }
+}
+
+impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>>
+    for BankOfAmericaPaymentsRequest
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
+    ) -> Result<Self, Self::Error> {
+        let payment_method_data = item.router_data.request.payment_method_data.clone().ok_or(
+            errors::ConnectorError::MissingRequiredField {
+                field_name: "payment_method_data",
+            },
+        )?;
+        match payment_method_data {
+            payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)),
+            payments::PaymentMethodData::Wallet(_)
+            | payments::PaymentMethodData::CardRedirect(_)
+            | payments::PaymentMethodData::PayLater(_)
+            | payments::PaymentMethodData::BankRedirect(_)
+            | payments::PaymentMethodData::BankDebit(_)
+            | payments::PaymentMethodData::BankTransfer(_)
+            | payments::PaymentMethodData::Crypto(_)
+            | payments::PaymentMethodData::MandatePayment
+            | payments::PaymentMethodData::Reward
+            | payments::PaymentMethodData::Upi(_)
+            | payments::PaymentMethodData::Voucher(_)
+            | payments::PaymentMethodData::GiftCard(_)
+            | payments::PaymentMethodData::CardToken(_) => {
+                Err(errors::ConnectorError::NotImplemented(
+                    utils::get_unimplemented_payment_method_error_message("BankOfAmerica"),
+                )
+                .into())
+            }
+        }
+    }
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum BankOfAmericaAuthEnrollmentStatus {
+    PendingAuthentication,
+    AuthenticationSuccessful,
+    AuthenticationFailed,
+}
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BankOfAmericaConsumerAuthValidateResponse {
+    ucaf_collection_indicator: Option<String>,
+    cavv: Option<String>,
+    ucaf_authentication_data: Option<String>,
+    xid: Option<String>,
+    specification_version: Option<String>,
+    directory_server_transaction_id: Option<String>,
+    indicator: Option<String>,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub struct BankOfAmericaThreeDSMetadata {
+    three_ds_data: BankOfAmericaConsumerAuthValidateResponse,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BankOfAmericaConsumerAuthInformationEnrollmentResponse {
+    access_token: Option<String>,
+    step_up_url: Option<String>,
+    //Added to segregate the three_ds_data in a separate struct
+    #[serde(flatten)]
+    validate_response: BankOfAmericaConsumerAuthValidateResponse,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ClientAuthCheckInfoResponse {
+    id: String,
+    client_reference_information: ClientReferenceInformation,
+    consumer_authentication_information: BankOfAmericaConsumerAuthInformationEnrollmentResponse,
+    status: BankOfAmericaAuthEnrollmentStatus,
+    error_information: Option<BankOfAmericaErrorInformation>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(untagged)]
+pub enum BankOfAmericaPreProcessingResponse {
+    ClientAuthCheckInfo(Box<ClientAuthCheckInfoResponse>),
+    ErrorInformation(BankOfAmericaErrorInformationResponse),
+}
+
+impl From<BankOfAmericaAuthEnrollmentStatus> for enums::AttemptStatus {
+    fn from(item: BankOfAmericaAuthEnrollmentStatus) -> Self {
+        match item {
+            BankOfAmericaAuthEnrollmentStatus::PendingAuthentication => Self::AuthenticationPending,
+            BankOfAmericaAuthEnrollmentStatus::AuthenticationSuccessful => {
+                Self::AuthenticationSuccessful
+            }
+            BankOfAmericaAuthEnrollmentStatus::AuthenticationFailed => Self::AuthenticationFailed,
+        }
+    }
+}
+
+impl<F>
+    TryFrom<
+        types::ResponseRouterData<
+            F,
+            BankOfAmericaPreProcessingResponse,
+            types::PaymentsPreProcessingData,
+            types::PaymentsResponseData,
+        >,
+    > for types::RouterData<F, types::PaymentsPreProcessingData, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::ResponseRouterData<
+            F,
+            BankOfAmericaPreProcessingResponse,
+            types::PaymentsPreProcessingData,
+            types::PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        match item.response {
+            BankOfAmericaPreProcessingResponse::ClientAuthCheckInfo(info_response) => {
+                let status = enums::AttemptStatus::from(info_response.status);
+                let risk_info: Option<ClientRiskInformation> = None;
+                if utils::is_payment_failure(status) {
+                    let response = Err(types::ErrorResponse::from((
+                        &info_response.error_information,
+                        &risk_info,
+                        item.http_code,
+                        info_response.id.clone(),
+                    )));
+
+                    Ok(Self {
+                        status,
+                        response,
+                        ..item.data
+                    })
+                } else {
+                    let connector_response_reference_id = Some(
+                        info_response
+                            .client_reference_information
+                            .code
+                            .unwrap_or(info_response.id.clone()),
+                    );
+
+                    let redirection_data = match (
+                        info_response
+                            .consumer_authentication_information
+                            .access_token,
+                        info_response
+                            .consumer_authentication_information
+                            .step_up_url,
+                    ) {
+                        (Some(access_token), Some(step_up_url)) => {
+                            Some(services::RedirectForm::CybersourceConsumerAuth {
+                                access_token,
+                                step_up_url,
+                            })
+                        }
+                        _ => None,
+                    };
+                    let three_ds_data = serde_json::to_value(
+                        info_response
+                            .consumer_authentication_information
+                            .validate_response,
+                    )
+                    .into_report()
+                    .change_context(errors::ConnectorError::ResponseHandlingFailed)?;
+                    Ok(Self {
+                        status,
+                        response: Ok(types::PaymentsResponseData::TransactionResponse {
+                            resource_id: types::ResponseId::NoResponseId,
+                            redirection_data,
+                            mandate_reference: None,
+                            connector_metadata: Some(
+                                serde_json::json!({"three_ds_data":three_ds_data}),
+                            ),
+                            network_txn_id: None,
+                            connector_response_reference_id,
+                            incremental_authorization_allowed: None,
+                        }),
+                        ..item.data
+                    })
+                }
+            }
+            BankOfAmericaPreProcessingResponse::ErrorInformation(ref error_response) => {
+                let error_reason = error_response
+                    .error_information
+                    .message
+                    .to_owned()
+                    .unwrap_or(consts::NO_ERROR_MESSAGE.to_string());
+                let error_message = error_response.error_information.reason.to_owned();
+                let response = Err(types::ErrorResponse {
+                    code: error_message
+                        .clone()
+                        .unwrap_or(consts::NO_ERROR_CODE.to_string()),
+                    message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
+                    reason: Some(error_reason),
+                    status_code: item.http_code,
+                    attempt_status: None,
+                    connector_transaction_id: Some(error_response.id.clone()),
+                });
+                Ok(Self {
+                    response,
+                    status: enums::AttemptStatus::AuthenticationFailed,
+                    ..item.data
+                })
+            }
+        }
+    }
+}
+
+impl<F>
+    TryFrom<
+        types::ResponseRouterData<
+            F,
+            BankOfAmericaPaymentsResponse,
+            types::CompleteAuthorizeData,
+            types::PaymentsResponseData,
+        >,
+    > for types::RouterData<F, types::CompleteAuthorizeData, types::PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::ResponseRouterData<
+            F,
+            BankOfAmericaPaymentsResponse,
+            types::CompleteAuthorizeData,
+            types::PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        match item.response {
+            BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => {
+                let status = enums::AttemptStatus::foreign_from((
+                    info_response.status.clone(),
+                    item.data.request.is_auto_capture()?,
+                ));
+                let response = get_payment_response((&info_response, status, item.http_code));
+                Ok(Self {
+                    status,
+                    response,
+                    ..item.data
+                })
+            }
+            BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => {
+                Ok(Self::from((
+                    &error_response.clone(),
+                    item,
+                    Some(enums::AttemptStatus::Failure),
+                )))
+            }
+        }
+    }
+}
+
 impl<F>
     TryFrom<
         types::ResponseRouterData<
diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs
index 69159c10c8a..b300e97b44a 100644
--- a/crates/router/src/connector/cybersource.rs
+++ b/crates/router/src/connector/cybersource.rs
@@ -874,6 +874,33 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
     ) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
         self.build_error_response(res)
     }
+
+    fn get_5xx_error_response(
+        &self,
+        res: types::Response,
+    ) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
+        let response: cybersource::CybersourceServerErrorResponse = res
+            .response
+            .parse_struct("CybersourceServerErrorResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        let attempt_status = match response.reason {
+            Some(reason) => match reason {
+                transformers::Reason::SystemError => Some(enums::AttemptStatus::Failure),
+                transformers::Reason::ServerTimeout | transformers::Reason::ServiceTimeout => None,
+            },
+            None => None,
+        };
+        Ok(types::ErrorResponse {
+            status_code: res.status_code,
+            reason: response.status.clone(),
+            code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()),
+            message: response
+                .message
+                .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
+            attempt_status,
+            connector_transaction_id: None,
+        })
+    }
 }
 
 impl
diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs
index 21cdec92ccb..49a9bcf6664 100644
--- a/crates/router/src/core/payments.rs
+++ b/crates/router/src/core/payments.rs
@@ -1489,7 +1489,8 @@ where
                 router_data = router_data.preprocessing_steps(state, connector).await?;
 
                 (router_data, false)
-            } else if connector.connector_name == router_types::Connector::Cybersource
+            } else if (connector.connector_name == router_types::Connector::Cybersource
+                || connector.connector_name == router_types::Connector::Bankofamerica)
                 && is_operation_complete_authorize(&operation)
                 && router_data.auth_type == storage_enums::AuthenticationType::ThreeDs
             {
diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs
index 6dd692f1525..c9f9d6d87f5 100644
--- a/crates/router/src/core/payments/flows.rs
+++ b/crates/router/src/core/payments/flows.rs
@@ -147,7 +147,6 @@ impl<const T: u8>
 default_imp_for_complete_authorize!(
     connector::Aci,
     connector::Adyen,
-    connector::Bankofamerica,
     connector::Bitpay,
     connector::Boku,
     connector::Cashtocode,
@@ -863,7 +862,6 @@ default_imp_for_pre_processing_steps!(
     connector::Airwallex,
     connector::Authorizedotnet,
     connector::Bambora,
-    connector::Bankofamerica,
     connector::Bitpay,
     connector::Bluesnap,
     connector::Boku,

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
Implement 3DS flow for cards

```
Legend: 
UB: User Browser
M: Merchant
HS: Hyperswitch
CS: Bankofamerica/Cybersource Connector
```

![3DS FLOW DIAGRAM](https://github.com/juspay/hyperswitch/assets/41580413/9f2e0e71-65df-4310-84cf-1419090a680f)


### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
https://github.com/juspay/hyperswitch/issues/3342

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->

### QA Testing

![Screenshot 2024-01-12 at 5 35 04 PM](https://github.com/juspay/hyperswitch/assets/41580413/bdab526f-736b-44c1-8904-3ae51e9781f6)
![Screenshot 2024-01-12 at 5 33 54 PM](https://github.com/juspay/hyperswitch/assets/41580413/55ca3a43-b8de-43f8-b463-3555a8e79e21)
![Screenshot 2024-01-12 at 5 33 10 PM](https://github.com/juspay/hyperswitch/assets/41580413/6a9d3d59-1a7d-411d-b5e3-61f59376e9c8)

![Screenshot 2024-01-12 at 5 33 28 PM](https://github.com/juspay/hyperswitch/assets/41580413/3ea39f9f-2ea1-4d25-a85e-fcb9b9522e31)

- Test Non-3DS flows to confirm they are not affected
- Test 3DS transactions

All the possible 3DS test cases and test cards are available here: https://developer.cybersource.com/content/dam/docs/cybs/en-us/payer-authentication/developer/all/rest/payer-auth.pdf

3DS Payment Curl:
`curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: API_KEY_HERE' \
--data-raw '{
  "amount": 1404,
  "currency": "USD",
  "confirm": true,
  "capture_method": "automatic",
  "capture_on": "2022-09-10T10:11:12Z",
  "customer_id": "CustomerX",
  "email": "guest@example.com",
  "name": "John Doe",
  "phone": "999999999",
  "phone_country_code": "+1",
  "description": "Its my first payment request",
  "authentication_type": "three_ds",
  "return_url": "https://google.com",
  "payment_method": "card",
  "payment_method_type": "credit",
  "payment_method_data": {
    "card": {
       "card_number": "340000000001098",    
      "card_exp_month": "12",
      "card_exp_year": "30",
      "card_holder_name": "joseph",
      "card_cvc": "123"
    }
  },
  "billing": {
    "address": {
      "line1": "sdv",
      "line2": "Harrison Street",
      "line3": "Harrison Street",
      "city": "San Fransico",
      "state": "California",
      "zip": "46282",
      "country": "US",
      "first_name": "joseph",
      "last_name": "ewcjwd"
    },
    "phone": {
      "number": "8056594427",
      "country_code": "+97"
    }
  },
  "statement_descriptor_name": "joseph",
  "statement_descriptor_suffix": "JS",
  "metadata": {
    "count_tickets": "1",
    "transaction_number": 2233
  },
  "business_label": "food",
  "business_country": "US"
}'`

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
