Bug: [FEATURE] Payment void endpoint for v2



### Feature Description

Add  support for Payment Cancel in V2.


### Possible Implementation

Add V2 payments_cancel flow by completing GetTracker, UpdateTracker, and Domain traits for payment cancel endpoint.

### 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/events/payment.rs b/crates/api_models/src/events/payment.rs
index 8a0af806334..ad107225177 100644
--- a/crates/api_models/src/events/payment.rs
+++ b/crates/api_models/src/events/payment.rs
@@ -238,6 +238,22 @@ impl ApiEventMetric for payments::PaymentsResponse {
     }
 }
 
+#[cfg(feature = "v2")]
+impl ApiEventMetric for payments::PaymentsCancelRequest {
+    fn get_api_event_type(&self) -> Option<ApiEventsType> {
+        None
+    }
+}
+
+#[cfg(feature = "v2")]
+impl ApiEventMetric for payments::PaymentsCancelResponse {
+    fn get_api_event_type(&self) -> Option<ApiEventsType> {
+        Some(ApiEventsType::Payment {
+            payment_id: self.id.clone(),
+        })
+    }
+}
+
 #[cfg(feature = "v1")]
 impl ApiEventMetric for payments::PaymentsResponse {
     fn get_api_event_type(&self) -> Option<ApiEventsType> {
diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs
index 62390067d3b..43bd9c2a119 100644
--- a/crates/api_models/src/payments.rs
+++ b/crates/api_models/src/payments.rs
@@ -4873,6 +4873,56 @@ pub struct PaymentsCaptureResponse {
     pub amount: PaymentAmountDetailsResponse,
 }
 
+#[cfg(feature = "v2")]
+#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
+pub struct PaymentsCancelRequest {
+    /// The reason for the payment cancel
+    pub cancellation_reason: Option<String>,
+}
+
+#[cfg(feature = "v2")]
+#[derive(Debug, Clone, serde::Serialize, ToSchema)]
+pub struct PaymentsCancelResponse {
+    /// The unique identifier for the payment
+    pub id: id_type::GlobalPaymentId,
+
+    /// Status of the payment
+    #[schema(value_type = IntentStatus, example = "cancelled")]
+    pub status: common_enums::IntentStatus,
+
+    /// Cancellation reason for the payment cancellation
+    #[schema(example = "Requested by merchant")]
+    pub cancellation_reason: Option<String>,
+
+    /// Amount details related to the payment
+    pub amount: PaymentAmountDetailsResponse,
+
+    /// The unique identifier for the customer associated with the payment
+    pub customer_id: Option<id_type::GlobalCustomerId>,
+
+    /// The connector used for the payment
+    #[schema(example = "stripe")]
+    pub connector: Option<api_enums::Connector>,
+
+    #[schema(example = "2022-09-10T10:11:12Z")]
+    #[serde(with = "common_utils::custom_serde::iso8601")]
+    pub created: PrimitiveDateTime,
+
+    /// The payment method type for this payment attempt
+    #[schema(value_type = Option<PaymentMethod>, example = "wallet")]
+    pub payment_method_type: Option<api_enums::PaymentMethod>,
+
+    #[schema(value_type = Option<PaymentMethodType>, example = "apple_pay")]
+    pub payment_method_subtype: Option<api_enums::PaymentMethodType>,
+
+    /// List of payment attempts associated with payment intent
+    pub attempts: Option<Vec<PaymentAttemptResponse>>,
+
+    /// The url to which user must be redirected to after completion of the purchase
+    #[schema(value_type = Option<String>)]
+    pub return_url: Option<common_utils::types::Url>,
+}
+
 #[derive(Default, Clone, Debug, Eq, PartialEq, serde::Serialize)]
 pub struct UrlDetails {
     pub url: String,
@@ -8210,6 +8260,7 @@ pub struct PaymentsCompleteAuthorizeRequest {
     pub threeds_method_comp_ind: Option<ThreeDsCompletionIndicator>,
 }
 
+#[cfg(feature = "v1")]
 #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
 pub struct PaymentsCancelRequest {
     /// The identifier for the payment
diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs
index d42b9d8d108..49d5ff79ab8 100644
--- a/crates/diesel_models/src/payment_attempt.rs
+++ b/crates/diesel_models/src/payment_attempt.rs
@@ -862,7 +862,7 @@ pub struct PaymentAttemptUpdateInternal {
     pub connector_payment_id: Option<ConnectorTransactionId>,
     pub connector_payment_data: Option<String>,
     pub payment_method_id: Option<id_type::GlobalPaymentMethodId>,
-    // cancellation_reason: Option<String>,
+    pub cancellation_reason: Option<String>,
     pub modified_at: PrimitiveDateTime,
     pub browser_info: Option<serde_json::Value>,
     // payment_token: Option<String>,
@@ -914,6 +914,7 @@ impl PaymentAttemptUpdateInternal {
             modified_at,
             browser_info,
             error_code,
+            cancellation_reason,
             connector_metadata,
             error_reason,
             amount_capturable,
@@ -1404,6 +1405,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
                     network_advice_code: None,
                     network_error_message: None,
                     connector_request_reference_id: None,
+                    cancellation_reason: None,
                 }
             }
             PaymentAttemptUpdate::ErrorUpdate {
@@ -1451,6 +1453,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
                     network_error_message: None,
                     connector_request_reference_id: None,
                     connector_response_reference_id: None,
+                    cancellation_reason: None,
                 }
             }
             PaymentAttemptUpdate::UnresolvedResponseUpdate {
@@ -1496,6 +1499,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
                     network_advice_code: None,
                     network_error_message: None,
                     connector_request_reference_id: None,
+                    cancellation_reason: None,
                 }
             }
             PaymentAttemptUpdate::PreprocessingUpdate {
@@ -1539,6 +1543,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
                     network_advice_code: None,
                     network_error_message: None,
                     connector_request_reference_id: None,
+                    cancellation_reason: None,
                 }
             }
             PaymentAttemptUpdate::ConnectorResponse {
@@ -1578,6 +1583,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
                     network_error_message: None,
                     connector_request_reference_id: None,
                     connector_response_reference_id: None,
+                    cancellation_reason: None,
                 }
             }
             PaymentAttemptUpdate::ManualUpdate {
@@ -1622,6 +1628,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
                     network_error_message: None,
                     connector_request_reference_id: None,
                     connector_response_reference_id: None,
+                    cancellation_reason: None,
                 }
             }
         }
diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs
index c8ac34f8613..20abea03b56 100644
--- a/crates/hyperswitch_domain_models/src/payments.rs
+++ b/crates/hyperswitch_domain_models/src/payments.rs
@@ -991,6 +991,17 @@ where
     pub payment_attempt: PaymentAttempt,
 }
 
+#[cfg(feature = "v2")]
+#[derive(Clone)]
+pub struct PaymentCancelData<F>
+where
+    F: Clone,
+{
+    pub flow: PhantomData<F>,
+    pub payment_intent: PaymentIntent,
+    pub payment_attempt: PaymentAttempt,
+}
+
 #[cfg(feature = "v2")]
 impl<F> PaymentStatusData<F>
 where
diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs
index 7f8b9020618..7eba77e6294 100644
--- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs
+++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs
@@ -2044,6 +2044,11 @@ pub enum PaymentAttemptUpdate {
         updated_by: String,
         connector_payment_id: Option<String>,
     },
+    VoidUpdate {
+        status: storage_enums::AttemptStatus,
+        cancellation_reason: Option<String>,
+        updated_by: String,
+    },
 }
 
 #[cfg(feature = "v2")]
@@ -2849,6 +2854,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
                 network_error_message: None,
                 connector_request_reference_id,
                 connector_response_reference_id,
