Bug: [FEATURE] : [Connector] Implement Revoke Mandate Flow



### Feature Description

When revoke-mandate endpoint is triggered, the mandate should be revoked with connector also. Currently it is being revoked  only at core level which is a feature gap.

### Possible Implementation

Add a `execute_connector_processing_step` and a flow to revoke the mandate at connector level

### 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/crates/api_models/src/mandates.rs b/crates/api_models/src/mandates.rs
index 035f7adec9f..5c0810dc21b 100644
--- a/crates/api_models/src/mandates.rs
+++ b/crates/api_models/src/mandates.rs
@@ -17,6 +17,12 @@ pub struct MandateRevokedResponse {
     /// The status for mandates
     #[schema(value_type = MandateStatus)]
     pub status: api_enums::MandateStatus,
+    /// If there was an error while calling the connectors the code is received here
+    #[schema(example = "E0001")]
+    pub error_code: Option<String>,
+    /// If there was an error while calling the connector the error message is received here
+    #[schema(example = "Failed while verifying the card")]
+    pub error_message: Option<String>,
 }
 
 #[derive(Default, Debug, Deserialize, Serialize, ToSchema, Clone)]
diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs
index 3496f2483ab..33503102e4b 100644
--- a/crates/router/src/connector/cybersource.rs
+++ b/crates/router/src/connector/cybersource.rs
@@ -55,6 +55,7 @@ impl Cybersource {
         } = auth;
         let is_post_method = matches!(http_method, services::Method::Post);
         let is_patch_method = matches!(http_method, services::Method::Patch);
+        let is_delete_method = matches!(http_method, services::Method::Delete);
         let digest_str = if is_post_method || is_patch_method {
             "digest "
         } else {
@@ -65,6 +66,8 @@ impl Cybersource {
             format!("(request-target): post {resource}\ndigest: SHA-256={payload}\n")
         } else if is_patch_method {
             format!("(request-target): patch {resource}\ndigest: SHA-256={payload}\n")
+        } else if is_delete_method {
+            format!("(request-target): delete {resource}\n")
         } else {
             format!("(request-target): get {resource}\n")
         };
@@ -162,6 +165,28 @@ impl ConnectorCommon for Cybersource {
                     connector_transaction_id: None,
                 })
             }
+            transformers::CybersourceErrorResponse::NotAvailableError(response) => {
+                let error_response = response
+                    .errors
+                    .iter()
+                    .map(|error_info| {
+                        format!(
+                            "{}: {}",
+                            error_info.error_type.clone().unwrap_or("".to_string()),
+                            error_info.message.clone().unwrap_or("".to_string())
+                        )
+                    })
+                    .collect::<Vec<String>>()
+                    .join(" & ");
+                Ok(types::ErrorResponse {
+                    status_code: res.status_code,
+                    code: consts::NO_ERROR_CODE.to_string(),
+                    message: error_response.clone(),
+                    reason: Some(error_response),
+                    attempt_status: None,
+                    connector_transaction_id: None,
+                })
+            }
         }
     }
 }
@@ -261,6 +286,7 @@ impl api::PaymentIncrementalAuthorization for Cybersource {}
 impl api::MandateSetup for Cybersource {}
 impl api::ConnectorAccessToken for Cybersource {}
 impl api::PaymentToken for Cybersource {}
+impl api::ConnectorMandateRevoke for Cybersource {}
 
 impl
     ConnectorIntegration<
@@ -347,6 +373,92 @@ impl
     }
 }
 
