Bug: [FEATURE]: [Noon] Implement Revoke Mandate



### Feature Description

When revoke mandate api is triggered, revoke the existing connector mandate with noon also

### Possible Implementation

Implement Revoke Mandate for Noon

### 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/router/src/connector/noon.rs b/crates/router/src/connector/noon.rs
index 180b4b1485f..6c98a307637 100644
--- a/crates/router/src/connector/noon.rs
+++ b/crates/router/src/connector/noon.rs
@@ -46,6 +46,7 @@ impl api::Refund for Noon {}
 impl api::RefundExecute for Noon {}
 impl api::RefundSync for Noon {}
 impl api::PaymentToken for Noon {}
+impl api::ConnectorMandateRevoke for Noon {}
 
 impl
     ConnectorIntegration<
@@ -492,6 +493,81 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
     }
 }
 
+impl
+    ConnectorIntegration<
+        api::MandateRevoke,
+        types::MandateRevokeRequestData,
+        types::MandateRevokeResponseData,
+    > for Noon
+{
+    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_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!("{}payment/v1/order", self.base_url(connectors)))
+    }
+    fn build_request(
+        &self,
+        req: &types::MandateRevokeRouterData,
+        connectors: &settings::Connectors,
+    ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
+        Ok(Some(
+            services::RequestBuilder::new()
+                .method(services::Method::Post)
+                .url(&types::MandateRevokeType::get_url(self, req, connectors)?)
+                .attach_default_headers()
+                .headers(types::MandateRevokeType::get_headers(
+                    self, req, connectors,
+                )?)
+                .set_body(types::MandateRevokeType::get_request_body(
+                    self, req, connectors,
+                )?)
+                .build(),
+        ))
+    }
+    fn get_request_body(
+        &self,
+        req: &types::MandateRevokeRouterData,
+        _connectors: &settings::Connectors,
+    ) -> CustomResult<RequestContent, errors::ConnectorError> {
+        let connector_req = noon::NoonRevokeMandateRequest::try_from(req)?;
+        Ok(RequestContent::Json(Box::new(connector_req)))
+    }
+
+    fn handle_response(
+        &self,
+        data: &types::MandateRevokeRouterData,
+        res: Response,
+    ) -> CustomResult<types::MandateRevokeRouterData, errors::ConnectorError> {
+        let response: noon::NoonRevokeMandateResponse = res
+            .response
+            .parse_struct("Noon NoonRevokeMandateResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        types::RouterData::try_from(types::ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+    }
+    fn get_error_response(
+        &self,
+        res: Response,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res)
+    }
+}
+
 impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Noon {
     fn get_headers(
         &self,
diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs
index bbf284848b5..ee06cd064be 100644
--- a/crates/router/src/connector/noon/transformers.rs
+++ b/crates/router/src/connector/noon/transformers.rs
@@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize};
 
 use crate::{
     connector::utils::{
-        self as conn_utils, CardData, PaymentsAuthorizeRequestData, RouterData, WalletData,
+        self as conn_utils, CardData, PaymentsAuthorizeRequestData, RevokeMandateRequestData,
+        RouterData, WalletData,
     },
     core::errors,
     services,
@@ -30,11 +31,13 @@ pub enum NoonSubscriptionType {
 }
 
 #[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
 pub struct NoonSubscriptionData {
     #[serde(rename = "type")]
     subscription_type: NoonSubscriptionType,
     //Short description about the subscription.
     name: String,
+    max_amount: Option<String>,
 }
 
 #[derive(Debug, Serialize)]
@@ -168,12 +171,13 @@ pub enum NoonPaymentData {
 }
 
 #[derive(Debug, Serialize)]
-#[serde(rename_all = "UPPERCASE")]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
 pub enum NoonApiOperations {
     Initiate,
     Capture,
     Reverse,
     Refund,
+    CancelSubscription,
 }
 #[derive(Debug, Serialize)]
 #[serde(rename_all = "camelCase")]
@@ -335,6 +339,21 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest {
                 NoonSubscriptionData {
                     subscription_type: NoonSubscriptionType::Unscheduled,
                     name: name.clone(),
+                    max_amount: item
+                        .request
+                        .setup_mandate_details
+                        .clone()
+                        .and_then(|mandate_details| match mandate_details.mandate_type {
+                            Some(data_models::mandates::MandateDataType::SingleUse(mandate))
+                            | Some(data_models::mandates::MandateDataType::MultiUse(Some(
+                                mandate,
+                            ))) => Some(
+                                conn_utils::to_currency_base_unit(mandate.amount, mandate.currency)
+                                    .ok(),
+                            ),
+                            _ => None,
+                        })
+                        .flatten(),
                 },
                 true,
             )) {
@@ -450,7 +469,7 @@ impl ForeignFrom<(NoonPaymentStatus, Self)> for enums::AttemptStatus {
 }
 
 #[derive(Debug, Serialize, Deserialize)]
-pub struct NoonSubscriptionResponse {
+pub struct NoonSubscriptionObject {
     identifier: String,
 }
 
@@ -475,7 +494,7 @@ pub struct NoonCheckoutData {
 pub struct NoonPaymentsResponseResult {
     order: NoonPaymentsOrderResponse,
     checkout_data: Option<NoonCheckoutData>,
-    subscription: Option<NoonSubscriptionResponse>,
+    subscription: Option<NoonSubscriptionObject>,
 }
 
 #[derive(Debug, Serialize, Deserialize)]
@@ -603,6 +622,25 @@ impl TryFrom<&types::PaymentsCancelRouterData> for NoonPaymentsCancelRequest {
     }
 }
 
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct NoonRevokeMandateRequest {
+    api_operation: NoonApiOperations,
+    subscription: NoonSubscriptionObject,
+}
+
+impl TryFrom<&types::MandateRevokeRouterData> for NoonRevokeMandateRequest {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(item: &types::MandateRevokeRouterData) -> Result<Self, Self::Error> {
+        Ok(Self {
+            api_operation: NoonApiOperations::CancelSubscription,
+            subscription: NoonSubscriptionObject {
+                identifier: item.request.get_connector_mandate_id()?,
+            },
+        })
+    }
+}
+
 impl<F> TryFrom<&types::RefundsRouterData<F>> for NoonPaymentsActionRequest {
     type Error = error_stack::Report<errors::ConnectorError>;
     fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
@@ -624,6 +662,55 @@ impl<F> TryFrom<&types::RefundsRouterData<F>> for NoonPaymentsActionRequest {
         })
     }
 }
+#[derive(Debug, Deserialize)]
+pub enum NoonRevokeStatus {
+    Cancelled,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct NoonCancelSubscriptionObject {
+    status: NoonRevokeStatus,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct NoonRevokeMandateResult {
+    subscription: NoonCancelSubscriptionObject,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct NoonRevokeMandateResponse {
+    result: NoonRevokeMandateResult,
+}
+
+impl<F>
+    TryFrom<
+        types::ResponseRouterData<
+            F,
+            NoonRevokeMandateResponse,
+            types::MandateRevokeRequestData,
+            types::MandateRevokeResponseData,
+        >,
+    > for types::RouterData<F, types::MandateRevokeRequestData, types::MandateRevokeResponseData>
+{
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(
+        item: types::ResponseRouterData<
+            F,
+            NoonRevokeMandateResponse,
+            types::MandateRevokeRequestData,
+            types::MandateRevokeResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        match item.response.result.subscription.status {
+            NoonRevokeStatus::Cancelled => Ok(Self {
+                response: Ok(types::MandateRevokeResponseData {
+                    mandate_status: common_enums::MandateStatus::Revoked,
+                }),
+                ..item.data
+            }),
+        }
+    }
+}
 
 #[derive(Debug, Default, Deserialize, Clone)]
 #[serde(rename_all = "UPPERCASE")]
diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs
index c9f9d6d87f5..ebc0cf3664a 100644
--- a/crates/router/src/core/payments/flows.rs
+++ b/crates/router/src/core/payments/flows.rs
@@ -2247,7 +2247,6 @@ default_imp_for_revoking_mandates!(
     connector::Multisafepay,
     connector::Nexinets,
     connector::Nmi,
-    connector::Noon,
     connector::Nuvei,
     connector::Opayo,
     connector::Opennode,

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

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

## Description
<!-- Describe your changes in detail -->
Implement revoke mandate flow for Noon

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

## 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 Mandate with the Noon
- Test Recurring Payment using mandate_id
- Revoke the mandate and check at connector if it is revoked or not.

<img width="928" alt="Screenshot 2024-01-30 at 12 40 51 PM" src="https://github.com/juspay/hyperswitch/assets/55536657/365fd0a3-5992-44ec-a43f-ce2616ad40ba">
<img width="877" alt="Screenshot 2024-01-30 at 12 39 25 PM" src="https://github.com/juspay/hyperswitch/assets/55536657/1170ecc7-0a7b-43ba-95d7-758cb92da3d4">
<img width="1029" alt="Screenshot 2024-01-30 at 12 39 15 PM" src="https://github.com/juspay/hyperswitch/assets/55536657/75b40aea-c29a-41a7-be42-d72409a86f06">
<img width="884" alt="Screenshot 2024-01-30 at 5 12 52 PM" src="https://github.com/juspay/hyperswitch/assets/55536657/386d12c1-3c69-4107-93d5-5b101a7ba4ff">


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