+                cancellation_reason: None,
             },
             PaymentAttemptUpdate::ErrorUpdate {
                 status,
@@ -2890,6 +2896,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
                     network_error_message: error.network_error_message,
                     connector_request_reference_id: None,
                     connector_response_reference_id: None,
+                    cancellation_reason: None,
                 }
             }
             PaymentAttemptUpdate::ConfirmIntentResponse(confirm_intent_response_update) => {
@@ -2937,6 +2944,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
                     network_error_message: None,
                     connector_request_reference_id: None,
                     connector_response_reference_id,
+                    cancellation_reason: None,
                 }
             }
             PaymentAttemptUpdate::SyncUpdate {
@@ -2970,6 +2978,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
                 network_error_message: None,
                 connector_request_reference_id: None,
                 connector_response_reference_id: None,
+                cancellation_reason: None,
             },
             PaymentAttemptUpdate::CaptureUpdate {
                 status,
@@ -3002,6 +3011,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
                 network_error_message: None,
                 connector_request_reference_id: None,
                 connector_response_reference_id: None,
+                cancellation_reason: None,
             },
             PaymentAttemptUpdate::PreCaptureUpdate {
                 amount_to_capture,
@@ -3033,6 +3043,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
                 network_error_message: None,
                 connector_request_reference_id: None,
                 connector_response_reference_id: None,
+                cancellation_reason: None,
             },
             PaymentAttemptUpdate::ConfirmIntentTokenized {
                 status,
@@ -3068,6 +3079,40 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
                 network_error_message: None,
                 connector_request_reference_id: None,
                 connector_response_reference_id: None,
+                cancellation_reason: None,
+            },
+            PaymentAttemptUpdate::VoidUpdate {
+                status,
+                cancellation_reason,
+                updated_by,
+            } => Self {
+                status: Some(status),
+                cancellation_reason,
+                error_message: None,
+                error_code: None,
+                modified_at: common_utils::date_time::now(),
+                browser_info: None,
+                error_reason: None,
+                updated_by,
+                merchant_connector_id: None,
+                unified_code: None,
+                unified_message: None,
+                connector_payment_id: None,
+                connector_payment_data: None,
+                connector: None,
+                redirection_data: None,
+                connector_metadata: None,
+                amount_capturable: None,
+                amount_to_capture: None,
+                connector_token_details: None,
+                authentication_type: None,
+                feature_metadata: None,
+                network_advice_code: None,
+                network_decline_code: None,
+                network_error_message: None,
+                connector_request_reference_id: None,
+                connector_response_reference_id: None,
+                payment_method_id: None,
             },
         }
     }
diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs
index 8cd3be165e0..776b6e2d4cf 100644
--- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs
+++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs
@@ -387,6 +387,11 @@ pub enum PaymentIntentUpdate {
     },
     /// UpdateIntent
     UpdateIntent(Box<PaymentIntentUpdateFields>),
+    /// VoidUpdate for payment cancellation
+    VoidUpdate {
+        status: common_enums::IntentStatus,
+        updated_by: String,
+    },
 }
 
 #[cfg(feature = "v2")]
@@ -803,6 +808,45 @@ impl TryFrom<PaymentIntentUpdate> for diesel_models::PaymentIntentUpdateInternal
                 force_3ds_challenge: None,
                 is_iframe_redirection_enabled: None,
             }),
+            PaymentIntentUpdate::VoidUpdate { status, updated_by } => Ok(Self {
+                status: Some(status),
+                amount_captured: None,
+                active_attempt_id: None,
+                prerouting_algorithm: None,
+                modified_at: common_utils::date_time::now(),
+                amount: None,
+                currency: None,
+                shipping_cost: None,
+                tax_details: None,
+                skip_external_tax_calculation: None,
+                surcharge_applicable: None,
+                surcharge_amount: None,
+                tax_on_surcharge: None,
+                routing_algorithm_id: None,
+                capture_method: None,
+                authentication_type: None,
+                billing_address: None,
+                shipping_address: None,
+                customer_present: None,
+                description: None,
+                return_url: None,
+                setup_future_usage: None,
+                apply_mit_exemption: None,
+                statement_descriptor: None,
+                order_details: None,
+                allowed_payment_method_types: None,
+                metadata: None,
+                connector_metadata: None,
+                feature_metadata: None,
+                payment_link_config: None,
+                request_incremental_authorization: None,
+                session_expiry: None,
+                frm_metadata: None,
+                request_external_three_ds_authentication: None,
+                updated_by,
+                force_3ds_challenge: None,
+                is_iframe_redirection_enabled: None,
+            }),
         }
     }
 }
diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs
index e975423d9ff..ad778e9aa74 100644
--- a/crates/hyperswitch_domain_models/src/router_data.rs
+++ b/crates/hyperswitch_domain_models/src/router_data.rs
@@ -1814,3 +1814,65 @@ impl
         }
     }
 }
+
+#[cfg(feature = "v2")]
+impl
+    TrackerPostUpdateObjects<
+        router_flow_types::Void,
+        router_request_types::PaymentsCancelData,
+        payments::PaymentCancelData<router_flow_types::Void>,
+    >
+    for RouterData<
+        router_flow_types::Void,
+        router_request_types::PaymentsCancelData,
+        router_response_types::PaymentsResponseData,
+    >
+{
+    fn get_payment_intent_update(
+        &self,
+        _payment_data: &payments::PaymentCancelData<router_flow_types::Void>,
+        storage_scheme: common_enums::MerchantStorageScheme,
+    ) -> PaymentIntentUpdate {
+        let intent_status = common_enums::IntentStatus::from(self.status);
+        PaymentIntentUpdate::VoidUpdate {
+            status: intent_status,
+            updated_by: storage_scheme.to_string(),
+        }
+    }
+
+    fn get_payment_attempt_update(
+        &self,
+        payment_data: &payments::PaymentCancelData<router_flow_types::Void>,
+        storage_scheme: common_enums::MerchantStorageScheme,
+    ) -> PaymentAttemptUpdate {
+        PaymentAttemptUpdate::VoidUpdate {
+            status: self.status,
+            cancellation_reason: payment_data.payment_attempt.cancellation_reason.clone(),
+            updated_by: storage_scheme.to_string(),
+        }
+    }
+
+    fn get_amount_capturable(
+        &self,
+        _payment_data: &payments::PaymentCancelData<router_flow_types::Void>,
+    ) -> Option<MinorUnit> {
+        // For void operations, no amount is capturable
+        Some(MinorUnit::zero())
+    }
+
+    fn get_captured_amount(
+        &self,
+        _payment_data: &payments::PaymentCancelData<router_flow_types::Void>,
+    ) -> Option<MinorUnit> {
+        // For void operations, no amount is captured
+        Some(MinorUnit::zero())
+    }
+
+    fn get_attempt_status_for_db_update(
+        &self,
+        _payment_data: &payments::PaymentCancelData<router_flow_types::Void>,
+    ) -> common_enums::AttemptStatus {
+        // For void operations, return Voided status
+        common_enums::AttemptStatus::Voided
+    }
+}
diff --git a/crates/hyperswitch_interfaces/src/conversion_impls.rs b/crates/hyperswitch_interfaces/src/conversion_impls.rs
index 32818ca5d4b..1b451692bdd 100644
--- a/crates/hyperswitch_interfaces/src/conversion_impls.rs
+++ b/crates/hyperswitch_interfaces/src/conversion_impls.rs
@@ -324,6 +324,7 @@ impl<T, Req: Clone, Resp: Clone> RouterDataConversion<T, Req, Resp> for PaymentF
         router_data.apple_pay_flow = apple_pay_flow;
         router_data.connector_response = connector_response;
         router_data.payment_method_status = payment_method_status;
+        router_data.connector_auth_type = new_router_data.connector_auth_type;
         Ok(router_data)
     }
 }
diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs
index f7f445d1b6a..e40830404b3 100644
--- a/crates/router/src/core/payments.rs
+++ b/crates/router/src/core/payments.rs
@@ -48,8 +48,8 @@ use futures::future::join_all;
 use helpers::{decrypt_paze_token, ApplePayData};
 #[cfg(feature = "v2")]
 use hyperswitch_domain_models::payments::{
-    PaymentAttemptListData, PaymentCaptureData, PaymentConfirmData, PaymentIntentData,
-    PaymentStatusData,
+    PaymentAttemptListData, PaymentCancelData, PaymentCaptureData, PaymentConfirmData,
+    PaymentIntentData, PaymentStatusData,
 };
 #[cfg(feature = "v2")]
 use hyperswitch_domain_models::router_response_types::RedirectForm;