+impl
+    ConnectorIntegration<
+        api::MandateRevoke,
+        types::MandateRevokeRequestData,
+        types::MandateRevokeResponseData,
+    > for Cybersource
+{
+    fn get_headers(
+        &self,
+        req: &types::MandateRevokeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
+    fn get_http_method(&self) -> services::Method {
+        services::Method::Delete
+    }
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+    fn get_url(
+        &self,
+        req: &types::MandateRevokeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!(
+            "{}tms/v1/paymentinstruments/{}",
+            self.base_url(connectors),
+            connector_utils::RevokeMandateRequestData::get_connector_mandate_id(&req.request)?
+        ))
+    }
+    fn build_request(
+        &self,
+        req: &types::MandateRevokeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Delete)
+                .url(&types::MandateRevokeType::get_url(self, req, connectors)?)
+                .attach_default_headers()
+                .headers(types::MandateRevokeType::get_headers(
+                    self, req, connectors,
+                )?)
+                .build(),
+        ))
+    }
+    fn handle_response(
+        &self,
+        data: &types::MandateRevokeRouterData,
+        res: types::Response,
+    ) -> CustomResult<types::MandateRevokeRouterData, errors::ConnectorError> {
+        if matches!(res.status_code, 204) {
+            Ok(types::MandateRevokeRouterData {
+                response: Ok(types::MandateRevokeResponseData {
+                    mandate_status: common_enums::MandateStatus::Revoked,
+                }),
+                ..data.clone()
+            })
+        } else {
+            // If http_code != 204 || http_code != 4xx, we dont know any other response scenario yet.
+            let response_value: serde_json::Value = serde_json::from_slice(&res.response)
+                .into_report()
+                .change_context(errors::ConnectorError::ResponseHandlingFailed)?;
+            let response_string = response_value.to_string();
+
+            Ok(types::MandateRevokeRouterData {
+                response: Err(types::ErrorResponse {
+                    code: consts::NO_ERROR_CODE.to_string(),
+                    message: response_string.clone(),
+                    reason: Some(response_string),
+                    status_code: res.status_code,
+                    attempt_status: None,
+                    connector_transaction_id: None,
+                }),
+                ..data.clone()
+            })
+        }
+    }
+    fn get_error_response(
+        &self,
+        res: types::Response,
+    ) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
 impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
     for Cybersource
 {
diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs
index a5a0a7237ef..cf769f1a2fd 100644
--- a/crates/router/src/connector/cybersource/transformers.rs
+++ b/crates/router/src/connector/cybersource/transformers.rs
@@ -1742,6 +1742,20 @@ pub struct CybersourceStandardErrorResponse {
     pub details: Option<Vec<Details>>,
 }
 
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CybersourceNotAvailableErrorResponse {
+    pub errors: Vec<CybersourceNotAvailableErrorObject>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CybersourceNotAvailableErrorObject {
+    #[serde(rename = "type")]
+    pub error_type: Option<String>,
+    pub message: Option<String>,
+}
+
 #[derive(Debug, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct CybersourceServerErrorResponse {
@@ -1766,8 +1780,10 @@ pub struct CybersourceAuthenticationErrorResponse {
 #[derive(Debug, Deserialize)]
 #[serde(untagged)]
 pub enum CybersourceErrorResponse {
-    StandardError(CybersourceStandardErrorResponse),
     AuthenticationError(CybersourceAuthenticationErrorResponse),
+    //If the request resource is not available/exists in cybersource
+    NotAvailableError(CybersourceNotAvailableErrorResponse),
+    StandardError(CybersourceStandardErrorResponse),
 }
 
 #[derive(Debug, Deserialize, Clone)]
diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs
index 0dca3ace947..c44f8cd391e 100644
--- a/crates/router/src/connector/utils.rs
+++ b/crates/router/src/connector/utils.rs
@@ -335,6 +335,18 @@ impl PaymentsCaptureRequestData for types::PaymentsCaptureData {
     }
 }
 
+pub trait RevokeMandateRequestData {
+    fn get_connector_mandate_id(&self) -> Result<String, Error>;
+}
+
+impl RevokeMandateRequestData for types::MandateRevokeRequestData {
+    fn get_connector_mandate_id(&self) -> Result<String, Error> {
+        self.connector_mandate_id
+            .clone()
+            .ok_or_else(missing_field_err("connector_mandate_id"))
+    }
+}
+
 pub trait PaymentsSetupMandateRequestData {
     fn get_browser_info(&self) -> Result<BrowserInformation, Error>;
     fn get_email(&self) -> Result<Email, Error>;
diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs
index 55de11549a4..aabd846660c 100644
--- a/crates/router/src/core/mandate.rs
+++ b/crates/router/src/core/mandate.rs
@@ -1,13 +1,18 @@
+pub mod utils;
+
 use api_models::payments;
 use common_utils::{ext_traits::Encode, pii};
 use diesel_models::enums as storage_enums;
-use error_stack::{report, ResultExt};
+use error_stack::{report, IntoReport, ResultExt};
 use futures::future;
 use router_env::{instrument, logger, tracing};
 
 use super::payments::helpers;
 use crate::{
-    core::errors::{self, RouterResponse, StorageErrorExt},
+    core::{
+        errors::{self, RouterResponse, StorageErrorExt},
+        payments::CallConnectorAction,
+    },
     db::StorageInterface,
     routes::{metrics, AppState},
     services,
@@ -16,6 +21,7 @@ use crate::{
         api::{
             customers,
             mandates::{self, MandateResponseExt},
+            ConnectorData, GetToken,
         },
         domain, storage,
         transformers::ForeignTryFrom,
@@ -44,26 +50,121 @@ pub async fn get_mandate(
 pub async fn revoke_mandate(
     state: AppState,
     merchant_account: domain::MerchantAccount,
+    key_store: domain::MerchantKeyStore,
     req: mandates::MandateId,
 ) -> RouterResponse<mandates::MandateRevokedResponse> {
     let db = state.store.as_ref();
     let mandate = db
-        .update_mandate_by_merchant_id_mandate_id(
-            &merchant_account.merchant_id,
-            &req.mandate_id,
-            storage::MandateUpdate::StatusUpdate {
-                mandate_status: storage::enums::MandateStatus::Revoked,
-            },
-        )
+        .find_mandate_by_merchant_id_mandate_id(&merchant_account.merchant_id, &req.mandate_id)
         .await
         .to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?;
 
-    Ok(services::ApplicationResponse::Json(
-        mandates::MandateRevokedResponse {
-            mandate_id: mandate.mandate_id,
-            status: mandate.mandate_status,
-        },
-    ))
+    let mandate_revoke_status = match mandate.mandate_status {
+        common_enums::MandateStatus::Active
+        | common_enums::MandateStatus::Inactive
+        | common_enums::MandateStatus::Pending => {
+            let profile_id = if let Some(ref payment_id) = mandate.original_payment_id {
+                let pi = db
+                    .find_payment_intent_by_payment_id_merchant_id(
+                        payment_id,
+                        &merchant_account.merchant_id,
+                        merchant_account.storage_scheme,
+                    )
+                    .await
+                    .change_context(errors::ApiErrorResponse::PaymentNotFound)?;
+                let profile_id = pi.profile_id.clone().ok_or(
+                    errors::ApiErrorResponse::BusinessProfileNotFound {
+                        id: pi
+                            .profile_id
+                            .unwrap_or_else(|| "Profile id is Null".to_string()),
+                    },
+                )?;
+                Ok(profile_id)
+            } else {
+                Err(errors::ApiErrorResponse::PaymentNotFound)
+            }?;
+
+            let merchant_connector_account = helpers::get_merchant_connector_account(
+                &state,
+                &merchant_account.merchant_id,
+                None,
+                &key_store,
+                &profile_id,
+                &mandate.connector,
+                mandate.merchant_connector_id.as_ref(),
+            )
+            .await?;
+
+            let connector_data = ConnectorData::get_connector_by_name(
+                &state.conf.connectors,
+                &mandate.connector,
+                GetToken::Connector,
+                mandate.merchant_connector_id.clone(),
+            )?;
+            let connector_integration: services::BoxedConnectorIntegration<
+                '_,
+                types::api::MandateRevoke,
+                types::MandateRevokeRequestData,
+                types::MandateRevokeResponseData,
+            > = connector_data.connector.get_connector_integration();
+
+            let router_data = utils::construct_mandate_revoke_router_data(
+                merchant_connector_account,
+                &merchant_account,
+                mandate.clone(),
+            )
+            .await?;
+
+            let response = services::execute_connector_processing_step(
+                &state,
+                connector_integration,
+                &router_data,
+                CallConnectorAction::Trigger,
+                None,
+            )
+            .await
+            .change_context(errors::ApiErrorResponse::InternalServerError)?;
+
+            match response.response {
+                Ok(_) => {
+                    let update_mandate = db
+                        .update_mandate_by_merchant_id_mandate_id(
+                            &merchant_account.merchant_id,
+                            &req.mandate_id,
+                            storage::MandateUpdate::StatusUpdate {
+                                mandate_status: storage::enums::MandateStatus::Revoked,
+                            },
+                        )
+                        .await
+                        .to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?;
+                    Ok(services::ApplicationResponse::Json(
+                        mandates::MandateRevokedResponse {
+                            mandate_id: update_mandate.mandate_id,
+                            status: update_mandate.mandate_status,
+                            error_code: None,
+                            error_message: None,
+                        },
+                    ))
+                }
+
+                Err(err) => Err(errors::ApiErrorResponse::ExternalConnectorError {
+                    code: err.code,
+                    message: err.message,
+                    connector: mandate.connector,
+                    status_code: err.status_code,
+                    reason: err.reason,
+                })
+                .into_report(),
+            }
+        }
+        common_enums::MandateStatus::Revoked => {
+            Err(errors::ApiErrorResponse::MandateValidationFailed {
+                reason: "Mandate has already been revoked".to_string(),
+            })
+            .into_report()
+        }
+    };
+    mandate_revoke_status
 }
 
 #[instrument(skip(db))]
diff --git a/crates/router/src/core/mandate/utils.rs b/crates/router/src/core/mandate/utils.rs
new file mode 100644
index 00000000000..25267db1a96
--- /dev/null
+++ b/crates/router/src/core/mandate/utils.rs
@@ -0,0 +1,76 @@
+use std::marker::PhantomData;
+
+use common_utils::{errors::CustomResult, ext_traits::ValueExt};
+use diesel_models::Mandate;
+use error_stack::ResultExt;
+
+use crate::{
+    core::{errors, payments::helpers},
+    types::{self, domain, PaymentAddress},
+};
+const IRRELEVANT_PAYMENT_ID_IN_MANDATE_REVOKE_FLOW: &str =
+    "irrelevant_payment_id_in_mandate_revoke_flow";
+
+const IRRELEVANT_ATTEMPT_ID_IN_MANDATE_REVOKE_FLOW: &str =
+    "irrelevant_attempt_id_in_mandate_revoke_flow";
+
+const IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_MANDATE_REVOKE_FLOW: &str =
+    "irrelevant_connector_request_reference_id_in_mandate_revoke_flow";
+
+pub async fn construct_mandate_revoke_router_data(
+    merchant_connector_account: helpers::MerchantConnectorAccountType,
+    merchant_account: &domain::MerchantAccount,
+    mandate: Mandate,
+) -> CustomResult<types::MandateRevokeRouterData, errors::ApiErrorResponse> {
+    let auth_type: types::ConnectorAuthType = merchant_connector_account
+        .get_connector_account_details()
+        .parse_value("ConnectorAuthType")
+        .change_context(errors::ApiErrorResponse::InternalServerError)?;
+    let router_data = types::RouterData {
+        flow: PhantomData,
+        merchant_id: merchant_account.merchant_id.clone(),
+        customer_id: Some(mandate.customer_id),
+        connector_customer: None,
+        connector: mandate.connector,
+        payment_id: mandate
+            .original_payment_id
+            .unwrap_or_else(|| IRRELEVANT_PAYMENT_ID_IN_MANDATE_REVOKE_FLOW.to_string()),
+        attempt_id: IRRELEVANT_ATTEMPT_ID_IN_MANDATE_REVOKE_FLOW.to_string(),
+        status: diesel_models::enums::AttemptStatus::default(),
+        payment_method: diesel_models::enums::PaymentMethod::default(),
+        connector_auth_type: auth_type,
+        description: None,
+        return_url: None,
+        address: PaymentAddress::default(),
+        auth_type: diesel_models::enums::AuthenticationType::default(),
+        connector_meta_data: None,
+        amount_captured: None,
+        access_token: None,
+        session_token: None,
+        reference_id: None,
+        payment_method_token: None,
+        recurring_mandate_payment_data: None,
+        preprocessing_id: None,
+        payment_method_balance: None,
+        connector_api_version: None,
+        request: types::MandateRevokeRequestData {
+            mandate_id: mandate.mandate_id,
+            connector_mandate_id: mandate.connector_mandate_id,
+        },
+        response: Err(types::ErrorResponse::get_not_implemented()),
+        payment_method_id: None,
+        connector_request_reference_id:
+            IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_MANDATE_REVOKE_FLOW.to_string(),
+        test_mode: None,
+        connector_http_status_code: None,
+        external_latency: None,
+        apple_pay_flow: None,
+        frm_metadata: None,
+        #[cfg(feature = "payouts")]
+        payout_method_data: None,
+        #[cfg(feature = "payouts")]
+        quote_id: None,
+    };
+
+    Ok(router_data)
+}
diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs
index ec8e13cff50..27ddd3f6d81 100644
--- a/crates/router/src/core/payments/flows.rs
+++ b/crates/router/src/core/payments/flows.rs
@@ -2197,3 +2197,83 @@ default_imp_for_incremental_authorization!(
     connector::Worldpay,
     connector::Zen
 );
+
+macro_rules! default_imp_for_revoking_mandates {
+    ($($path:ident::$connector:ident),*) => {
+        $( impl api::ConnectorMandateRevoke for $path::$connector {}
+            impl
+            services::ConnectorIntegration<
+            api::MandateRevoke,
+            types::MandateRevokeRequestData,
+            types::MandateRevokeResponseData,
+        > for $path::$connector
+        {}
+    )*
+    };
+}
+
+#[cfg(feature = "dummy_connector")]
+impl<const T: u8> api::ConnectorMandateRevoke for connector::DummyConnector<T> {}
+#[cfg(feature = "dummy_connector")]
+impl<const T: u8>
+    services::ConnectorIntegration<
+        api::MandateRevoke,
+        types::MandateRevokeRequestData,
+        types::MandateRevokeResponseData,
+    > for connector::DummyConnector<T>
+{
+}
+default_imp_for_revoking_mandates!(
+    connector::Aci,
+    connector::Adyen,
+    connector::Airwallex,
+    connector::Authorizedotnet,
+    connector::Bambora,
+    connector::Bankofamerica,
+    connector::Bitpay,
+    connector::Bluesnap,
+    connector::Boku,
+    connector::Braintree,
+    connector::Cashtocode,
+    connector::Checkout,
+    connector::Cryptopay,
+    connector::Coinbase,
+    connector::Dlocal,
+    connector::Fiserv,
+    connector::Forte,
+    connector::Globalpay,
+    connector::Globepay,
+    connector::Gocardless,
+    connector::Helcim,
+    connector::Iatapay,
+    connector::Klarna,
+    connector::Mollie,
+    connector::Multisafepay,
+    connector::Nexinets,
+    connector::Nmi,
+    connector::Noon,
+    connector::Nuvei,
+    connector::Opayo,
+    connector::Opennode,
+    connector::Payeezy,
+    connector::Payme,
+    connector::Paypal,
+    connector::Payu,
+    connector::Placetopay,
+    connector::Powertranz,
+    connector::Prophetpay,
+    connector::Rapyd,
+    connector::Riskified,
+    connector::Signifyd,
+    connector::Square,
+    connector::Stax,
+    connector::Stripe,
+    connector::Shift4,
+    connector::Trustpay,
+    connector::Tsys,
+    connector::Volt,
+    connector::Wise,
+    connector::Worldline,
+    connector::Worldpay,
+    connector::Zen
+);
diff --git a/crates/router/src/routes/mandates.rs b/crates/router/src/routes/mandates.rs
index 1e446136297..ecc89a10fa2 100644
--- a/crates/router/src/routes/mandates.rs
+++ b/crates/router/src/routes/mandates.rs
@@ -80,7 +80,9 @@ pub async fn revoke_mandate(
         state,
         &req,
         mandate_id,
-        |state, auth, req| mandate::revoke_mandate(state, auth.merchant_account, req),
+        |state, auth, req| {
+            mandate::revoke_mandate(state, auth.merchant_account, auth.key_store, req)
+        },
         &auth::ApiKeyAuth,
         api_locking::LockAction::NotApplicable,
     )
diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs
index 50aba3de7b5..2225c2965bc 100644
--- a/crates/router/src/types.rs
+++ b/crates/router/src/types.rs
@@ -19,8 +19,9 @@ use std::{collections::HashMap, marker::PhantomData};
 
 pub use api_models::{
     enums::{Connector, PayoutConnectors},
-    payouts as payout_types,
+    mandates, payouts as payout_types,
 };
+use common_enums::MandateStatus;
 pub use common_utils::request::{RequestBody, RequestContent};
 use common_utils::{pii, pii::Email};
 use data_models::mandates::MandateData;
@@ -117,6 +118,11 @@ pub type SetupMandateType = dyn services::ConnectorIntegration<
     SetupMandateRequestData,
     PaymentsResponseData,
 >;
+pub type MandateRevokeType = dyn services::ConnectorIntegration<
+    api::MandateRevoke,
+    MandateRevokeRequestData,
+    MandateRevokeResponseData,
+>;
 pub type PaymentsPreProcessingType = dyn services::ConnectorIntegration<
     api::PreProcessing,
     PaymentsPreProcessingData,
@@ -246,6 +252,9 @@ pub type RetrieveFileRouterData =
 pub type DefendDisputeRouterData =
     RouterData<api::Defend, DefendDisputeRequestData, DefendDisputeResponse>;
 
+pub type MandateRevokeRouterData =
+    RouterData<api::MandateRevoke, MandateRevokeRequestData, MandateRevokeResponseData>;
+
 #[cfg(feature = "payouts")]
 pub type PayoutsRouterData<F> = RouterData<F, PayoutsData, PayoutsResponseData>;
 
@@ -373,8 +382,8 @@ pub struct PayoutsFulfillResponseData {
 #[derive(Debug, Clone)]
 pub struct PaymentsAuthorizeData {
     pub payment_method_data: payments::PaymentMethodData,
-    /// total amount (original_amount + surcharge_amount + tax_on_surcharge_amount)  
-    /// If connector supports separate field for surcharge amount, consider using below functions defined on `PaymentsAuthorizeData` to fetch original amount and surcharge amount separately  
+    /// total amount (original_amount + surcharge_amount + tax_on_surcharge_amount)
+    /// If connector supports separate field for surcharge amount, consider using below functions defined on `PaymentsAuthorizeData` to fetch original amount and surcharge amount separately
     /// ```
     /// get_original_amount()
     /// get_surcharge_amount()
@@ -957,6 +966,17 @@ pub struct ResponseRouterData<Flow, R, Request, Response> {
     pub http_code: u16,
 }
 
+#[derive(Debug, Clone)]
+pub struct MandateRevokeRequestData {
+    pub mandate_id: String,
+    pub connector_mandate_id: Option<String>,
+}
+
+#[derive(Debug, Clone)]
+pub struct MandateRevokeResponseData {
+    pub mandate_status: MandateStatus,
+}
+
 // Different patterns of authentication.
 #[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
 #[serde(tag = "auth_type")]
diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs
index fcbd3801c94..b60153eaf19 100644
--- a/crates/router/src/types/api.rs
+++ b/crates/router/src/types/api.rs
@@ -70,6 +70,18 @@ pub trait ConnectorVerifyWebhookSource:
 {
 }
 
+#[derive(Clone, Debug)]
+pub struct MandateRevoke;
+
+pub trait ConnectorMandateRevoke:
+    ConnectorIntegration<
+    MandateRevoke,
+    types::MandateRevokeRequestData,
+    types::MandateRevokeResponseData,
+>
+{
+}
+
 pub trait ConnectorTransactionId: ConnectorCommon + Sync {
     fn connector_transaction_id(
         &self,
@@ -159,6 +171,7 @@ pub trait Connector:
     + Payouts
     + ConnectorVerifyWebhookSource
     + FraudCheck
+    + ConnectorMandateRevoke
 {
 }
 
@@ -179,7 +192,8 @@ impl<
             + ConnectorTransactionId
             + Payouts
             + ConnectorVerifyWebhookSource
-            + FraudCheck,
+            + FraudCheck
+            + ConnectorMandateRevoke,
     > Connector for T
 {
 }
diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json
index bf373220fde..d7942c17447 100644
--- a/openapi/openapi_spec.json
+++ b/openapi/openapi_spec.json
@@ -6746,6 +6746,18 @@
           },
           "status": {
             "$ref": "#/components/schemas/MandateStatus"
+          },
+          "error_code": {
+            "type": "string",
+            "description": "If there was an error while calling the connectors the code is received here",
+            "example": "E0001",
+            "nullable": true
+          },
+          "error_message": {
+            "type": "string",
+            "description": "If there was an error while calling the connector the error message is received here",
+            "example": "Failed while verifying the card",
+            "nullable": true
           }
         }
       },

## 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 -->
Add Revoke Mandate flow: When the merchant invokes the revoke-mandate endpoint, the mandate should be revoked at both the connector level and core level.

### 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).
-->


## 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)?
-->
- Create a mandate (zero and non-zero) with the connector
- Make Payment using the mandates
- Revoke the mandate
- Try to revoke an already revoked mandate

### Revoke a Mandate
<img width="1119" alt="Screenshot 2024-01-08 at 12 26 34 PM" src="https://github.com/juspay/hyperswitch/assets/55536657/ed66827d-c74c-4b39-9e42-9bae5d15dc31">

### Try to revoke already revoked mandate
<img width="1123" alt="Screenshot 2024-01-08 at 12 26 45 PM" src="https://github.com/juspay/hyperswitch/assets/55536657/c46cc8a6-26cc-49f0-b3b4-78565364a85e">

### Make Payment with revoked mandate
<img width="1100" alt="Screenshot 2024-01-08 at 12 27 18 PM" src="https://github.com/juspay/hyperswitch/assets/55536657/e74ab57a-27f7-46e8-b01d-46af4b164ed7">

### Handle Error Response from connector
<img width="1160" alt="Screenshot 2024-01-08 at 3 28 17 PM" src="https://github.com/juspay/hyperswitch/assets/55536657/9668538d-c83e-4e64-98d6-f8a6a08e91d8">

### Not implemented Error
<img width="1177" alt="Screenshot 2024-01-08 at 5 58 31 PM" src="https://github.com/juspay/hyperswitch/assets/55536657/82de1e70-6b9f-4d73-a4ec-389b44d69239">


## 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
