From ac753352769a67003eedd183e957bee6eb83b103 Mon Sep 17 00:00:00 2001
From: Debarshi Gupta <debarshigupta47@gmail.com>
Date: Mon, 13 Jan 2025 16:49:50 +0530
Subject: [PATCH] feat(connector): [Deutschebank] Implement Card 3ds (#6844)

Co-authored-by: Debarshi Gupta <debarshi.gupta@Debarshi-Gupta-CM92YWDXFD.local>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
---
 crates/diesel_models/src/payment_attempt.rs   |   4 +
 .../src/connectors/deutschebank.rs            |  77 ++-
 .../connectors/deutschebank/transformers.rs   | 508 ++++++++++++++----
 .../src/router_response_types.rs              |  10 +
 .../payment_connector_required_fields.rs      | 246 +++++++++
 crates/router/src/services/api.rs             |  40 ++
 .../cypress/e2e/PaymentUtils/Deutschebank.js  | 214 ++++++++
 .../cypress/e2e/PaymentUtils/Utils.js         |   2 +
 8 files changed, 990 insertions(+), 111 deletions(-)
 create mode 100644 cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js

diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs
index 03facc2ebab4..a450a30fa760 100644
--- a/crates/diesel_models/src/payment_attempt.rs
+++ b/crates/diesel_models/src/payment_attempt.rs
@@ -3426,6 +3426,10 @@ pub enum RedirectForm {
         access_token: String,
         step_up_url: String,
     },
+    DeutschebankThreeDSChallengeFlow {
+        acs_url: String,
+        creq: String,
+    },
     Payme,
     Braintree {
         client_token: String,
diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs
index 3098d27e7ecb..f4d5fe5fdf15 100644
--- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs
+++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs
@@ -59,7 +59,7 @@ use crate::{
     types::ResponseRouterData,
     utils::{
         self, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData,
-        RefundsRequestData,
+        RefundsRequestData, RouterData as ConnectorRouterData,
     },
 };
 
@@ -131,7 +131,7 @@ impl ConnectorCommon for Deutschebank {
     }
 
     fn get_currency_unit(&self) -> api::CurrencyUnit {
-        api::CurrencyUnit::Base
+        api::CurrencyUnit::Minor
     }
 
     fn common_get_content_type(&self) -> &'static str {
@@ -311,18 +311,30 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
         req: &PaymentsAuthorizeRouterData,
         connectors: &Connectors,
     ) -> CustomResult<String, errors::ConnectorError> {
-        if req.request.connector_mandate_id().is_none() {
+        let event_id = req.connector_request_reference_id.clone();
+        let tx_action = if req.request.is_auto_capture()? {
+            "authorization"
+        } else {
+            "preauthorization"
+        };
+
+        if req.is_three_ds() && req.request.is_card() {
+            Ok(format!(
+                "{}/services/v2.1/headless3DSecure/event/{event_id}/{tx_action}/initialize",
+                self.base_url(connectors)
+            ))
+        } else if !req.is_three_ds() && req.request.is_card() {
+            Err(errors::ConnectorError::NotSupported {
+                message: "Non-ThreeDs".to_owned(),
+                connector: "deutschebank",
+            }
+            .into())
+        } else if req.request.connector_mandate_id().is_none() {
             Ok(format!(
                 "{}/services/v2.1/managedmandate",
                 self.base_url(connectors)
             ))
         } else {
-            let event_id = req.connector_request_reference_id.clone();
-            let tx_action = if req.request.is_auto_capture()? {
-                "authorization"
-            } else {
-                "preauthorization"
-            };
             Ok(format!(
                 "{}/services/v2.1/payment/event/{event_id}/directdebit/{tx_action}",
                 self.base_url(connectors)
@@ -375,7 +387,19 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
         event_builder: Option<&mut ConnectorEvent>,
         res: Response,
     ) -> CustomResult<PaymentsAuthorizeRouterData, errors::ConnectorError> {
-        if data.request.connector_mandate_id().is_none() {
+        if data.is_three_ds() && data.request.is_card() {
+            let response: deutschebank::DeutschebankThreeDSInitializeResponse = res
+                .response
+                .parse_struct("DeutschebankPaymentsAuthorizeResponse")
+                .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+            event_builder.map(|i| i.set_response_body(&response));
+            router_env::logger::info!(connector_response=?response);
+            RouterData::try_from(ResponseRouterData {
+                response,
+                data: data.clone(),
+                http_code: res.status_code,
+            })
+        } else if data.request.connector_mandate_id().is_none() {
             let response: deutschebank::DeutschebankMandatePostResponse = res
                 .response
                 .parse_struct("DeutschebankMandatePostResponse")
@@ -437,10 +461,18 @@ impl ConnectorIntegration<CompleteAuthorize, CompleteAuthorizeData, PaymentsResp
         } else {
             "preauthorization"
         };
-        Ok(format!(
-            "{}/services/v2.1/payment/event/{event_id}/directdebit/{tx_action}",
-            self.base_url(connectors)
-        ))
+
+        if req.is_three_ds() && matches!(req.payment_method, enums::PaymentMethod::Card) {
+            Ok(format!(
+                "{}/services/v2.1//headless3DSecure/event/{event_id}/final",
+                self.base_url(connectors)
+            ))
+        } else {
+            Ok(format!(
+                "{}/services/v2.1/payment/event/{event_id}/directdebit/{tx_action}",
+                self.base_url(connectors)
+            ))
+        }
     }
 
     fn get_request_body(
@@ -453,10 +485,9 @@ impl ConnectorIntegration<CompleteAuthorize, CompleteAuthorizeData, PaymentsResp
             req.request.minor_amount,
             req.request.currency,
         )?;
-
         let connector_router_data = deutschebank::DeutschebankRouterData::from((amount, req));
         let connector_req =
-            deutschebank::DeutschebankDirectDebitRequest::try_from(&connector_router_data)?;
+            deutschebank::DeutschebankCompleteAuthorizeRequest::try_from(&connector_router_data)?;
         Ok(RequestContent::Json(Box::new(connector_req)))
     }
 
@@ -952,10 +983,20 @@ lazy_static! {
         deutschebank_supported_payment_methods.add(
             enums::PaymentMethod::BankDebit,
             enums::PaymentMethodType::Sepa,
+            PaymentMethodDetails{
+                mandates: enums::FeatureStatus::Supported,
+                refunds: enums::FeatureStatus::Supported,
+                supported_capture_methods: supported_capture_methods.clone(),
+            }
+        );
+
+        deutschebank_supported_payment_methods.add(
+            enums::PaymentMethod::Card,
+            enums::PaymentMethodType::Credit,
             PaymentMethodDetails{
                 mandates: enums::FeatureStatus::NotSupported,
-                refunds: enums::FeatureStatus::NotSupported,
-                supported_capture_methods,
+                refunds: enums::FeatureStatus::Supported,
+                supported_capture_methods: supported_capture_methods.clone(),
             }
         );
 
diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs
index 85c5fc8dc816..8133be2fa794 100644
--- a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs
+++ b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs
@@ -1,6 +1,7 @@
 use std::collections::HashMap;
 
-use common_enums::enums;
+use cards::CardNumber;
+use common_enums::{enums, PaymentMethod};
 use common_utils::{ext_traits::ValueExt, pii::Email, types::MinorUnit};
 use error_stack::ResultExt;
 use hyperswitch_domain_models::{
@@ -22,14 +23,14 @@ use hyperswitch_domain_models::{
         PaymentsCompleteAuthorizeRouterData, RefundsRouterData,
     },
 };
-use hyperswitch_interfaces::errors;
-use masking::{PeekInterface, Secret};
+use hyperswitch_interfaces::{consts, errors};
+use masking::{ExposeInterface, PeekInterface, Secret};
 use serde::{Deserialize, Serialize};
 
 use crate::{
     types::{PaymentsCancelResponseRouterData, RefundsResponseRouterData, ResponseRouterData},
     utils::{
-        self, AddressDetailsData, PaymentsAuthorizeRequestData,
+        self, AddressDetailsData, CardData, PaymentsAuthorizeRequestData,
         PaymentsCompleteAuthorizeRequestData, RefundsRequestData, RouterData as OtherRouterData,
     },
 };
@@ -129,6 +130,75 @@ pub struct DeutschebankMandatePostRequest {
 pub enum DeutschebankPaymentsRequest {
     MandatePost(DeutschebankMandatePostRequest),
     DirectDebit(DeutschebankDirectDebitRequest),
+    CreditCard(Box<DeutschebankThreeDSInitializeRequest>),
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub struct DeutschebankThreeDSInitializeRequest {
+    means_of_payment: DeutschebankThreeDSInitializeRequestMeansOfPayment,
+    tds_20_data: DeutschebankThreeDSInitializeRequestTds20Data,
+    amount_total: DeutschebankThreeDSInitializeRequestAmountTotal,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub struct DeutschebankThreeDSInitializeRequestMeansOfPayment {
+    credit_card: DeutschebankThreeDSInitializeRequestCreditCard,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub struct DeutschebankThreeDSInitializeRequestCreditCard {
+    number: CardNumber,
+    expiry_date: DeutschebankThreeDSInitializeRequestCreditCardExpiry,
+    code: Secret<String>,
+    cardholder: Secret<String>,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub struct DeutschebankThreeDSInitializeRequestCreditCardExpiry {
+    year: Secret<String>,
+    month: Secret<String>,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub struct DeutschebankThreeDSInitializeRequestAmountTotal {
+    amount: MinorUnit,
+    currency: api_models::enums::Currency,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub struct DeutschebankThreeDSInitializeRequestTds20Data {
+    communication_data: DeutschebankThreeDSInitializeRequestCommunicationData,
+    customer_data: DeutschebankThreeDSInitializeRequestCustomerData,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub struct DeutschebankThreeDSInitializeRequestCommunicationData {
+    method_notification_url: String,
+    cres_notification_url: String,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub struct DeutschebankThreeDSInitializeRequestCustomerData {
+    billing_address: DeutschebankThreeDSInitializeRequestCustomerBillingData,
+    cardholder_email: Email,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub struct DeutschebankThreeDSInitializeRequestCustomerBillingData {
+    street: Secret<String>,
+    postal_code: Secret<String>,
+    city: String,
+    state: Secret<String>,
+    country: String,
 }
 
 impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>>
@@ -148,11 +218,9 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>>
             None => {
                 // To facilitate one-off payments via SEPA with Deutsche Bank, we are considering not storing the connector mandate ID in our system if future usage is on-session.
                 // We will only check for customer acceptance to make a one-off payment. we will be storing the connector mandate details only when setup future usage is off-session.
-                if item.router_data.request.customer_acceptance.is_some() {
-                    match item.router_data.request.payment_method_data.clone() {
-                        PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit {
-                            iban, ..
-                        }) => {
+                match item.router_data.request.payment_method_data.clone() {
+                    PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. }) => {
+                        if item.router_data.request.customer_acceptance.is_some() {
                             let billing_address = item.router_data.get_billing_address()?;
                             Ok(Self::MandatePost(DeutschebankMandatePostRequest {
                                 approval_by: DeutschebankSEPAApproval::Click,
@@ -161,17 +229,60 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>>
                                 first_name: billing_address.get_first_name()?.clone(),
                                 last_name: billing_address.get_last_name()?.clone(),
                             }))
+                        } else {
+                            Err(errors::ConnectorError::MissingRequiredField {
+                                field_name: "customer_acceptance",
+                            }
+                            .into())
                         }
-                        _ => Err(errors::ConnectorError::NotImplemented(
-                            utils::get_unimplemented_payment_method_error_message("deutschebank"),
-                        )
-                        .into()),
                     }
-                } else {
-                    Err(errors::ConnectorError::MissingRequiredField {
-                        field_name: "customer_acceptance",
+                    PaymentMethodData::Card(ccard) => {
+                        if !item.router_data.clone().is_three_ds() {
+                            Err(errors::ConnectorError::NotSupported {
+                                message: "Non-ThreeDs".to_owned(),
+                                connector: "deutschebank",
+                            }
+                            .into())
+                        } else {
+                            let billing_address = item.router_data.get_billing_address()?;
+                            Ok(Self::CreditCard(Box::new(DeutschebankThreeDSInitializeRequest {
+                                    means_of_payment: DeutschebankThreeDSInitializeRequestMeansOfPayment {
+                                        credit_card: DeutschebankThreeDSInitializeRequestCreditCard {
+                                            number: ccard.clone().card_number,
+                                            expiry_date: DeutschebankThreeDSInitializeRequestCreditCardExpiry {
+                                                year: ccard.get_expiry_year_4_digit(),
+                                                month: ccard.card_exp_month,
+                                            },
+                                            code: ccard.card_cvc,
+                                            cardholder: item.router_data.get_billing_full_name()?,
+                                        }},
+                                    amount_total: DeutschebankThreeDSInitializeRequestAmountTotal {
+                                        amount: item.amount,
+                                        currency: item.router_data.request.currency,
+                                    },
+                                    tds_20_data: DeutschebankThreeDSInitializeRequestTds20Data {
+                                        communication_data: DeutschebankThreeDSInitializeRequestCommunicationData {
+                                            method_notification_url: item.router_data.request.get_complete_authorize_url()?,
+                                            cres_notification_url: item.router_data.request.get_complete_authorize_url()?,
+                                        },
+                                        customer_data: DeutschebankThreeDSInitializeRequestCustomerData {
+                                            billing_address: DeutschebankThreeDSInitializeRequestCustomerBillingData {
+                                                street: billing_address.get_line1()?.clone(),
+                                                postal_code: billing_address.get_zip()?.clone(),
+                                                city: billing_address.get_city()?.to_string(),
+                                                state: billing_address.get_state()?.clone(),
+                                                country: item.router_data.get_billing_country()?.to_string(),
+                                            },
+                                            cardholder_email: item.router_data.request.get_email()?,
+                                        }
+                                    }
+                                })))
+                        }
                     }
-                    .into())
+                    _ => Err(errors::ConnectorError::NotImplemented(
+                        utils::get_unimplemented_payment_method_error_message("deutschebank"),
+                    )
+                    .into()),
                 }
             }
             Some(api_models::payments::MandateReferenceId::ConnectorMandateId(mandate_data)) => {
@@ -209,6 +320,138 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>>
     }
 }
 
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct DeutschebankThreeDSInitializeResponse {
+    outcome: DeutschebankThreeDSInitializeResponseOutcome,
+    challenge_required: Option<DeutschebankThreeDSInitializeResponseChallengeRequired>,
+    processed: Option<DeutschebankThreeDSInitializeResponseProcessed>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct DeutschebankThreeDSInitializeResponseProcessed {
+    rc: String,
+    message: String,
+    tx_id: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum DeutschebankThreeDSInitializeResponseOutcome {
+    Processed,
+    ChallengeRequired,
+    MethodRequired,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct DeutschebankThreeDSInitializeResponseChallengeRequired {
+    acs_url: String,
+    creq: String,
+}
+
+impl
+    TryFrom<
+        ResponseRouterData<
+            Authorize,
+            DeutschebankThreeDSInitializeResponse,
+            PaymentsAuthorizeData,
+            PaymentsResponseData,
+        >,
+    > for RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: ResponseRouterData<
+            Authorize,
+            DeutschebankThreeDSInitializeResponse,
+            PaymentsAuthorizeData,
+            PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        match item.response.outcome {
+            DeutschebankThreeDSInitializeResponseOutcome::Processed => {
+                match item.response.processed {
+                    Some(processed) => Ok(Self {
+                        status: if is_response_success(&processed.rc) {
+                            match item.data.request.is_auto_capture()? {
+                                true => common_enums::AttemptStatus::Charged,
+                                false => common_enums::AttemptStatus::Authorized,
+                            }
+                        } else {
+                            common_enums::AttemptStatus::AuthenticationFailed
+                        },
+                        response: Ok(PaymentsResponseData::TransactionResponse {
+                            resource_id: ResponseId::ConnectorTransactionId(
+                                processed.tx_id.clone(),
+                            ),
+                            redirection_data: Box::new(None),
+                            mandate_reference: Box::new(None),
+                            connector_metadata: None,
+                            network_txn_id: None,
+                            connector_response_reference_id: Some(processed.tx_id.clone()),
+                            incremental_authorization_allowed: None,
+                            charge_id: None,
+                        }),
+                        ..item.data
+                    }),
+                    None => {
+                        let response_string = format!("{:?}", item.response);
+                        Err(
+                            errors::ConnectorError::UnexpectedResponseError(bytes::Bytes::from(
+                                response_string,
+                            ))
+                            .into(),
+                        )
+                    }
+                }
+            }
+            DeutschebankThreeDSInitializeResponseOutcome::ChallengeRequired => {
+                match item.response.challenge_required {
+                    Some(challenge) => Ok(Self {
+                        status: common_enums::AttemptStatus::AuthenticationPending,
+                        response: Ok(PaymentsResponseData::TransactionResponse {
+                            resource_id: ResponseId::NoResponseId,
+                            redirection_data: Box::new(Some(
+                                RedirectForm::DeutschebankThreeDSChallengeFlow {
+                                    acs_url: challenge.acs_url,
+                                    creq: challenge.creq,
+                                },
+                            )),
+                            mandate_reference: Box::new(None),
+                            connector_metadata: None,
+                            network_txn_id: None,
+                            connector_response_reference_id: None,
+                            incremental_authorization_allowed: None,
+                            charge_id: None,
+                        }),
+                        ..item.data
+                    }),
+                    None => {
+                        let response_string = format!("{:?}", item.response);
+                        Err(
+                            errors::ConnectorError::UnexpectedResponseError(bytes::Bytes::from(
+                                response_string,
+                            ))
+                            .into(),
+                        )
+                    }
+                }
+            }
+            DeutschebankThreeDSInitializeResponseOutcome::MethodRequired => Ok(Self {
+                status: common_enums::AttemptStatus::Failure,
+                response: Err(ErrorResponse {
+                    code: consts::NO_ERROR_CODE.to_owned(),
+                    message: "METHOD_REQUIRED Flow not supported for deutschebank 3ds payments".to_owned(),
+                    reason: Some("METHOD_REQUIRED Flow is not currently supported for deutschebank 3ds payments".to_owned()),
+                    status_code: item.http_code,
+                    attempt_status: None,
+                    connector_transaction_id: None,
+                }),
+                ..item.data
+            }),
+        }
+    }
+}
+
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
 pub enum DeutschebankSEPAMandateStatus {
@@ -450,79 +693,117 @@ pub struct DeutschebankDirectDebitRequest {
     mandate: DeutschebankMandate,
 }
 
+#[derive(Debug, Serialize)]
+#[serde(untagged)]
+pub enum DeutschebankCompleteAuthorizeRequest {
+    DeutschebankDirectDebitRequest(DeutschebankDirectDebitRequest),
+    DeutschebankThreeDSCompleteAuthorizeRequest(DeutschebankThreeDSCompleteAuthorizeRequest),
+}
+
+#[derive(Debug, Serialize, PartialEq)]
+pub struct DeutschebankThreeDSCompleteAuthorizeRequest {
+    cres: String,
+}
+
 impl TryFrom<&DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>>
-    for DeutschebankDirectDebitRequest
+    for DeutschebankCompleteAuthorizeRequest
 {
     type Error = error_stack::Report<errors::ConnectorError>;
     fn try_from(
         item: &DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>,
     ) -> Result<Self, Self::Error> {
-        let account_holder = item.router_data.get_billing_address()?.get_full_name()?;
-        let redirect_response = item.router_data.request.redirect_response.clone().ok_or(
-            errors::ConnectorError::MissingRequiredField {
-                field_name: "redirect_response",
-            },
-        )?;
-        let queries_params = redirect_response
-            .params
-            .map(|param| {
-                let mut queries = HashMap::<String, String>::new();
-                let values = param.peek().split('&').collect::<Vec<&str>>();
-                for value in values {
-                    let pair = value.split('=').collect::<Vec<&str>>();
-                    queries.insert(
-                        pair.first()
-                            .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?
-                            .to_string(),
-                        pair.get(1)
-                            .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?
-                            .to_string(),
+        if matches!(item.router_data.payment_method, PaymentMethod::Card) {
+            let redirect_response_payload = item
+                .router_data
+                .request
+                .get_redirect_response_payload()?
+                .expose();
+
+            let cres = redirect_response_payload
+                .get("cres")
+                .and_then(|v| v.as_str())
+                .map(String::from)
+                .ok_or(errors::ConnectorError::MissingRequiredField { field_name: "cres" })?;
+
+            Ok(Self::DeutschebankThreeDSCompleteAuthorizeRequest(
+                DeutschebankThreeDSCompleteAuthorizeRequest { cres },
+            ))
+        } else {
+            match item.router_data.request.payment_method_data.clone() {
+                Some(PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit {
+                    iban, ..
+                })) => {
+                    let account_holder = item.router_data.get_billing_address()?.get_full_name()?;
+                    let redirect_response =
+                        item.router_data.request.redirect_response.clone().ok_or(
+                            errors::ConnectorError::MissingRequiredField {
+                                field_name: "redirect_response",
+                            },
+                        )?;
+                    let queries_params = redirect_response
+                        .params
+                        .map(|param| {
+                            let mut queries = HashMap::<String, String>::new();
+                            let values = param.peek().split('&').collect::<Vec<&str>>();
+                            for value in values {
+                                let pair = value.split('=').collect::<Vec<&str>>();
+                                queries.insert(
+                                    pair.first()
+                                        .ok_or(
+                                            errors::ConnectorError::ResponseDeserializationFailed,
+                                        )?
+                                        .to_string(),
+                                    pair.get(1)
+                                        .ok_or(
+                                            errors::ConnectorError::ResponseDeserializationFailed,
+                                        )?
+                                        .to_string(),
+                                );
+                            }
+                            Ok::<_, errors::ConnectorError>(queries)
+                        })
+                        .transpose()?
+                        .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?;
+                    let reference = Secret::from(
+                        queries_params
+                            .get("reference")
+                            .ok_or(errors::ConnectorError::MissingRequiredField {
+                                field_name: "reference",
+                            })?
+                            .to_owned(),
                     );
-                }
-                Ok::<_, errors::ConnectorError>(queries)
-            })
-            .transpose()?
-            .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?;
-        let reference = Secret::from(
-            queries_params
-                .get("reference")
-                .ok_or(errors::ConnectorError::MissingRequiredField {
-                    field_name: "reference",
-                })?
-                .to_owned(),
-        );
-        let signed_on = queries_params
-            .get("signed_on")
-            .ok_or(errors::ConnectorError::MissingRequiredField {
-                field_name: "signed_on",
-            })?
-            .to_owned();
-
-        match item.router_data.request.payment_method_data.clone() {
-            Some(PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. })) => {
-                Ok(Self {
-                    amount_total: DeutschebankAmount {
-                        amount: item.amount,
-                        currency: item.router_data.request.currency,
-                    },
-                    means_of_payment: DeutschebankMeansOfPayment {
-                        bank_account: DeutschebankBankAccount {
-                            account_holder,
-                            iban: Secret::from(iban.peek().replace(" ", "")),
+                    let signed_on = queries_params
+                        .get("signed_on")
+                        .ok_or(errors::ConnectorError::MissingRequiredField {
+                            field_name: "signed_on",
+                        })?
+                        .to_owned();
+                    Ok(Self::DeutschebankDirectDebitRequest(
+                        DeutschebankDirectDebitRequest {
+                            amount_total: DeutschebankAmount {
+                                amount: item.amount,
+                                currency: item.router_data.request.currency,
+                            },
+                            means_of_payment: DeutschebankMeansOfPayment {
+                                bank_account: DeutschebankBankAccount {
+                                    account_holder,
+                                    iban: Secret::from(iban.peek().replace(" ", "")),
+                                },
+                            },
+                            mandate: {
+                                DeutschebankMandate {
+                                    reference,
+                                    signed_on,
+                                }
+                            },
                         },
-                    },
-                    mandate: {
-                        DeutschebankMandate {
-                            reference,
-                            signed_on,
-                        }
-                    },
-                })
+                    ))
+                }
+                _ => Err(errors::ConnectorError::NotImplemented(
+                    utils::get_unimplemented_payment_method_error_message("deutschebank"),
+                )
+                .into()),
             }
-            _ => Err(errors::ConnectorError::NotImplemented(
-                utils::get_unimplemented_payment_method_error_message("deutschebank"),
-            )
-            .into()),
         }
     }
 }
@@ -636,6 +917,8 @@ impl
 #[serde(rename_all = "UPPERCASE")]
 pub enum DeutschebankTransactionKind {
     Directdebit,
+    #[serde(rename = "CREDITCARD_3DS20")]
+    Creditcard3ds20,
 }
 
 #[derive(Debug, Serialize, PartialEq)]
@@ -649,10 +932,24 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsCaptureRouterData>> for Deutscheba
     fn try_from(
         item: &DeutschebankRouterData<&PaymentsCaptureRouterData>,
     ) -> Result<Self, Self::Error> {
-        Ok(Self {
-            changed_amount: item.amount,
-            kind: DeutschebankTransactionKind::Directdebit,
-        })
+        if matches!(item.router_data.payment_method, PaymentMethod::BankDebit) {
+            Ok(Self {
+                changed_amount: item.amount,
+                kind: DeutschebankTransactionKind::Directdebit,
+            })
+        } else if item.router_data.is_three_ds()
+            && matches!(item.router_data.payment_method, PaymentMethod::Card)
+        {
+            Ok(Self {
+                changed_amount: item.amount,
+                kind: DeutschebankTransactionKind::Creditcard3ds20,
+            })
+        } else {
+            Err(errors::ConnectorError::NotImplemented(
+                utils::get_unimplemented_payment_method_error_message("deutschebank"),
+            )
+            .into())
+        }
     }
 }
 
@@ -772,10 +1069,21 @@ pub struct DeutschebankReversalRequest {
 
 impl TryFrom<&PaymentsCancelRouterData> for DeutschebankReversalRequest {
     type Error = error_stack::Report<errors::ConnectorError>;
-    fn try_from(_item: &PaymentsCancelRouterData) -> Result<Self, Self::Error> {
-        Ok(Self {
-            kind: DeutschebankTransactionKind::Directdebit,
-        })
+    fn try_from(item: &PaymentsCancelRouterData) -> Result<Self, Self::Error> {
+        if matches!(item.payment_method, PaymentMethod::BankDebit) {
+            Ok(Self {
+                kind: DeutschebankTransactionKind::Directdebit,
+            })
+        } else if item.is_three_ds() && matches!(item.payment_method, PaymentMethod::Card) {
+            Ok(Self {
+                kind: DeutschebankTransactionKind::Creditcard3ds20,
+            })
+        } else {
+            Err(errors::ConnectorError::NotImplemented(
+                utils::get_unimplemented_payment_method_error_message("deutschebank"),
+            )
+            .into())
+        }
     }
 }
 
@@ -815,10 +1123,24 @@ pub struct DeutschebankRefundRequest {
 impl<F> TryFrom<&DeutschebankRouterData<&RefundsRouterData<F>>> for DeutschebankRefundRequest {
     type Error = error_stack::Report<errors::ConnectorError>;
     fn try_from(item: &DeutschebankRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
-        Ok(Self {
-            changed_amount: item.amount.to_owned(),
-            kind: DeutschebankTransactionKind::Directdebit,
-        })
+        if matches!(item.router_data.payment_method, PaymentMethod::BankDebit) {
+            Ok(Self {
+                changed_amount: item.amount,
+                kind: DeutschebankTransactionKind::Directdebit,
+            })
+        } else if item.router_data.is_three_ds()
+            && matches!(item.router_data.payment_method, PaymentMethod::Card)
+        {
+            Ok(Self {
+                changed_amount: item.amount,
+                kind: DeutschebankTransactionKind::Creditcard3ds20,
+            })
+        } else {
+            Err(errors::ConnectorError::NotImplemented(
+                utils::get_unimplemented_payment_method_error_message("deutschebank"),
+            )
+            .into())
+        }
     }
 }
 
diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs
index 61453f36b84e..613797e2988c 100644
--- a/crates/hyperswitch_domain_models/src/router_response_types.rs
+++ b/crates/hyperswitch_domain_models/src/router_response_types.rs
@@ -236,6 +236,10 @@ pub enum RedirectForm {
         access_token: String,
         step_up_url: String,
     },
+    DeutschebankThreeDSChallengeFlow {
+        acs_url: String,
+        creq: String,
+    },
     Payme,
     Braintree {
         client_token: String,
@@ -313,6 +317,9 @@ impl From<RedirectForm> for diesel_models::payment_attempt::RedirectForm {
                 access_token,
                 step_up_url,
             },
+            RedirectForm::DeutschebankThreeDSChallengeFlow { acs_url, creq } => {
+                Self::DeutschebankThreeDSChallengeFlow { acs_url, creq }
+            }
             RedirectForm::Payme => Self::Payme,
             RedirectForm::Braintree {
                 client_token,
@@ -392,6 +399,9 @@ impl From<diesel_models::payment_attempt::RedirectForm> for RedirectForm {
                 access_token,
                 step_up_url,
             },
+            diesel_models::RedirectForm::DeutschebankThreeDSChallengeFlow { acs_url, creq } => {
+                Self::DeutschebankThreeDSChallengeFlow { acs_url, creq }
+            }
             diesel_models::payment_attempt::RedirectForm::Payme => Self::Payme,
             diesel_models::payment_attempt::RedirectForm::Braintree {
                 client_token,
diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs
index 93ed327c31dc..25bc4db46389 100644
--- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs
+++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs
@@ -942,6 +942,129 @@ impl Default for settings::RequiredFields {
                                     ),
                                 }
                             ),
+                            (
+                                enums::Connector::Deutschebank,
+                                RequiredFieldFinal {
+                                    mandate: HashMap::new(),
+                                    non_mandate : HashMap::from(
+                                        [
+                                            (
+                                                "payment_method_data.card.card_number".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.card.card_number".to_string(),
+                                                    display_name: "card_number".to_string(),
+                                                    field_type: enums::FieldType::UserCardNumber,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "payment_method_data.card.card_exp_month".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.card.card_exp_month".to_string(),
+                                                    display_name: "card_exp_month".to_string(),
+                                                    field_type: enums::FieldType::UserCardExpiryMonth,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "payment_method_data.card.card_exp_year".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.card.card_exp_year".to_string(),
+                                                    display_name: "card_exp_year".to_string(),
+                                                    field_type: enums::FieldType::UserCardExpiryYear,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "payment_method_data.card.card_cvc".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.card.card_cvc".to_string(),
+                                                    display_name: "card_cvc".to_string(),
+                                                    field_type: enums::FieldType::UserCardCvc,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "email".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "email".to_string(),
+                                                    display_name: "email".to_string(),
+                                                    field_type: enums::FieldType::UserEmailAddress,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.line1".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.line1".to_string(),
+                                                    display_name: "line1".to_string(),
+                                                    field_type: enums::FieldType::UserAddressLine1,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.city".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.city".to_string(),
+                                                    display_name: "city".to_string(),
+                                                    field_type: enums::FieldType::UserAddressCity,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.state".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.state".to_string(),
+                                                    display_name: "state".to_string(),
+                                                    field_type: enums::FieldType::UserAddressState,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.zip".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.zip".to_string(),
+                                                    display_name: "zip".to_string(),
+                                                    field_type: enums::FieldType::UserAddressPincode,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.country".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.country".to_string(),
+                                                    display_name: "country".to_string(),
+                                                    field_type: enums::FieldType::UserAddressCountry{
+                                                        options: vec![
+                                                            "ALL".to_string(),
+                                                        ]
+                                                    },
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.first_name".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.first_name".to_string(),
+                                                    display_name: "first_name".to_string(),
+                                                    field_type: enums::FieldType::UserFullName,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.last_name".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.last_name".to_string(),
+                                                    display_name: "last_name".to_string(),
+                                                    field_type: enums::FieldType::UserFullName,
+                                                    value: None,
+                                                }
+                                            )
+                                        ]
+                                    ),
+                                    common: HashMap::new(),
+                                }
+                            ),
                             (
                                 enums::Connector::Dlocal,
                                 RequiredFieldFinal {
@@ -4139,6 +4262,129 @@ impl Default for settings::RequiredFields {
                                     ),
                                 }
                             ),
+                            (
+                                enums::Connector::Deutschebank,
+                                RequiredFieldFinal {
+                                    mandate: HashMap::new(),
+                                    non_mandate : HashMap::from(
+                                        [
+                                            (
+                                                "payment_method_data.card.card_number".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.card.card_number".to_string(),
+                                                    display_name: "card_number".to_string(),
+                                                    field_type: enums::FieldType::UserCardNumber,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "payment_method_data.card.card_exp_month".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.card.card_exp_month".to_string(),
+                                                    display_name: "card_exp_month".to_string(),
+                                                    field_type: enums::FieldType::UserCardExpiryMonth,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "payment_method_data.card.card_exp_year".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.card.card_exp_year".to_string(),
+                                                    display_name: "card_exp_year".to_string(),
+                                                    field_type: enums::FieldType::UserCardExpiryYear,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "payment_method_data.card.card_cvc".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.card.card_cvc".to_string(),
+                                                    display_name: "card_cvc".to_string(),
+                                                    field_type: enums::FieldType::UserCardCvc,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "email".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "email".to_string(),
+                                                    display_name: "email".to_string(),
+                                                    field_type: enums::FieldType::UserEmailAddress,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.line1".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.line1".to_string(),
+                                                    display_name: "line1".to_string(),
+                                                    field_type: enums::FieldType::UserAddressLine1,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.city".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.city".to_string(),
+                                                    display_name: "city".to_string(),
+                                                    field_type: enums::FieldType::UserAddressCity,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.state".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.state".to_string(),
+                                                    display_name: "state".to_string(),
+                                                    field_type: enums::FieldType::UserAddressState,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.zip".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.zip".to_string(),
+                                                    display_name: "zip".to_string(),
+                                                    field_type: enums::FieldType::UserAddressPincode,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.country".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.country".to_string(),
+                                                    display_name: "country".to_string(),
+                                                    field_type: enums::FieldType::UserAddressCountry{
+                                                        options: vec![
+                                                            "ALL".to_string(),
+                                                        ]
+                                                    },
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.first_name".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.first_name".to_string(),
+                                                    display_name: "first_name".to_string(),
+                                                    field_type: enums::FieldType::UserFullName,
+                                                    value: None,
+                                                }
+                                            ),
+                                            (
+                                                "billing.address.last_name".to_string(),
+                                                RequiredFieldInfo {
+                                                    required_field: "payment_method_data.billing.address.last_name".to_string(),
+                                                    display_name: "last_name".to_string(),
+                                                    field_type: enums::FieldType::UserFullName,
+                                                    value: None,
+                                                }
+                                            )
+                                        ]
+                                    ),
+                                    common: HashMap::new(),
+                                }
+                            ),
                             (
                                 enums::Connector::Dlocal,
                                 RequiredFieldFinal {
diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs
index f90528353cfb..97801c1b4a4f 100644
--- a/crates/router/src/services/api.rs
+++ b/crates/router/src/services/api.rs
@@ -1535,6 +1535,46 @@ pub fn build_redirection_form(
               </script>")))
             }}
         }
+        RedirectForm::DeutschebankThreeDSChallengeFlow { acs_url, creq } => {
+            maud::html! {
+                (maud::DOCTYPE)
+                html {
+                    head {
+                        meta name="viewport" content="width=device-width, initial-scale=1";
+                    }
+
+                    body style="background-color: #ffffff; padding: 20px; font-family: Arial, Helvetica, Sans-Serif;" {
+                        div id="loader1" class="lottie" style="height: 150px; display: block; position: relative; margin-top: 150px; margin-left: auto; margin-right: auto;" { "" }
+
+                        (PreEscaped(r#"<script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.7.4/lottie.min.js"></script>"#))
+
+                        (PreEscaped(r#"
+                            <script>
+                            var anime = bodymovin.loadAnimation({
+                                container: document.getElementById('loader1'),
+                                renderer: 'svg',
+                                loop: true,
+                                autoplay: true,
+                                name: 'hyperswitch loader',
+                                animationData: {"v":"4.8.0","meta":{"g":"LottieFiles AE 3.1.1","a":"","k":"","d":"","tc":""},"fr":29.9700012207031,"ip":0,"op":31.0000012626559,"w":400,"h":250,"nm":"loader_shape","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[278.25,202.671,0],"ix":2},"a":{"a":0,"k":[23.72,23.72,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[12.935,0],[0,-12.936],[-12.935,0],[0,12.935]],"o":[[-12.952,0],[0,12.935],[12.935,0],[0,-12.936]],"v":[[0,-23.471],[-23.47,0.001],[0,23.471],[23.47,0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.427451010311,0.976470648074,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":19.99,"s":[100]},{"t":29.9800012211104,"s":[10]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[23.72,23.721],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":48.0000019550801,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"square 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[196.25,201.271,0],"ix":2},"a":{"a":0,"k":[22.028,22.03,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.914,0],[0,0],[0,-1.914],[0,0],[-1.914,0],[0,0],[0,1.914],[0,0]],"o":[[0,0],[-1.914,0],[0,0],[0,1.914],[0,0],[1.914,0],[0,0],[0,-1.914]],"v":[[18.313,-21.779],[-18.312,-21.779],[-21.779,-18.313],[-21.779,18.314],[-18.312,21.779],[18.313,21.779],[21.779,18.314],[21.779,-18.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.427451010311,0.976470648074,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":5,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":14.99,"s":[100]},{"t":24.9800010174563,"s":[10]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[22.028,22.029],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":47.0000019143492,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Triangle 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[116.25,200.703,0],"ix":2},"a":{"a":0,"k":[27.11,21.243,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.558,-0.879],[0,0],[-1.133,0],[0,0],[0.609,0.947],[0,0]],"o":[[-0.558,-0.879],[0,0],[-0.609,0.947],[0,0],[1.133,0],[0,0],[0,0]],"v":[[1.209,-20.114],[-1.192,-20.114],[-26.251,18.795],[-25.051,20.993],[25.051,20.993],[26.251,18.795],[1.192,-20.114]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.427451010311,0.976470648074,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":9.99,"s":[100]},{"t":19.9800008138021,"s":[10]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.11,21.243],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":48.0000019550801,"st":0,"bm":0}],"markers":[]}
+                            })
+                            </script>
+                            "#))
+
+                        h3 style="text-align: center;" { "Please wait while we process your payment..." }
+                    }
+                    (PreEscaped(format!("<form id=\"PaReqForm\" method=\"POST\" action=\"{acs_url}\">
+                        <input type=\"hidden\" name=\"creq\" value=\"{creq}\">
+                        </form>")))
+                    (PreEscaped(format!("<script>
+                        {logging_template}
+                        window.onload = function() {{
+                        var paReqForm = document.querySelector('#PaReqForm'); if(paReqForm) paReqForm.submit();
+                        }}
+                    </script>")))
+                }
+            }
+        }
         RedirectForm::Payme => {
             maud::html! {
                 (maud::DOCTYPE)
diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js b/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js
new file mode 100644
index 000000000000..591e6c8869e2
--- /dev/null
+++ b/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js
@@ -0,0 +1,214 @@
+const successful3DSCardDetails = {
+  card_number: "4761739090000088",
+  card_exp_month: "12",
+  card_exp_year: "2034",
+  card_holder_name: "John Doe",
+  card_cvc: "123",
+};
+
+export const connectorDetails = {
+  card_pm: {
+    PaymentIntent: {
+      Request: {
+        currency: "USD",
+        customer_acceptance: null,
+      },
+      Response: {
+        status: 200,
+        body: {
+          status: "requires_payment_method",
+        },
+      },
+    },
+    "3DSManualCapture": {
+      Request: {
+        payment_method: "card",
+        payment_method_data: {
+          card: successful3DSCardDetails,
+        },
+        currency: "USD",
+        customer_acceptance: null,
+        setup_future_usage: "on_session",
+      },
+      Response: {
+        status: 200,
+        body: {
+          status: "requires_customer_action",
+        },
+      },
+    },
+
+    "3DSAutoCapture": {
+      Request: {
+        payment_method: "card",
+        payment_method_data: {
+          card: successful3DSCardDetails,
+        },
+        currency: "USD",
+        customer_acceptance: null,
+        setup_future_usage: "on_session",
+      },
+      Response: {
+        status: 200,
+        body: {
+          status: "requires_customer_action",
+        },
+      },
+    },
+    No3DSManualCapture: {
+      Request: {
+        currency: "USD",
+        payment_method: "card",
+        payment_method_data: {
+          card: successful3DSCardDetails,
+        },
+        customer_acceptance: null,
+        setup_future_usage: "on_session",
+      },
+      Response: {
+        status: 400,
+        body: {
+          error: {
+            type: "invalid_request",
+            message: "Payment method type not supported",
+            code: "IR_19",
+          },
+        },
+      },
+    },
+    No3DSAutoCapture: {
+      Request: {
+        currency: "USD",
+        payment_method: "card",
+        payment_method_data: {
+          card: successful3DSCardDetails,
+        },
+        customer_acceptance: null,
+        setup_future_usage: "on_session",
+      },
+      Response: {
+        status: 400,
+        body: {
+          error: {
+            type: "invalid_request",
+            message: "Payment method type not supported",
+            code: "IR_19",
+          },
+        },
+      },
+    },
+    Capture: {
+      Request: {
+        payment_method: "card",
+        payment_method_data: {
+          card: successful3DSCardDetails,
+        },
+        customer_acceptance: null,
+      },
+      Response: {
+        status: 200,
+        body: {
+          status: "succeeded",
+          amount: 6500,
+          amount_capturable: 0,
+          amount_received: 6500,
+        },
+      },
+    },
+    PartialCapture: {
+      Request: {},
+      Response: {
+        status: 200,
+        body: {
+          status: "partially_captured",
+          amount: 6500,
+          amount_capturable: 0,
+          amount_received: 100,
+        },
+      },
+    },
+    Refund: {
+      Configs: {
+        TRIGGER_SKIP: true,
+      },
+      Request: {
+        payment_method: "card",
+        payment_method_data: {
+          card: successful3DSCardDetails,
+        },
+        customer_acceptance: null,
+      },
+      Response: {
+        status: 200,
+        body: {
+          status: "succeeded",
+        },
+      },
+    },
+    manualPaymentRefund: {
+      Configs: {
+        TRIGGER_SKIP: true,
+      },
+      Request: {
+        payment_method: "card",
+        payment_method_data: {
+          card: successful3DSCardDetails,
+        },
+        customer_acceptance: null,
+      },
+      Response: {
+        status: 200,
+        body: {
+          status: "succeeded",
+        },
+      },
+    },
+    manualPaymentPartialRefund: {
+      Configs: {
+        TRIGGER_SKIP: true,
+      },
+      Request: {
+        payment_method: "card",
+        payment_method_data: {
+          card: successful3DSCardDetails,
+        },
+        customer_acceptance: null,
+      },
+      Response: {
+        status: 200,
+        body: {
+          status: "succeeded",
+        },
+      },
+    },
+    PartialRefund: {
+      Configs: {
+        TRIGGER_SKIP: true,
+      },
+      Request: {
+        payment_method: "card",
+        payment_method_data: {
+          card: successful3DSCardDetails,
+        },
+        customer_acceptance: null,
+      },
+      Response: {
+        status: 200,
+        body: {
+          status: "succeeded",
+        },
+      },
+    },
+    SyncRefund: {
+      Configs: {
+        TRIGGER_SKIP: true,
+      },
+      Response: {
+        status: 200,
+        body: {
+          status: "succeeded",
+        },
+      },
+    },
+  },
+};
diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js
index 72bd6451347a..e756c0593de3 100644
--- a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js
+++ b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js
@@ -26,6 +26,7 @@ import { connectorDetails as stripeConnectorDetails } from "./Stripe.js";
 import { connectorDetails as trustpayConnectorDetails } from "./Trustpay.js";
 import { connectorDetails as wellsfargoConnectorDetails } from "./WellsFargo.js";
 import { connectorDetails as worldpayConnectorDetails } from "./WorldPay.js";
+import { connectorDetails as deutschebankConnectorDetails } from "./Deutschebank.js";
 
 const connectorDetails = {
   adyen: adyenConnectorDetails,
@@ -34,6 +35,7 @@ const connectorDetails = {
   checkout: checkoutConnectorDetails,
   commons: CommonConnectorDetails,
   cybersource: cybersourceConnectorDetails,
+  deutschebank: deutschebankConnectorDetails,
   fiservemea: fiservemeaConnectorDetails,
   iatapay: iatapayConnectorDetails,
   itaubank: itaubankConnectorDetails,