@@ -10638,6 +10638,8 @@ pub trait OperationSessionSetters<F> {
         &mut self,
         connector_request_reference_id: String,
     );
+    #[cfg(feature = "v2")]
+    fn set_cancellation_reason(&mut self, cancellation_reason: Option<String>);
 }
 
 #[cfg(feature = "v1")]
@@ -11290,6 +11292,10 @@ impl<F: Clone> OperationSessionSetters<F> for PaymentIntentData<F> {
     ) {
         todo!()
     }
+
+    fn set_cancellation_reason(&mut self, cancellation_reason: Option<String>) {
+        todo!()
+    }
 }
 
 #[cfg(feature = "v2")]
@@ -11597,6 +11603,10 @@ impl<F: Clone> OperationSessionSetters<F> for PaymentConfirmData<F> {
     ) {
         todo!()
     }
+
+    fn set_cancellation_reason(&mut self, cancellation_reason: Option<String>) {
+        todo!()
+    }
 }
 
 #[cfg(feature = "v2")]
@@ -11899,6 +11909,10 @@ impl<F: Clone> OperationSessionSetters<F> for PaymentStatusData<F> {
     ) {
         todo!()
     }
+
+    fn set_cancellation_reason(&mut self, cancellation_reason: Option<String>) {
+        todo!()
+    }
 }
 
 #[cfg(feature = "v2")]
@@ -12204,6 +12218,10 @@ impl<F: Clone> OperationSessionSetters<F> for PaymentCaptureData<F> {
     ) {
         todo!()
     }
+
+    fn set_cancellation_reason(&mut self, cancellation_reason: Option<String>) {
+        todo!()
+    }
 }
 
 #[cfg(feature = "v2")]
@@ -12371,3 +12389,311 @@ impl<F: Clone> OperationSessionGetters<F> for PaymentAttemptListData<F> {
         todo!()
     }
 }
+
+#[cfg(feature = "v2")]
+impl<F: Clone> OperationSessionGetters<F> for PaymentCancelData<F> {
+    #[track_caller]
+    fn get_payment_attempt(&self) -> &storage::PaymentAttempt {
+        &self.payment_attempt
+    }
+    fn list_payments_attempts(&self) -> &Vec<storage::PaymentAttempt> {
+        todo!()
+    }
+    fn get_client_secret(&self) -> &Option<Secret<String>> {
+        todo!()
+    }
+    fn get_payment_intent(&self) -> &storage::PaymentIntent {
+        &self.payment_intent
+    }
+
+    fn get_merchant_connector_details(
+        &self,
+    ) -> Option<common_types::domain::MerchantConnectorAuthDetails> {
+        todo!()
+    }
+
+    fn get_payment_method_info(&self) -> Option<&domain::PaymentMethod> {
+        todo!()
+    }
+
+    fn get_payment_method_token(&self) -> Option<&PaymentMethodToken> {
+        todo!()
+    }
+
+    fn get_mandate_id(&self) -> Option<&payments_api::MandateIds> {
+        todo!()
+    }
+
+    // what is this address find out and not required remove this
+    fn get_address(&self) -> &PaymentAddress {
+        todo!()
+    }
+
+    fn get_creds_identifier(&self) -> Option<&str> {
+        None
+    }
+
+    fn get_token(&self) -> Option<&str> {
+        todo!()
+    }
+
+    fn get_multiple_capture_data(&self) -> Option<&types::MultipleCaptureData> {
+        todo!()
+    }
+
+    fn get_payment_link_data(&self) -> Option<api_models::payments::PaymentLinkResponse> {
+        todo!()
+    }
+
+    fn get_ephemeral_key(&self) -> Option<ephemeral_key::EphemeralKey> {
+        todo!()
+    }
+
+    fn get_setup_mandate(&self) -> Option<&MandateData> {
+        todo!()
+    }
+
+    fn get_poll_config(&self) -> Option<router_types::PollConfig> {
+        todo!()
+    }
+
+    fn get_authentication(
+        &self,
+    ) -> Option<&hyperswitch_domain_models::router_request_types::authentication::AuthenticationStore>
+    {
+        todo!()
+    }
+
+    fn get_frm_message(&self) -> Option<FraudCheck> {
+        todo!()
+    }
+
+    fn get_refunds(&self) -> Vec<diesel_refund::Refund> {
+        todo!()
+    }
+
+    fn get_disputes(&self) -> Vec<storage::Dispute> {
+        todo!()
+    }
+
+    fn get_authorizations(&self) -> Vec<diesel_models::authorization::Authorization> {
+        todo!()
+    }
+
+    fn get_attempts(&self) -> Option<Vec<storage::PaymentAttempt>> {
+        todo!()
+    }
+
+    fn get_recurring_details(&self) -> Option<&RecurringDetails> {
+        todo!()
+    }
+
+    fn get_payment_intent_profile_id(&self) -> Option<&id_type::ProfileId> {
+        todo!()
+    }
+
+    fn get_currency(&self) -> storage_enums::Currency {
+        todo!()
+    }
+
+    fn get_amount(&self) -> api::Amount {
+        todo!()
+    }
+
+    fn get_payment_attempt_connector(&self) -> Option<&str> {
+        todo!()
+    }
+
+    fn get_merchant_connector_id_in_attempt(&self) -> Option<id_type::MerchantConnectorAccountId> {
+        todo!()
+    }
+
+    fn get_connector_customer_id(&self) -> Option<String> {
+        todo!()
+    }
+
+    fn get_billing_address(&self) -> Option<hyperswitch_domain_models::address::Address> {
+        todo!()
+    }
+
+    fn get_payment_method_data(&self) -> Option<&domain::PaymentMethodData> {
+        todo!()
+    }
+
+    fn get_sessions_token(&self) -> Vec<api::SessionToken> {
+        todo!()
+    }
+
+    fn get_token_data(&self) -> Option<&storage::PaymentTokenData> {
+        todo!()
+    }
+
+    fn get_mandate_connector(&self) -> Option<&MandateConnectorDetails> {
+        todo!()
+    }
+
+    fn get_force_sync(&self) -> Option<bool> {
+        todo!()
+    }
+
+    fn get_capture_method(&self) -> Option<enums::CaptureMethod> {
+        todo!()
+    }
+
+    fn get_optional_payment_attempt(&self) -> Option<&storage::PaymentAttempt> {
+        None
+    }
+
+    fn get_pre_routing_result(
+        &self,
+    ) -> Option<HashMap<enums::PaymentMethodType, domain::PreRoutingConnectorChoice>> {
+        None
+    }
+
+    fn get_optional_external_vault_session_details(&self) -> Option<api::VaultSessionDetails> {
+        todo!()
+    }
+}
+
+#[cfg(feature = "v2")]
+impl<F: Clone> OperationSessionSetters<F> for PaymentCancelData<F> {
+    fn set_payment_intent(&mut self, payment_intent: storage::PaymentIntent) {
+        self.payment_intent = payment_intent;
+    }
+
+    fn set_client_secret(&mut self, _client_secret: Option<Secret<String>>) {
+        todo!()
+    }
+
+    fn set_payment_attempt(&mut self, payment_attempt: storage::PaymentAttempt) {
+        self.payment_attempt = payment_attempt;
+    }
+
+    fn set_payment_method_data(&mut self, _payment_method_data: Option<domain::PaymentMethodData>) {
+        todo!()
+    }
+
+    fn set_payment_method_token(&mut self, _payment_method_token: Option<PaymentMethodToken>) {
+        todo!()
+    }
+
+    fn set_email_if_not_present(&mut self, _email: pii::Email) {
+        todo!()
+    }
+
+    fn set_payment_method_id_in_attempt(&mut self, _payment_method_id: Option<String>) {
+        todo!()
+    }
+
+    fn set_pm_token(&mut self, _token: String) {
+        !todo!()
+    }
+
+    fn set_connector_customer_id(&mut self, _customer_id: Option<String>) {
+        // TODO: handle this case. Should we add connector_customer_id in PaymentCancelData?
+    }
+
+    fn push_sessions_token(&mut self, _token: api::SessionToken) {
+        todo!()
+    }
+
+    fn set_surcharge_details(&mut self, _surcharge_details: Option<types::SurchargeDetails>) {
+        todo!()
+    }
+
+    fn set_merchant_connector_id_in_attempt(
+        &mut self,
+        _merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
+    ) {
+        todo!()
+    }
+
+    fn set_card_network(&mut self, _card_network: enums::CardNetwork) {
+        todo!()
+    }
+
+    fn set_co_badged_card_data(
+        &mut self,
+        _debit_routing_output: &api_models::open_router::DebitRoutingOutput,
+    ) {
+        todo!()
+    }
+
+    fn set_frm_message(&mut self, _frm_message: FraudCheck) {
+        todo!()
+    }
+
+    fn set_payment_intent_status(&mut self, status: storage_enums::IntentStatus) {
+        self.payment_intent.status = status;
+    }
+
+    fn set_authentication_type_in_attempt(
+        &mut self,
+        _authentication_type: Option<enums::AuthenticationType>,
+    ) {
+        todo!()
+    }
+
+    fn set_recurring_mandate_payment_data(
+        &mut self,
+        _recurring_mandate_payment_data:
+            hyperswitch_domain_models::router_data::RecurringMandatePaymentData,
+    ) {
+        todo!()
+    }
+
+    fn set_mandate_id(&mut self, _mandate_id: api_models::payments::MandateIds) {
+        todo!()
+    }
+
+    fn set_setup_future_usage_in_payment_intent(
+        &mut self,
+        setup_future_usage: storage_enums::FutureUsage,
+    ) {
+        todo!()
+    }
+
+    fn set_prerouting_algorithm_in_payment_intent(
+        &mut self,
+        prerouting_algorithm: storage::PaymentRoutingInfo,
+    ) {
+        self.payment_intent.prerouting_algorithm = Some(prerouting_algorithm);
+    }
+
+    fn set_connector_in_payment_attempt(&mut self, _connector: Option<String>) {
+        todo!()
+    }
+
+    fn set_connector_request_reference_id(&mut self, _reference_id: Option<String>) {
+        todo!()
+    }
+
+    fn set_connector_response_reference_id(&mut self, _reference_id: Option<String>) {
+        todo!()
+    }
+
+    fn set_vault_session_details(
+        &mut self,
+        _external_vault_session_details: Option<api::VaultSessionDetails>,
+    ) {
+        todo!()
+    }
+
+    fn set_routing_approach_in_attempt(
+        &mut self,
+        _routing_approach: Option<enums::RoutingApproach>,
+    ) {
+        todo!()
+    }
+
+    fn set_connector_request_reference_id_in_payment_attempt(
+        &mut self,
+        _connector_request_reference_id: String,
+    ) {
+        todo!()
+    }
+
+    fn set_cancellation_reason(&mut self, cancellation_reason: Option<String>) {
+        self.payment_attempt.cancellation_reason = cancellation_reason;
+    }
+}
diff --git a/crates/router/src/core/payments/flows/cancel_flow.rs b/crates/router/src/core/payments/flows/cancel_flow.rs
index a452a165c7d..1190cef2441 100644
--- a/crates/router/src/core/payments/flows/cancel_flow.rs
+++ b/crates/router/src/core/payments/flows/cancel_flow.rs
@@ -10,26 +10,11 @@ use crate::{
     services,
     types::{self, api, domain},
 };
-
+#[cfg(feature = "v1")]
 #[async_trait]
 impl ConstructFlowSpecificData<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
     for PaymentData<api::Void>
 {
-    #[cfg(feature = "v2")]
-    async fn construct_router_data<'a>(
-        &self,
-        _state: &SessionState,
-        _connector_id: &str,
-        _merchant_context: &domain::MerchantContext,
-        _customer: &Option<domain::Customer>,
-        _merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
-        _merchant_recipient_data: Option<types::MerchantRecipientData>,
-        _header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
-    ) -> RouterResult<types::PaymentsCancelRouterData> {
-        todo!()
-    }
-
-    #[cfg(feature = "v1")]
     async fn construct_router_data<'a>(
         &self,
         state: &SessionState,
@@ -56,6 +41,34 @@ impl ConstructFlowSpecificData<api::Void, types::PaymentsCancelData, types::Paym
         .await
     }
 }
+#[cfg(feature = "v2")]
+#[async_trait]
+impl ConstructFlowSpecificData<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
+    for hyperswitch_domain_models::payments::PaymentCancelData<api::Void>
+{
+    async fn construct_router_data<'a>(
+        &self,
+        state: &SessionState,
+        connector_id: &str,
+        merchant_context: &domain::MerchantContext,
+        customer: &Option<domain::Customer>,
+        merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
+        merchant_recipient_data: Option<types::MerchantRecipientData>,
+        header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
+    ) -> RouterResult<types::PaymentsCancelRouterData> {
+        Box::pin(transformers::construct_router_data_for_cancel(
+            state,
+            self.clone(),
+            connector_id,
+            merchant_context,
+            customer,
+            merchant_connector_account,
+            merchant_recipient_data,
+            header_payload,
+        ))
+        .await
+    }
+}
 
 #[async_trait]
 impl Feature<api::Void, types::PaymentsCancelData>
diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs
index 2d8488ab6cc..11c26b5c54b 100644
--- a/crates/router/src/core/payments/operations.rs
+++ b/crates/router/src/core/payments/operations.rs
@@ -58,6 +58,9 @@ pub mod payment_get;
 #[cfg(feature = "v2")]
 pub mod payment_capture_v2;
 
+#[cfg(feature = "v2")]
+pub mod payment_cancel_v2;
+
 use api_models::enums::FrmSuggestion;
 #[cfg(all(feature = "v1", feature = "dynamic_routing"))]
 use api_models::routing::RoutableConnectorChoice;
diff --git a/crates/router/src/core/payments/operations/payment_cancel_v2.rs b/crates/router/src/core/payments/operations/payment_cancel_v2.rs
new file mode 100644
index 00000000000..e54de6cb793
--- /dev/null
+++ b/crates/router/src/core/payments/operations/payment_cancel_v2.rs
@@ -0,0 +1,352 @@
+use std::marker::PhantomData;
+
+use api_models::enums::FrmSuggestion;
+use async_trait::async_trait;
+use common_enums;
+use common_utils::{ext_traits::AsyncExt, id_type::GlobalPaymentId};
+use error_stack::ResultExt;
+use router_env::{instrument, tracing};
+
+use super::{
+    BoxedOperation, Domain, GetTracker, Operation, OperationSessionSetters, UpdateTracker,
+    ValidateRequest, ValidateStatusForOperation,
+};
+use crate::{
+    core::{
+        errors::{self, CustomResult, RouterResult, StorageErrorExt},
+        payments::operations,
+    },
+    routes::{app::ReqState, SessionState},
+    types::{
+        self,
+        api::{self, ConnectorCallType, PaymentIdTypeExt},
+        domain,
+        storage::{self, enums},
+        PaymentsCancelData,
+    },
+    utils::OptionExt,
+};
+
+#[derive(Debug, Clone, Copy)]
+pub struct PaymentsCancel;
+
+type BoxedCancelOperation<'b, F> = BoxedOperation<
+    'b,
+    F,
+    api::PaymentsCancelRequest,
+    hyperswitch_domain_models::payments::PaymentCancelData<F>,
+>;
+
+// Manual Operation trait implementation for V2
+impl<F: Send + Clone + Sync> Operation<F, api::PaymentsCancelRequest> for &PaymentsCancel {
+    type Data = hyperswitch_domain_models::payments::PaymentCancelData<F>;
+
+    fn to_validate_request(
+        &self,
+    ) -> RouterResult<&(dyn ValidateRequest<F, api::PaymentsCancelRequest, Self::Data> + Send + Sync)>
+    {
+        Ok(*self)
+    }
+
+    fn to_get_tracker(
+        &self,
+    ) -> RouterResult<&(dyn GetTracker<F, Self::Data, api::PaymentsCancelRequest> + Send + Sync)>
+    {
+        Ok(*self)
+    }
+
+    fn to_domain(&self) -> RouterResult<&(dyn Domain<F, api::PaymentsCancelRequest, Self::Data>)> {
+        Ok(*self)
+    }
+
+    fn to_update_tracker(
+        &self,
+    ) -> RouterResult<&(dyn UpdateTracker<F, Self::Data, api::PaymentsCancelRequest> + Send + Sync)>
+    {
+        Ok(*self)
+    }
+}
+
+#[automatically_derived]
+impl<F: Send + Clone + Sync> Operation<F, api::PaymentsCancelRequest> for PaymentsCancel {
+    type Data = hyperswitch_domain_models::payments::PaymentCancelData<F>;
+
+    fn to_validate_request(
+        &self,
+    ) -> RouterResult<&(dyn ValidateRequest<F, api::PaymentsCancelRequest, Self::Data> + Send + Sync)>
+    {
+        Ok(self)
+    }
+
+    fn to_get_tracker(
+        &self,
+    ) -> RouterResult<&(dyn GetTracker<F, Self::Data, api::PaymentsCancelRequest> + Send + Sync)>
+    {
+        Ok(self)
+    }
+
+    fn to_domain(&self) -> RouterResult<&dyn Domain<F, api::PaymentsCancelRequest, Self::Data>> {
+        Ok(self)
+    }
+
+    fn to_update_tracker(
+        &self,
+    ) -> RouterResult<&(dyn UpdateTracker<F, Self::Data, api::PaymentsCancelRequest> + Send + Sync)>
+    {
+        Ok(self)
+    }
+}
+
+#[cfg(feature = "v2")]
+impl<F: Send + Clone + Sync>
+    ValidateRequest<
+        F,
+        api::PaymentsCancelRequest,
+        hyperswitch_domain_models::payments::PaymentCancelData<F>,
+    > for PaymentsCancel
+{
+    #[instrument(skip_all)]
+    fn validate_request(
+        &self,
+        _request: &api::PaymentsCancelRequest,
+        merchant_context: &domain::MerchantContext,
+    ) -> RouterResult<operations::ValidateResult> {
+        Ok(operations::ValidateResult {
+            merchant_id: merchant_context.get_merchant_account().get_id().to_owned(),
+            storage_scheme: merchant_context.get_merchant_account().storage_scheme,
+            requeue: false,
+        })
+    }
+}
+
+#[cfg(feature = "v2")]
+#[async_trait]
+impl<F: Send + Clone + Sync>
+    GetTracker<
+        F,
+        hyperswitch_domain_models::payments::PaymentCancelData<F>,
+        api::PaymentsCancelRequest,
+    > for PaymentsCancel
+{
+    #[instrument(skip_all)]
+    async fn get_trackers<'a>(
+        &'a self,
+        state: &'a SessionState,
+        payment_id: &common_utils::id_type::GlobalPaymentId,
+        request: &api::PaymentsCancelRequest,
+        merchant_context: &domain::MerchantContext,
+        profile: &domain::Profile,
+        _header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
+    ) -> RouterResult<
+        operations::GetTrackerResponse<hyperswitch_domain_models::payments::PaymentCancelData<F>>,
+    > {
+        let db = &*state.store;
+        let key_manager_state = &state.into();
+
+        let merchant_id = merchant_context.get_merchant_account().get_id();
+        let storage_scheme = merchant_context.get_merchant_account().storage_scheme;
+        let payment_intent = db
+            .find_payment_intent_by_id(
+                key_manager_state,
+                payment_id,
+                merchant_context.get_merchant_key_store(),
+                storage_scheme,
+            )
+            .await
+            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
+            .attach_printable("Failed to find payment intent for cancellation")?;
+
+        self.validate_status_for_operation(payment_intent.status)?;
+
+        let active_attempt_id = payment_intent.active_attempt_id.as_ref().ok_or_else(|| {
+            errors::ApiErrorResponse::InvalidRequestData {
+                message: "Payment cancellation not possible - no active payment attempt found"
+                    .to_string(),
+            }
+        })?;
+
+        let payment_attempt = db
+            .find_payment_attempt_by_id(
+                key_manager_state,
+                merchant_context.get_merchant_key_store(),
+                active_attempt_id,
+                storage_scheme,
+            )
+            .await
+            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
+            .attach_printable("Failed to find payment attempt for cancellation")?;
+
+        let mut payment_data = hyperswitch_domain_models::payments::PaymentCancelData {
+            flow: PhantomData,
+            payment_intent,
+            payment_attempt,
+        };
+
+        payment_data.set_cancellation_reason(request.cancellation_reason.clone());
+
+        let get_trackers_response = operations::GetTrackerResponse { payment_data };
+
+        Ok(get_trackers_response)
+    }
+}
+
+#[cfg(feature = "v2")]
+#[async_trait]
+impl<F: Clone + Send + Sync>
+    UpdateTracker<
+        F,
+        hyperswitch_domain_models::payments::PaymentCancelData<F>,
+        api::PaymentsCancelRequest,
+    > for PaymentsCancel
+{
+    #[instrument(skip_all)]
+    async fn update_trackers<'b>(
+        &'b self,
+        state: &'b SessionState,
+        req_state: ReqState,
+        mut payment_data: hyperswitch_domain_models::payments::PaymentCancelData<F>,
+        _customer: Option<domain::Customer>,
+        storage_scheme: enums::MerchantStorageScheme,
+        _updated_customer: Option<storage::CustomerUpdate>,
+        merchant_key_store: &domain::MerchantKeyStore,
+        _frm_suggestion: Option<FrmSuggestion>,
+        _header_payload: hyperswitch_domain_models::payments::HeaderPayload,
+    ) -> RouterResult<(
+        BoxedCancelOperation<'b, F>,
+        hyperswitch_domain_models::payments::PaymentCancelData<F>,
+    )>
+    where
+        F: 'b + Send,
+    {
+        let db = &*state.store;
+        let key_manager_state = &state.into();
+
+        let payment_attempt_update = hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::VoidUpdate {
+            status: enums::AttemptStatus::VoidInitiated,
+            cancellation_reason: payment_data.payment_attempt.cancellation_reason.clone(),
+            updated_by: storage_scheme.to_string(),
+        };
+
+        let updated_payment_attempt = db
+            .update_payment_attempt(
+                key_manager_state,
+                merchant_key_store,
+                payment_data.payment_attempt.clone(),
+                payment_attempt_update,
+                storage_scheme,
+            )
+            .await
+            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
+            .attach_printable("Failed to update payment attempt for cancellation")?;
+        payment_data.set_payment_attempt(updated_payment_attempt);
+
+        Ok((Box::new(self), payment_data))
+    }
+}
+
+#[cfg(feature = "v2")]
+#[async_trait]
+impl<F: Send + Clone + Sync>
+    Domain<F, api::PaymentsCancelRequest, hyperswitch_domain_models::payments::PaymentCancelData<F>>
+    for PaymentsCancel
+{
+    async fn get_customer_details<'a>(
+        &'a self,
+        _state: &SessionState,
+        _payment_data: &mut hyperswitch_domain_models::payments::PaymentCancelData<F>,
+        _merchant_key_store: &domain::MerchantKeyStore,
+        _storage_scheme: enums::MerchantStorageScheme,
+    ) -> CustomResult<(BoxedCancelOperation<'a, F>, Option<domain::Customer>), errors::StorageError>
+    {
+        Ok((Box::new(*self), None))
+    }
+
+    async fn make_pm_data<'a>(
+        &'a self,
+        _state: &'a SessionState,
+        _payment_data: &mut hyperswitch_domain_models::payments::PaymentCancelData<F>,
+        _storage_scheme: enums::MerchantStorageScheme,
+        _merchant_key_store: &domain::MerchantKeyStore,
+        _customer: &Option<domain::Customer>,
+        _business_profile: &domain::Profile,
+        _should_retry_with_pan: bool,
+    ) -> RouterResult<(
+        BoxedCancelOperation<'a, F>,
+        Option<domain::PaymentMethodData>,
+        Option<String>,
+    )> {
+        Ok((Box::new(*self), None, None))
+    }
+
+    async fn perform_routing<'a>(
+        &'a self,
+        _merchant_context: &domain::MerchantContext,
+        _business_profile: &domain::Profile,
+        state: &SessionState,
+        payment_data: &mut hyperswitch_domain_models::payments::PaymentCancelData<F>,
+    ) -> RouterResult<api::ConnectorCallType> {
+        let payment_attempt = &payment_data.payment_attempt;
+        let connector = payment_attempt
+            .connector
+            .as_ref()
+            .get_required_value("connector")
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("Connector not found for payment cancellation")?;
+
+        let merchant_connector_id = payment_attempt
+            .merchant_connector_id
+            .as_ref()
+            .get_required_value("merchant_connector_id")
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("Merchant connector ID not found for payment cancellation")?;
+
+        let connector_data = api::ConnectorData::get_connector_by_name(
+            &state.conf.connectors,
+            connector,
+            api::GetToken::Connector,
+            Some(merchant_connector_id.to_owned()),
+        )
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Invalid connector name received")?;
+
+        Ok(api::ConnectorCallType::PreDetermined(connector_data.into()))
+    }
+}
+
+impl ValidateStatusForOperation for PaymentsCancel {
+    fn validate_status_for_operation(
+        &self,
+        intent_status: common_enums::IntentStatus,
+    ) -> Result<(), errors::ApiErrorResponse> {
+        match intent_status {
+            common_enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture
+            | common_enums::IntentStatus::PartiallyCapturedAndCapturable
+            | common_enums::IntentStatus::RequiresCapture => Ok(()),
+            common_enums::IntentStatus::Succeeded
+            | common_enums::IntentStatus::Failed
+            | common_enums::IntentStatus::Cancelled
+            | common_enums::IntentStatus::CancelledPostCapture
+            | common_enums::IntentStatus::Processing
+            | common_enums::IntentStatus::RequiresCustomerAction
+            | common_enums::IntentStatus::RequiresMerchantAction
+            | common_enums::IntentStatus::RequiresPaymentMethod
+            | common_enums::IntentStatus::RequiresConfirmation
+            | common_enums::IntentStatus::PartiallyCaptured
+            | common_enums::IntentStatus::Conflicted
+            | common_enums::IntentStatus::Expired => {
+                Err(errors::ApiErrorResponse::PaymentUnexpectedState {
+                    current_flow: format!("{self:?}"),
+                    field_name: "status".to_string(),
+                    current_value: intent_status.to_string(),
+                    states: [
+                        common_enums::IntentStatus::RequiresCapture,
+                        common_enums::IntentStatus::PartiallyCapturedAndCapturable,
+                        common_enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture,
+                    ]
+                    .map(|enum_value| enum_value.to_string())
+                    .join(", "),
+                })
+            }
+        }
+    }
+}
diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs
index 2b1d1024e76..410c546eeda 100644
--- a/crates/router/src/core/payments/operations/payment_response.rs
+++ b/crates/router/src/core/payments/operations/payment_response.rs
@@ -3232,3 +3232,83 @@ fn get_total_amount_captured<F: Clone, T: types::Capturable>(
         }
     }
 }
+
+#[cfg(feature = "v2")]
+impl<F: Send + Clone + Sync> Operation<F, types::PaymentsCancelData> for PaymentResponse {
+    type Data = hyperswitch_domain_models::payments::PaymentCancelData<F>;
+    fn to_post_update_tracker(
+        &self,
+    ) -> RouterResult<
+        &(dyn PostUpdateTracker<F, Self::Data, types::PaymentsCancelData> + Send + Sync),
+    > {
+        Ok(self)
+    }
+}
+
+#[cfg(feature = "v2")]
+#[async_trait]
+impl<F: Clone + Send + Sync>
+    PostUpdateTracker<
+        F,
+        hyperswitch_domain_models::payments::PaymentCancelData<F>,
+        types::PaymentsCancelData,
+    > for PaymentResponse
+{
+    async fn update_tracker<'b>(
+        &'b self,
+        state: &'b SessionState,
+        mut payment_data: hyperswitch_domain_models::payments::PaymentCancelData<F>,
+        router_data: types::RouterData<F, types::PaymentsCancelData, types::PaymentsResponseData>,
+        key_store: &domain::MerchantKeyStore,
+        storage_scheme: enums::MerchantStorageScheme,
+    ) -> RouterResult<hyperswitch_domain_models::payments::PaymentCancelData<F>>
+    where
+        F: 'b + Send + Sync,
+        types::RouterData<F, types::PaymentsCancelData, types::PaymentsResponseData>:
+            hyperswitch_domain_models::router_data::TrackerPostUpdateObjects<
+                F,
+                types::PaymentsCancelData,
+                hyperswitch_domain_models::payments::PaymentCancelData<F>,
+            >,
+    {
+        let db = &*state.store;
+        let key_manager_state = &state.into();
+
+        use hyperswitch_domain_models::router_data::TrackerPostUpdateObjects;
+
+        let payment_intent_update =
+            router_data.get_payment_intent_update(&payment_data, storage_scheme);
+
+        let updated_payment_intent = db
+            .update_payment_intent(
+                key_manager_state,
+                payment_data.payment_intent.clone(),
+                payment_intent_update,
+                key_store,
+                storage_scheme,
+            )
+            .await
+            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
+            .attach_printable("Error while updating the payment_intent")?;
+
+        let payment_attempt_update =
+            router_data.get_payment_attempt_update(&payment_data, storage_scheme);
+
+        let updated_payment_attempt = db
+            .update_payment_attempt(
+                key_manager_state,
+                key_store,
+                payment_data.payment_attempt.clone(),
+                payment_attempt_update,
+                storage_scheme,
+            )
+            .await
+            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
+            .attach_printable("Error while updating the payment_attempt")?;
+
+        payment_data.set_payment_intent(updated_payment_intent);
+        payment_data.set_payment_attempt(updated_payment_attempt);
+
+        Ok(payment_data)
+    }
+}
diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs
index f799fdebbfe..47733f64e36 100644
--- a/crates/router/src/core/payments/transformers.rs
+++ b/crates/router/src/core/payments/transformers.rs
@@ -1,5 +1,7 @@
 use std::{fmt::Debug, marker::PhantomData, str::FromStr};
 
+#[cfg(feature = "v2")]
+use api_models::enums as api_enums;
 use api_models::payments::{
     Address, ConnectorMandateReferenceId, CustomerDetails, CustomerDetailsResponse, FrmMessage,
     MandateIds, NetworkDetails, RequestSurchargeDetails,
@@ -21,16 +23,19 @@ use diesel_models::{
     },
 };
 use error_stack::{report, ResultExt};
-#[cfg(feature = "v2")]
-use hyperswitch_domain_models::ApiModelToDieselModelConvertor;
 use hyperswitch_domain_models::{payments::payment_intent::CustomerData, router_request_types};
 #[cfg(feature = "v2")]
+use hyperswitch_domain_models::{
+    router_data_v2::{flow_common_types, RouterDataV2},
+    ApiModelToDieselModelConvertor,
+};
+#[cfg(feature = "v2")]
 use hyperswitch_interfaces::api::ConnectorSpecifications;
 #[cfg(feature = "v2")]
 use hyperswitch_interfaces::connector_integration_interface::RouterDataConversion;
-#[cfg(feature = "v2")]
-use masking::PeekInterface;
 use masking::{ExposeInterface, Maskable, Secret};
+#[cfg(feature = "v2")]
+use masking::{ExposeOptionInterface, PeekInterface};
 use router_env::{instrument, tracing};
 
 use super::{flows::Feature, types::AuthenticationData, OperationSessionGetters, PaymentData};
@@ -200,7 +205,7 @@ pub async fn construct_external_vault_proxy_router_data_v2<'a>(
     customer_id: Option<common_utils::id_type::CustomerId>,
     header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
 ) -> RouterResult<
-    hyperswitch_domain_models::router_data_v2::RouterDataV2<
+    RouterDataV2<
         api::ExternalVaultProxy,
         hyperswitch_domain_models::router_data_v2::ExternalVaultProxyFlowData,
         types::ExternalVaultProxyPaymentsData,
@@ -685,11 +690,12 @@ pub async fn construct_external_vault_proxy_payment_router_data<'a>(
     .await?;
 
     // Convert RouterDataV2 to old RouterData (v1) using the existing RouterDataConversion trait
-    let router_data = hyperswitch_domain_models::router_data_v2::flow_common_types::ExternalVaultProxyFlowData::to_old_router_data(router_data_v2)
-    .change_context(errors::ApiErrorResponse::InternalServerError)
-        .attach_printable(
-            "Cannot construct router data for making the unified connector service call",
-        )?;
+    let router_data =
+        flow_common_types::ExternalVaultProxyFlowData::to_old_router_data(router_data_v2)
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable(
+                "Cannot construct router data for making the unified connector service call",
+            )?;
 
     Ok(router_data)
 }
@@ -991,6 +997,163 @@ pub async fn construct_router_data_for_psync<'a>(
     Ok(router_data)
 }
 
+#[cfg(feature = "v2")]
+#[instrument(skip_all)]
+#[allow(clippy::too_many_arguments)]
+pub async fn construct_cancel_router_data_v2<'a>(
+    state: &'a SessionState,
+    merchant_account: &domain::MerchantAccount,
+    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
+    payment_data: &hyperswitch_domain_models::payments::PaymentCancelData<api::Void>,
+    request: types::PaymentsCancelData,
+    connector_request_reference_id: String,
+    customer_id: Option<common_utils::id_type::CustomerId>,
+    header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
+) -> RouterResult<
+    RouterDataV2<
+        api::Void,
+        flow_common_types::PaymentFlowData,
+        types::PaymentsCancelData,
+        types::PaymentsResponseData,
+    >,
+> {
+    let auth_type: types::ConnectorAuthType = merchant_connector_account
+        .get_connector_account_details()
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Failed while parsing value for ConnectorAuthType")?;
+
+    let payment_cancel_data = flow_common_types::PaymentFlowData {
+        merchant_id: merchant_account.get_id().clone(),
+        customer_id,
+        connector_customer: None,
+        payment_id: payment_data
+            .payment_attempt
+            .payment_id
+            .get_string_repr()
+            .to_owned(),
+        attempt_id: payment_data
+            .payment_attempt
+            .get_id()
+            .get_string_repr()
+            .to_owned(),
+        status: payment_data.payment_attempt.status,
+        payment_method: payment_data.payment_attempt.payment_method_type,
+        description: payment_data
+            .payment_intent
+            .description
+            .as_ref()
+            .map(|description| description.get_string_repr())
+            .map(ToOwned::to_owned),
+        address: hyperswitch_domain_models::payment_address::PaymentAddress::default(),
+        auth_type: payment_data.payment_attempt.authentication_type,
+        connector_meta_data: merchant_connector_account.get_metadata(),
+        amount_captured: None,
+        minor_amount_captured: None,
+        access_token: None,
+        session_token: None,
+        reference_id: None,
+        payment_method_token: None,
+        recurring_mandate_payment_data: None,
+        preprocessing_id: payment_data.payment_attempt.preprocessing_step_id.clone(),
+        payment_method_balance: None,
+        connector_api_version: None,
+        connector_request_reference_id,
+        test_mode: Some(true),
+        connector_http_status_code: None,
+        external_latency: None,
+        apple_pay_flow: None,
+        connector_response: None,
+        payment_method_status: None,
+    };
+
+    let router_data_v2 = RouterDataV2 {
+        flow: PhantomData,
+        tenant_id: state.tenant.tenant_id.clone(),
+        resource_common_data: payment_cancel_data,
+        connector_auth_type: auth_type,
+        request,
+        response: Err(hyperswitch_domain_models::router_data::ErrorResponse::default()),
+    };
+
+    Ok(router_data_v2)
+}
+
+#[cfg(feature = "v2")]
+#[instrument(skip_all)]
+#[allow(clippy::too_many_arguments)]
+pub async fn construct_router_data_for_cancel<'a>(
+    state: &'a SessionState,
+    payment_data: hyperswitch_domain_models::payments::PaymentCancelData<
+        hyperswitch_domain_models::router_flow_types::Void,
+    >,
+    connector_id: &str,
+    merchant_context: &domain::MerchantContext,
+    customer: &'a Option<domain::Customer>,
+    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
+    _merchant_recipient_data: Option<types::MerchantRecipientData>,
+    header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
+) -> RouterResult<types::PaymentsCancelRouterData> {
+    fp_utils::when(merchant_connector_account.is_disabled(), || {
+        Err(errors::ApiErrorResponse::MerchantConnectorAccountDisabled)
+    })?;
+
+    // TODO: Take Globalid and convert to connector reference id
+    let customer_id = customer
+        .to_owned()
+        .map(|customer| common_utils::id_type::CustomerId::try_from(customer.id.clone()))
+        .transpose()
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable(
+            "Invalid global customer generated, not able to convert to reference id",
+        )?;
+    let payment_intent = payment_data.get_payment_intent();
+    let attempt = payment_data.get_payment_attempt();
+    let connector_request_reference_id = payment_data
+        .payment_attempt
+        .connector_request_reference_id
+        .clone()
+        .ok_or(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("connector_request_reference_id not found in payment_attempt")?;
+
+    let request = types::PaymentsCancelData {
+        amount: Some(attempt.amount_details.get_net_amount().get_amount_as_i64()),
+        currency: Some(payment_intent.amount_details.currency),
+        connector_transaction_id: attempt
+            .get_connector_payment_id()
+            .unwrap_or_default()
+            .to_string(),
+        cancellation_reason: attempt.cancellation_reason.clone(),
+        connector_meta: attempt.connector_metadata.clone().expose_option(),
+        browser_info: None,
+        metadata: None,
+        minor_amount: Some(attempt.amount_details.get_net_amount()),
+        webhook_url: None,
+        capture_method: Some(payment_intent.capture_method),
+    };
+
+    // Construct RouterDataV2 for cancel operation
+    let router_data_v2 = construct_cancel_router_data_v2(
+        state,
+        merchant_context.get_merchant_account(),
+        merchant_connector_account,
+        &payment_data,
+        request,
+        connector_request_reference_id.clone(),
+        customer_id.clone(),
+        header_payload.clone(),
+    )
+    .await?;
+
+    // Convert RouterDataV2 to old RouterData (v1) using the existing RouterDataConversion trait
+    let router_data = flow_common_types::PaymentFlowData::to_old_router_data(router_data_v2)
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable(
+            "Cannot construct router data for making the unified connector service call",
+        )?;
+
+    Ok(router_data)
+}
+
 #[cfg(feature = "v2")]
 #[instrument(skip_all)]
 #[allow(clippy::too_many_arguments)]
@@ -1978,6 +2141,63 @@ where
     }
 }
 
+#[cfg(feature = "v2")]
+impl<F> GenerateResponse<api_models::payments::PaymentsCancelResponse>
+    for hyperswitch_domain_models::payments::PaymentCancelData<F>
+where
+    F: Clone,
+{
+    fn generate_response(
+        self,
+        state: &SessionState,
+        connector_http_status_code: Option<u16>,
+        external_latency: Option<u128>,
+        is_latency_header_enabled: Option<bool>,
+        merchant_context: &domain::MerchantContext,
+        profile: &domain::Profile,
+        _connector_response_data: Option<common_types::domain::ConnectorResponseData>,
+    ) -> RouterResponse<api_models::payments::PaymentsCancelResponse> {
+        let payment_intent = &self.payment_intent;
+        let payment_attempt = &self.payment_attempt;
+
+        let amount = api_models::payments::PaymentAmountDetailsResponse::foreign_from((
+            &payment_intent.amount_details,
+            &payment_attempt.amount_details,
+        ));
+
+        let connector = payment_attempt
+            .connector
+            .as_ref()
+            .and_then(|conn| api_enums::Connector::from_str(conn).ok());
+        let response = api_models::payments::PaymentsCancelResponse {
+            id: payment_intent.id.clone(),
+            status: payment_intent.status,
+            cancellation_reason: payment_attempt.cancellation_reason.clone(),
+            amount,
+            customer_id: payment_intent.customer_id.clone(),
+            connector,
+            created: payment_intent.created_at,
+            payment_method_type: Some(payment_attempt.payment_method_type),
+            payment_method_subtype: Some(payment_attempt.payment_method_subtype),
+            attempts: None,
+            return_url: payment_intent.return_url.clone(),
+        };
+
+        let headers = connector_http_status_code
+            .map(|status_code| {
+                vec![(
+                    X_CONNECTOR_HTTP_STATUS_CODE.to_string(),
+                    Maskable::new_normal(status_code.to_string()),
+                )]
+            })
+            .unwrap_or_default();
+
+        Ok(services::ApplicationResponse::JsonWithHeaders((
+            response, headers,
+        )))
+    }
+}
+
 #[cfg(feature = "v1")]
 impl<F, Op, D> ToResponse<F, D, Op> for api::PaymentsResponse
 where
@@ -4550,7 +4770,55 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCancelDa
     type Error = error_stack::Report<errors::ApiErrorResponse>;
 
     fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
-        todo!()
+        let payment_data = additional_data.payment_data;
+        let connector = api::ConnectorData::get_connector_by_name(
+            &additional_data.state.conf.connectors,
+            &additional_data.connector_name,
+            api::GetToken::Connector,
+            payment_data.payment_attempt.merchant_connector_id.clone(),
+        )?;
+        let browser_info: Option<types::BrowserInformation> = payment_data
+            .payment_attempt
+            .browser_info
+            .clone()
+            .map(types::BrowserInformation::from);
+
+        let amount = payment_data.payment_attempt.amount_details.get_net_amount();
+
+        let router_base_url = &additional_data.router_base_url;
+        let attempt = &payment_data.payment_attempt;
+
+        let merchant_connector_account_id = payment_data
+            .payment_attempt
+            .merchant_connector_id
+            .as_ref()
+            .map(|mca_id| mca_id.get_string_repr())
+            .ok_or(errors::ApiErrorResponse::MerchantAccountNotFound)?;
+        let webhook_url: Option<_> = Some(helpers::create_webhook_url(
+            router_base_url,
+            &attempt.merchant_id,
+            merchant_connector_account_id,
+        ));
+        let capture_method = payment_data.payment_intent.capture_method;
+        Ok(Self {
+            amount: Some(amount.get_amount_as_i64()), // This should be removed once we start moving to connector module
+            minor_amount: Some(amount),
+            currency: Some(payment_data.payment_intent.amount_details.currency),
+            connector_transaction_id: connector
+                .connector
+                .connector_transaction_id(&payment_data.payment_attempt)?
+                .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
+            cancellation_reason: payment_data.payment_attempt.cancellation_reason,
+            connector_meta: payment_data
+                .payment_attempt
+                .connector_metadata
+                .clone()
+                .expose_option(),
+            browser_info,
+            metadata: payment_data.payment_intent.metadata.expose_option(),
+            webhook_url,
+            capture_method: Some(capture_method),
+        })
     }
 }
 
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index d52dc1570ce..dd4bb55c3e4 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -741,7 +741,8 @@ impl Payments {
                 .service(
                     web::resource("/check-gift-card-balance")
                         .route(web::post().to(payments::payment_check_gift_card_balance)),
-                ),
+                )
+                .service(web::resource("/cancel").route(web::post().to(payments::payments_cancel))),
         );
 
         route
diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs
index 6f29b4cb5d9..c1970b64c28 100644
--- a/crates/router/src/routes/payments.rs
+++ b/crates/router/src/routes/payments.rs
@@ -1529,6 +1529,84 @@ pub async fn payments_cancel(
     .await
 }
 
+#[cfg(feature = "v2")]
+#[instrument(skip_all, fields(flow = ?Flow::PaymentsCancel, payment_id))]
+pub async fn payments_cancel(
+    state: web::Data<app::AppState>,
+    req: actix_web::HttpRequest,
+    json_payload: web::Json<payment_types::PaymentsCancelRequest>,
+    path: web::Path<common_utils::id_type::GlobalPaymentId>,
+) -> impl Responder {
+    let flow = Flow::PaymentsCancel;
+    let global_payment_id = path.into_inner();
+
+    tracing::Span::current().record("payment_id", global_payment_id.get_string_repr());
+
+    let internal_payload = internal_payload_types::PaymentsGenericRequestWithResourceId {
+        global_payment_id: global_payment_id.clone(),
+        payload: json_payload.into_inner(),
+    };
+
+    let header_payload = match HeaderPayload::foreign_try_from(req.headers()) {
+        Ok(headers) => headers,
+        Err(err) => {
+            return api::log_and_return_error_response(err);
+        }
+    };
+
+    let locking_action = internal_payload.get_locking_input(flow.clone());
+
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        internal_payload,
+        |state, auth: auth::AuthenticationData, req, req_state| async {
+            let payment_id = req.global_payment_id;
+            let request = req.payload;
+
+            let operation = payments::operations::payment_cancel_v2::PaymentsCancel;
+            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
+                domain::Context(auth.merchant_account, auth.key_store),
+            ));
+
+            Box::pin(payments::payments_core::<
+                hyperswitch_domain_models::router_flow_types::Void,
+                api_models::payments::PaymentsCancelResponse,
+                _,
+                _,
+                _,
+                hyperswitch_domain_models::payments::PaymentCancelData<
+                    hyperswitch_domain_models::router_flow_types::Void,
+                >,
+            >(
+                state,
+                req_state,
+                merchant_context,
+                auth.profile,
+                operation,
+                request,
+                payment_id,
+                payments::CallConnectorAction::Trigger,
+                header_payload.clone(),
+            ))
+            .await
+        },
+        auth::auth_type(
+            &auth::V2ApiKeyAuth {
+                is_connected_allowed: false,
+                is_platform_allowed: false,
+            },
+            &auth::JWTAuth {
+                permission: Permission::ProfilePaymentWrite,
+            },
+            req.headers(),
+        ),
+        locking_action,
+    ))
+    .await
+}
+
 #[cfg(feature = "v1")]
 #[instrument(skip_all, fields(flow = ?Flow::PaymentsCancelPostCapture, payment_id))]
 pub async fn payments_cancel_post_capture(

## 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 V2 payments_cancel flow by endpoint `{{baseUrl}}/v2/payments/:payment_id/cancel`.A payment can be cancelled when the status is RequiresCapture, PartiallyAuthorizedAndRequiresCapture, or PartiallyCapturedAndCapturable.

### 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)?
-->
Request:
```
curl --location 'http://localhost:8080/v2/payments/12345_pay_0199568c7d307cb093a2f9d517fec5cc/cancel' \
--header 'X-Profile-Id: pro_ICrZFKL8QL6GlxNOuGiT' \
--header 'Authorization: api-key=dev_lLeDL7DZk8Vf8vF7PTd53ces09iGYEerHYDBQzle5s1N1QPpzj3pKViOnv7VsQd5' \
--header 'Content-Type: application/json' \
--header 'api-key: dev_lLeDL7DZk8Vf8vF7PTd53ces09iGYEerHYDBQzle5s1N1QPpzj3pKViOnv7VsQd5' \
--data '{
    "cancellation_reason": "Requested by Merchant"
}'
```
Response:
```
{
    "id": "12345_pay_019956bfbb0472f28d713b8190b897b7",
    "status": "cancelled",
    "cancellation_reason": "Requested by Merchant",
    "amount": {
        "order_amount": 100,
        "currency": "USD",
        "shipping_cost": null,
        "order_tax_amount": null,
        "external_tax_calculation": "skip",
        "surcharge_calculation": "skip",
        "surcharge_amount": null,
        "tax_on_surcharge": null,
        "net_amount": 100,
        "amount_to_capture": null,
        "amount_capturable": 100,
        "amount_captured": 0
    },
    "customer_id": "12345_cus_0199568c731e77d2960fde597ad801ac",
    "connector": "authorizedotnet",
    "created": "2025-09-17T08:17:09.937Z",
    "payment_method_type": "card",
    "payment_method_subtype": "credit",
    "attempts": null,
    "return_url": null
}
```

<img width="1314" height="1037" alt="image" src="https://github.com/user-attachments/assets/3e72e0fe-b0a5-46d8-9102-d8431518773d" />

<img width="3600" height="2508" alt="image" src="https://github.com/user-attachments/assets/fe1e4fbe-454a-4684-b009-820c9281b1b1" />

<img width="1228" height="426" alt="image" src="https://github.com/user-attachments/assets/73117c2d-600a-4ee4-bcbd-a43c5b7c55cd" />



closes #9082 

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