Bug: [FEAT] (CONNECTOR): Facilitapay webhook support



add support for facilitapay webhooks
diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml
index 90aae593617..cfc8589bdf2 100644
--- a/crates/connector_configs/toml/development.toml
+++ b/crates/connector_configs/toml/development.toml
@@ -6157,8 +6157,8 @@ api_secret="Secret Key"
   key1="Username"
 [facilitapay.metadata.destination_account_number]
   name="destination_account_number"
-  label="Destination Account Number"
-  placeholder="Enter Destination Account Number"
+  label="Merchant Account Number"
+  placeholder="Enter Merchant's (to_bank_account_id) Account Number"
   required=true
   type="Text"
 
diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml
index 704d85ea665..5e21a2246b0 100644
--- a/crates/connector_configs/toml/production.toml
+++ b/crates/connector_configs/toml/production.toml
@@ -4725,8 +4725,8 @@ key1 = "Username"
 
 [facilitapay.metadata.destination_account_number]
 name="destination_account_number"
-label="Destination Account Number"
-placeholder="Enter Destination Account Number"
+label="Merchant Account Number"
+placeholder="Enter Merchant's (to_bank_account_id) Account Number"
 required=true
 type="Text"
 
diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml
index 22819c5d4be..a256b81af66 100644
--- a/crates/connector_configs/toml/sandbox.toml
+++ b/crates/connector_configs/toml/sandbox.toml
@@ -6139,8 +6139,8 @@ key1 = "Username"
 
 [facilitapay.metadata.destination_account_number]
 name="destination_account_number"
-label="Destination Account Number"
-placeholder="Enter Destination Account Number"
+label="Merchant Account Number"
+placeholder="Enter Merchant's (to_bank_account_id) Account Number"
 required=true
 type="Text"
 
diff --git a/crates/hyperswitch_connectors/src/connectors/facilitapay.rs b/crates/hyperswitch_connectors/src/connectors/facilitapay.rs
index 9b1a6486df4..ffb74cc3a87 100644
--- a/crates/hyperswitch_connectors/src/connectors/facilitapay.rs
+++ b/crates/hyperswitch_connectors/src/connectors/facilitapay.rs
@@ -4,12 +4,13 @@ pub mod transformers;
 
 use common_enums::enums;
 use common_utils::{
+    crypto,
     errors::CustomResult,
-    ext_traits::BytesExt,
+    ext_traits::{ByteSliceExt, BytesExt, ValueExt},
     request::{Method, Request, RequestBuilder, RequestContent},
     types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector},
 };
-use error_stack::{report, ResultExt};
+use error_stack::ResultExt;
 use hyperswitch_domain_models::{
     router_data::{AccessToken, ErrorResponse, RouterData},
     router_flow_types::{
@@ -30,8 +31,8 @@ use hyperswitch_domain_models::{
         SupportedPaymentMethods, SupportedPaymentMethodsExt,
     },
     types::{
-        ConnectorCustomerRouterData, PaymentsAuthorizeRouterData, PaymentsCaptureRouterData,
-        PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData,
+        ConnectorCustomerRouterData, PaymentsAuthorizeRouterData, PaymentsCancelRouterData,
+        PaymentsCaptureRouterData, PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData,
     },
 };
 use hyperswitch_interfaces::{
@@ -46,18 +47,19 @@ use hyperswitch_interfaces::{
     webhooks,
 };
 use lazy_static::lazy_static;
-use masking::{Mask, PeekInterface};
+use masking::{ExposeInterface, Mask, PeekInterface};
 use requests::{
     FacilitapayAuthRequest, FacilitapayCustomerRequest, FacilitapayPaymentsRequest,
-    FacilitapayRefundRequest, FacilitapayRouterData,
+    FacilitapayRouterData,
 };
 use responses::{
     FacilitapayAuthResponse, FacilitapayCustomerResponse, FacilitapayPaymentsResponse,
-    FacilitapayRefundResponse,
+    FacilitapayRefundResponse, FacilitapayWebhookEventType,
 };
 use transformers::parse_facilitapay_error_response;
 
 use crate::{
+    connectors::facilitapay::responses::FacilitapayVoidResponse,
     constants::headers,
     types::{RefreshTokenRouterData, ResponseRouterData},
     utils::{self, RefundsRequestData},
@@ -581,12 +583,10 @@ impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> fo
     }
 }
 
-impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Facilitapay {}
-
-impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Facilitapay {
+impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Facilitapay {
     fn get_headers(
         &self,
-        req: &RefundsRouterData<Execute>,
+        req: &PaymentsCancelRouterData,
         connectors: &Connectors,
     ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
         self.build_headers(req, connectors)
@@ -598,30 +598,81 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Facilit
 
     fn get_url(
         &self,
-        req: &RefundsRouterData<Execute>,
+        req: &PaymentsCancelRouterData,
         connectors: &Connectors,
     ) -> CustomResult<String, errors::ConnectorError> {
         Ok(format!(
-            "{}/transactions/{}/refund_received_transaction",
+            "{}/transactions/{}/refund",
             self.base_url(connectors),
             req.request.connector_transaction_id
         ))
     }
 
-    fn get_request_body(
+    fn build_request(
+        &self,
+        req: &PaymentsCancelRouterData,
+        connectors: &Connectors,
+    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
+        let request = RequestBuilder::new()
+            .method(Method::Get)
+            .url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
+            .attach_default_headers()
+            .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
+            .build();
+        Ok(Some(request))
+    }
+
+    fn handle_response(
+        &self,
+        data: &PaymentsCancelRouterData,
+        event_builder: Option<&mut ConnectorEvent>,
+        res: Response,
+    ) -> CustomResult<PaymentsCancelRouterData, errors::ConnectorError> {
+        let response: FacilitapayVoidResponse = res
+            .response
+            .parse_struct("FacilitapayCancelResponse")
+            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
+        event_builder.map(|i| i.set_response_body(&response));
+        router_env::logger::info!(connector_response=?response);
+        RouterData::try_from(ResponseRouterData {
+            response,
+            data: data.clone(),
+            http_code: res.status_code,
+        })
+    }
+
+    fn get_error_response(
+        &self,
+        res: Response,
+        event_builder: Option<&mut ConnectorEvent>,
+    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
+        self.build_error_response(res, event_builder)
+    }
+}
+
+impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Facilitapay {
+    fn get_headers(
         &self,
         req: &RefundsRouterData<Execute>,
-        _connectors: &Connectors,
-    ) -> CustomResult<RequestContent, errors::ConnectorError> {
-        let refund_amount = utils::convert_amount(
-            self.amount_converter,
-            req.request.minor_refund_amount,
-            req.request.currency,
-        )?;
+        connectors: &Connectors,
+    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
+        self.build_headers(req, connectors)
+    }
 
-        let connector_router_data = FacilitapayRouterData::from((refund_amount, req));
-        let connector_req = FacilitapayRefundRequest::try_from(&connector_router_data)?;
-        Ok(RequestContent::Json(Box::new(connector_req)))
+    fn get_content_type(&self) -> &'static str {
+        self.common_get_content_type()
+    }
+
+    fn get_url(
+        &self,
+        req: &RefundsRouterData<Execute>,
+        connectors: &Connectors,
+    ) -> CustomResult<String, errors::ConnectorError> {
+        Ok(format!(
+            "{}/transactions/{}/refund_received_transaction",
+            self.base_url(connectors),
+            req.request.connector_transaction_id
+        ))
     }
 
     fn build_request(
@@ -629,16 +680,22 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Facilit
         req: &RefundsRouterData<Execute>,
         connectors: &Connectors,
     ) -> CustomResult<Option<Request>, errors::ConnectorError> {
+        // Validate that this is a full refund
+        if req.request.payment_amount != req.request.refund_amount {
+            return Err(errors::ConnectorError::NotSupported {
+                message: "Partial refund not supported by Facilitapay".to_string(),
+                connector: "Facilitapay",
+            }
+            .into());
+        }
+
         let request = RequestBuilder::new()
-            .method(Method::Post)
+            .method(Method::Get)
             .url(&types::RefundExecuteType::get_url(self, req, connectors)?)
             .attach_default_headers()
             .headers(types::RefundExecuteType::get_headers(
                 self, req, connectors,
             )?)
-            .set_body(types::RefundExecuteType::get_request_body(
-                self, req, connectors,
-            )?)
             .build();
         Ok(Some(request))
     }
@@ -743,25 +800,117 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Facilitap
 
 #[async_trait::async_trait]
 impl webhooks::IncomingWebhook for Facilitapay {
+    async fn verify_webhook_source(
+        &self,
+        request: &webhooks::IncomingWebhookRequestDetails<'_>,
+        _merchant_id: &common_utils::id_type::MerchantId,
+        connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
+        _connector_account_details: crypto::Encryptable<masking::Secret<serde_json::Value>>,
+        _connector_name: &str,
+    ) -> CustomResult<bool, errors::ConnectorError> {
+        let webhook_body: responses::FacilitapayWebhookNotification = request
+            .body
+            .parse_struct("FacilitapayWebhookNotification")
+            .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
+
+        let connector_webhook_secrets = match connector_webhook_details {
+            Some(secret_value) => {
+                let secret = secret_value
+                    .parse_value::<api_models::admin::MerchantConnectorWebhookDetails>(
+                        "MerchantConnectorWebhookDetails",
+                    )
+                    .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
+                secret.merchant_secret.expose()
+            }
+            None => "default_secret".to_string(),
+        };
+
+        // FacilitaPay uses a simple 4-digit secret for verification
+        Ok(webhook_body.notification.secret.peek() == &connector_webhook_secrets)
+    }
+
     fn get_webhook_object_reference_id(
         &self,
-        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
+        request: &webhooks::IncomingWebhookRequestDetails<'_>,
     ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
-        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
+        let webhook_body: responses::FacilitapayWebhookNotification = request
+            .body
+            .parse_struct("FacilitapayWebhookNotification")
+            .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
+
+        // Extract transaction ID from the webhook data
+        let transaction_id = match &webhook_body.notification.data {
+            responses::FacilitapayWebhookData::Transaction { transaction_id }
+            | responses::FacilitapayWebhookData::CardPayment { transaction_id, .. } => {
+                transaction_id.clone()
+            }
+            responses::FacilitapayWebhookData::Exchange {
+                transaction_ids, ..
+            }
+            | responses::FacilitapayWebhookData::Wire {
+                transaction_ids, ..
+            }
+            | responses::FacilitapayWebhookData::WireError {
+                transaction_ids, ..
+            } => transaction_ids
+                .first()
+                .ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?
+                .clone(),
+        };
+
+        // For refund webhooks, Facilitapay sends the original payment transaction ID
+        // not the refund transaction ID
+        Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
+            api_models::payments::PaymentIdType::ConnectorTransactionId(transaction_id),
+        ))
     }
 
     fn get_webhook_event_type(
         &self,
-        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
+        request: &webhooks::IncomingWebhookRequestDetails<'_>,
     ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
-        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
+        let webhook_body: responses::FacilitapayWebhookNotification = request
+            .body
+            .parse_struct("FacilitapayWebhookNotification")
+            .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
+
+        // Note: For "identified" events, we need additional logic to determine if it's cross-currency
+        // Since we don't have access to the payment data here, we'll default to Success for now
+        // The actual status determination happens in the webhook processing flow
+        let event = match webhook_body.notification.event_type {
+            FacilitapayWebhookEventType::ExchangeCreated => {
+                api_models::webhooks::IncomingWebhookEvent::PaymentIntentProcessing
+            }
+            FacilitapayWebhookEventType::Identified
+            | FacilitapayWebhookEventType::PaymentApproved
+            | FacilitapayWebhookEventType::WireCreated => {
+                api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess
+            }
+            FacilitapayWebhookEventType::PaymentExpired
+            | FacilitapayWebhookEventType::PaymentFailed => {
+                api_models::webhooks::IncomingWebhookEvent::PaymentIntentFailure
+            }
+            FacilitapayWebhookEventType::PaymentRefunded => {
+                api_models::webhooks::IncomingWebhookEvent::RefundSuccess
+            }
+            FacilitapayWebhookEventType::WireWaitingCorrection => {
+                api_models::webhooks::IncomingWebhookEvent::PaymentActionRequired
+            }
+        };
+
+        Ok(event)
     }
 
     fn get_webhook_resource_object(
         &self,
-        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
+        request: &webhooks::IncomingWebhookRequestDetails<'_>,
     ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
-        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
+        let webhook_body: responses::FacilitapayWebhookNotification = request
+            .body
+            .parse_struct("FacilitapayWebhookNotification")
+            .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
+
+        Ok(Box::new(webhook_body))
     }
 }
 
@@ -794,7 +943,9 @@ lazy_static! {
 
         facilitapay_supported_payment_methods
     };
-    static ref FACILITAPAY_SUPPORTED_WEBHOOK_FLOWS: Vec<enums::EventClass> = Vec::new();
+    static ref FACILITAPAY_SUPPORTED_WEBHOOK_FLOWS: Vec<enums::EventClass> = vec![
+        enums::EventClass::Payments,
+    ];
 }
 
 impl ConnectorSpecifications for Facilitapay {
diff --git a/crates/hyperswitch_connectors/src/connectors/facilitapay/requests.rs b/crates/hyperswitch_connectors/src/connectors/facilitapay/requests.rs
index 29c2f34f332..a14da2dd57a 100644
--- a/crates/hyperswitch_connectors/src/connectors/facilitapay/requests.rs
+++ b/crates/hyperswitch_connectors/src/connectors/facilitapay/requests.rs
@@ -69,12 +69,6 @@ pub struct FacilitapayPaymentsRequest {
     pub transaction: FacilitapayTransactionRequest,
 }
 
-// Type definition for RefundRequest
-#[derive(Default, Debug, Serialize)]
-pub struct FacilitapayRefundRequest {
-    pub amount: StringMajorUnit,
-}
-
 #[derive(Debug, Serialize, PartialEq)]
 #[serde(rename_all = "snake_case")]
 pub struct FacilitapayCustomerRequest {
diff --git a/crates/hyperswitch_connectors/src/connectors/facilitapay/responses.rs b/crates/hyperswitch_connectors/src/connectors/facilitapay/responses.rs
index dfa01402ce9..ead8569154f 100644
--- a/crates/hyperswitch_connectors/src/connectors/facilitapay/responses.rs
+++ b/crates/hyperswitch_connectors/src/connectors/facilitapay/responses.rs
@@ -136,6 +136,7 @@ pub struct BankAccountDetail {
     pub routing_number: Option<Secret<String>>,
     pub pix_info: Option<PixInfo>,
     pub owner_name: Option<Secret<String>>,
+    pub owner_document_type: Option<String>,
     pub owner_document_number: Option<Secret<String>>,
     pub owner_company: Option<OwnerCompany>,
     pub internal: Option<bool>,
@@ -176,7 +177,7 @@ pub struct TransactionData {
     pub subject_is_receiver: Option<bool>,
 
     // Source identification (potentially redundant with subject or card/bank info)
-    pub source_name: Secret<String>,
+    pub source_name: Option<Secret<String>>,
     pub source_document_type: DocumentType,
     pub source_document_number: Secret<String>,
 
@@ -204,14 +205,124 @@ pub struct TransactionData {
     pub meta: Option<serde_json::Value>,
 }
 
+// Void response structures (for /refund endpoint)
 #[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct RefundData {
+pub struct VoidBankTransaction {
     #[serde(rename = "id")]
-    pub refund_id: String,
+    pub transaction_id: String,
+    pub value: StringMajorUnit,
+    pub currency: api_models::enums::Currency,
+    pub iof_value: Option<StringMajorUnit>,
+    pub fx_value: Option<StringMajorUnit>,
+    pub exchange_rate: Option<StringMajorUnit>,
+    pub exchange_currency: api_models::enums::Currency,
+    pub exchanged_value: StringMajorUnit,
+    pub exchange_approved: bool,
+    pub wire_id: Option<String>,
+    pub exchange_id: Option<String>,
+    pub movement_date: String,
+    pub source_name: Secret<String>,
+    pub source_document_number: Secret<String>,
+    pub source_document_type: String,
+    pub source_id: String,
+    pub source_type: String,
+    pub source_description: String,
+    pub source_bank: Option<String>,
+    pub source_branch: Option<String>,
+    pub source_account: Option<String>,
+    pub source_bank_ispb: Option<String>,
+    pub company_id: String,
+    pub company_name: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct VoidData {
+    #[serde(rename = "id")]
+    pub void_id: String,
+    pub reason: Option<String>,
+    pub inserted_at: String,
     pub status: FacilitapayPaymentStatus,
+    pub transaction_kind: String,
+    pub bank_transaction: VoidBankTransaction,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct FacilitapayVoidResponse {
+    pub data: VoidData,
 }
 
+// Refund response uses the same TransactionData structure as payments
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct FacilitapayRefundResponse {
-    pub data: RefundData,
+    pub data: TransactionData,
+}
+
+// Webhook structures
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct FacilitapayWebhookNotification {
+    pub notification: FacilitapayWebhookBody,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub struct FacilitapayWebhookBody {
+    #[serde(rename = "type")]
+    pub event_type: FacilitapayWebhookEventType,
+    pub secret: Secret<String>,
+    #[serde(flatten)]
+    pub data: FacilitapayWebhookData,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "snake_case")]
+pub enum FacilitapayWebhookEventType {
+    ExchangeCreated,
+    Identified,
+    PaymentApproved,
+    PaymentExpired,
+    PaymentFailed,
+    PaymentRefunded,
+    WireCreated,
+    WireWaitingCorrection,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "UPPERCASE")]
+pub enum FacilitapayWebhookErrorCode {
+    /// Creditor account number invalid or missing (branch_number or account_number incorrect)
+    Ac03,
+    /// Creditor account type missing or invalid (account_type incorrect)
+    Ac14,
+    /// Value in Creditor Identifier is incorrect (owner_document_number incorrect)
+    Ch11,
+    /// Transaction type not supported/authorized on this account (account rejected the payment)
+    Ag03,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum FacilitapayWebhookData {
+    CardPayment {
+        transaction_id: String,
+        checkout_id: Option<String>,
+    },
+    Exchange {
+        exchange_id: String,
+        transaction_ids: Vec<String>,
+    },
+    Transaction {
+        transaction_id: String,
+    },
+    Wire {
+        wire_id: String,
+        transaction_ids: Vec<String>,
+    },
+    WireError {
+        error_code: FacilitapayWebhookErrorCode,
+        error_description: String,
+        bank_account_owner_id: String,
+        bank_account_id: String,
+        transaction_ids: Vec<String>,
+        wire_id: String,
+    },
 }
diff --git a/crates/hyperswitch_connectors/src/connectors/facilitapay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/facilitapay/transformers.rs
index c0025fe44ff..d0e186cf35b 100644
--- a/crates/hyperswitch_connectors/src/connectors/facilitapay/transformers.rs
+++ b/crates/hyperswitch_connectors/src/connectors/facilitapay/transformers.rs
@@ -11,8 +11,11 @@ use error_stack::ResultExt;
 use hyperswitch_domain_models::{
     payment_method_data::{BankTransferData, PaymentMethodData},
     router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
-    router_flow_types::refunds::{Execute, RSync},
-    router_request_types::ResponseId,
+    router_flow_types::{
+        payments::Void,
+        refunds::{Execute, RSync},
+    },
+    router_request_types::{PaymentsCancelData, ResponseId},
     router_response_types::{PaymentsResponseData, RefundsResponseData},
     types,
 };
@@ -27,12 +30,12 @@ use url::Url;
 use super::{
     requests::{
         DocumentType, FacilitapayAuthRequest, FacilitapayCredentials, FacilitapayCustomerRequest,
-        FacilitapayPaymentsRequest, FacilitapayPerson, FacilitapayRefundRequest,
-        FacilitapayRouterData, FacilitapayTransactionRequest, PixTransactionRequest,
+        FacilitapayPaymentsRequest, FacilitapayPerson, FacilitapayRouterData,
+        FacilitapayTransactionRequest, PixTransactionRequest,
     },
     responses::{
         FacilitapayAuthResponse, FacilitapayCustomerResponse, FacilitapayPaymentStatus,
-        FacilitapayPaymentsResponse, FacilitapayRefundResponse,
+        FacilitapayPaymentsResponse, FacilitapayRefundResponse, FacilitapayVoidResponse,
     },
 };
 use crate::{
@@ -509,17 +512,6 @@ fn get_qr_code_data(
         .change_context(errors::ConnectorError::ResponseHandlingFailed)
 }
 
-impl<F> TryFrom<&FacilitapayRouterData<&types::RefundsRouterData<F>>> for FacilitapayRefundRequest {
-    type Error = Error;
-    fn try_from(
-        item: &FacilitapayRouterData<&types::RefundsRouterData<F>>,
-    ) -> Result<Self, Self::Error> {
-        Ok(Self {
-            amount: item.amount.clone(),
-        })
-    }
-}
-
 impl From<FacilitapayPaymentStatus> for enums::RefundStatus {
     fn from(item: FacilitapayPaymentStatus) -> Self {
         match item {
@@ -532,6 +524,56 @@ impl From<FacilitapayPaymentStatus> for enums::RefundStatus {
     }
 }
 
+// Void (cancel unprocessed payment) transformer
+impl
+    TryFrom<
+        ResponseRouterData<Void, FacilitapayVoidResponse, PaymentsCancelData, PaymentsResponseData>,
+    > for RouterData<Void, PaymentsCancelData, PaymentsResponseData>
+{
+    type Error = Error;
+    fn try_from(
+        item: ResponseRouterData<
+            Void,
+            FacilitapayVoidResponse,
+            PaymentsCancelData,
+            PaymentsResponseData,
+        >,
+    ) -> Result<Self, Self::Error> {
+        let status = common_enums::AttemptStatus::from(item.response.data.status.clone());
+
+        Ok(Self {
+            status,
+            response: if is_payment_failure(status) {
+                Err(ErrorResponse {
+                    code: item.response.data.status.clone().to_string(),
+                    message: item.response.data.status.clone().to_string(),
+                    reason: item.response.data.reason,
+                    status_code: item.http_code,
+                    attempt_status: None,
+                    connector_transaction_id: Some(item.response.data.void_id.clone()),
+                    network_decline_code: None,
+                    network_advice_code: None,
+                    network_error_message: None,
+                })
+            } else {
+                Ok(PaymentsResponseData::TransactionResponse {
+                    resource_id: ResponseId::ConnectorTransactionId(
+                        item.response.data.void_id.clone(),
+                    ),
+                    redirection_data: Box::new(None),
+                    mandate_reference: Box::new(None),
+                    connector_metadata: None,
+                    network_txn_id: None,
+                    connector_response_reference_id: Some(item.response.data.void_id),
+                    incremental_authorization_allowed: None,
+                    charges: None,
+                })
+            },
+            ..item.data
+        })
+    }
+}
+
 impl TryFrom<RefundsResponseRouterData<Execute, FacilitapayRefundResponse>>
     for types::RefundsRouterData<Execute>
 {
@@ -541,7 +583,7 @@ impl TryFrom<RefundsResponseRouterData<Execute, FacilitapayRefundResponse>>
     ) -> Result<Self, Self::Error> {
         Ok(Self {
             response: Ok(RefundsResponseData {
-                connector_refund_id: item.response.data.refund_id.to_string(),
+                connector_refund_id: item.response.data.transaction_id.clone(),
                 refund_status: enums::RefundStatus::from(item.response.data.status),
             }),
             ..item.data
@@ -558,7 +600,7 @@ impl TryFrom<RefundsResponseRouterData<RSync, FacilitapayRefundResponse>>
     ) -> Result<Self, Self::Error> {
         Ok(Self {
             response: Ok(RefundsResponseData {
-                connector_refund_id: item.response.data.refund_id.to_string(),
+                connector_refund_id: item.response.data.transaction_id.clone(),
                 refund_status: enums::RefundStatus::from(item.response.data.status),
             }),
             ..item.data

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

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

## Description
<!-- Describe your changes in detail -->

This Facilitapay PR introudces 2 new features and a bug fix which is described below:

- Add support for webhooks
  - Source verification supported
  - Payments
  - Refunds (It is only supported for pix payment when a third party makes a transaction instead of the actual user). The payload for both payments and refunds is exactly the same with 0 difference. It is kind of use less to have webhook for support for Refunds
- Add support for voiding payments
  - At Facilita, `refund` means voiding a payment
  - This is a `GET` call with no request body
- Fix a bug in Refunds (It was not testable until now)
  - At Facilita, `refund_received_transaction` means refunding a payment
  - This is a `GET` call with no request body
  - Does not support partial refunds

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

closes https://github.com/juspay/hyperswitch/issues/8777

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

<details>

<summary>Partial refunds</summary>

```sh
curl --location 'http://localhost:8080/refunds' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_FwRHVH9xL5cvYOsONW8YzIphnGhIibnEy9tCWuNCpDLwSnzR4Ilogl6sia26pcND' \
--data '{
    "payment_id": "pay_le5uOFoizxZPXFHPQuK1",
    "amount": 1000,
    "reason": "Customer returned product",
    "refund_type": "instant",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}'
```
```json
{
    "error": {
        "type": "invalid_request",
        "message": "Payment method type not supported",
        "code": "IR_19",
        "reason": "Partial refund not supported by Facilitapay is not supported by Facilitapay"
    }
}
```

</details>

<details>

<summary>Refunds</summary>

```sh
curl --location 'http://localhost:8080/refunds' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_FwRHVH9xL5cvYOsONW8YzIphnGhIibnEy9tCWuNCpDLwSnzR4Ilogl6sia26pcND' \
--data '{
    "payment_id": "pay_le5uOFoizxZPXFHPQuK1",
    "amount": 10400,
    "reason": "Customer returned product",
    "refund_type": "instant",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}'
```
```json
{
    "refund_id": "ref_bEMav88LA7llD0KS6Twk",
    "payment_id": "pay_le5uOFoizxZPXFHPQuK1",
    "amount": 10400,
    "currency": "BRL",
    "status": "succeeded",
    "reason": "Customer returned product",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    },
    "error_message": null,
    "error_code": null,
    "unified_code": null,
    "unified_message": null,
    "created_at": "2025-07-28T18:43:11.748Z",
    "updated_at": "2025-07-28T18:43:12.819Z",
    "connector": "facilitapay",
    "profile_id": "pro_ODZKRkH2uJIfAPClUy8B",
    "merchant_connector_id": "mca_A4Kpc6NUvBsN8RGvvJsm",
    "split_refunds": null,
    "issuer_error_code": null,
    "issuer_error_message": null
}
```

will introduce cypress in a separate pr

</details>

<details>

<summary>Void</summary>

```sh
curl --location 'https://pix-mbp.orthrus-monster.ts.net/payments/pay_XIq2h8snAnSp425StpHM/cancel' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_FwRHVH9xL5cvYOsONW8YzIphnGhIibnEy9tCWuNCpDLwSnzR4Ilogl6sia26pcND' \
--data '{"cancellation_reason":"requested_by_customer"}'
```
```json
{
    "payment_id": "pay_XIq2h8snAnSp425StpHM",
    "merchant_id": "postman_merchant_GHAction_1753728068",
    "status": "cancelled",
    "amount": 10400,
    "net_amount": 10400,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": null,
    "connector": null,
    "client_secret": "pay_XIq2h8snAnSp425StpHM_secret_Za7LqUCCvag8LBMCyYBp",
    "created": "2025-07-28T18:44:39.081Z",
    "currency": "BRL",
    "customer_id": null,
    "customer": {
        "id": null,
        "name": null,
        "email": "guesjhvghht@example.com",
        "phone": "999999999",
        "phone_country_code": "+1"
    },
    "description": "Its my first payment request",
    "refunds": null,
    "disputes": null,
    "mandate_id": null,
    "mandate_data": null,
    "setup_future_usage": null,
    "off_session": null,
    "capture_on": null,
    "capture_method": "automatic",
    "payment_method": null,
    "payment_method_data": null,
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "BR",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": null
        },
        "phone": null,
        "email": null
    },
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": "BR",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": "THE"
        },
        "phone": null,
        "email": null
    },
    "order_details": null,
    "email": null,
    "name": null,
    "phone": null,
    "return_url": "https://duck.com/",
    "authentication_type": "three_ds",
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "next_action": null,
    "cancellation_reason": "requested_by_customer",
    "error_code": null,
    "error_message": null,
    "unified_code": null,
    "unified_message": null,
    "payment_experience": null,
    "payment_method_type": null,
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": null,
    "manual_retry_allowed": false,
    "connector_transaction_id": null,
    "frm_message": null,
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": null,
    "payment_link": null,
    "profile_id": "pro_ODZKRkH2uJIfAPClUy8B",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": null,
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-07-28T18:59:39.081Z",
    "fingerprint": null,
    "browser_info": {
        "language": "en-GB",
        "time_zone": -330,
        "ip_address": "208.127.127.193",
        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0",
        "color_depth": 24,
        "java_enabled": true,
        "screen_width": 1280,
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "screen_height": 720,
        "java_script_enabled": true
    },
    "payment_method_id": null,
    "payment_method_status": null,
    "updated": "2025-07-28T18:44:43.912Z",
    "split_payments": null,
    "frm_metadata": null,
    "extended_authorization_applied": null,
    "capture_before": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null,
    "card_discovery": null,
    "force_3ds_challenge": false,
    "force_3ds_challenge_trigger": false,
    "issuer_error_code": null,
    "issuer_error_message": null,
    "is_iframe_redirection_enabled": null,
    "whole_connector_response": null
}
```

</details>


<details>

<summary>Webhook Setup</summary>

```sh
curl --location 'https://sandbox-api.facilitapay.com/api/v1/enable_webhooks' \
--header 'Authorization: Bearer <jwt_token>' \
--header 'Content-Type: application/json' \
--data '{
    "url": "https://baseUrl/webhooks/:merchant_id/facilitapay"
    
}'
```
```json
{
    "data": {
        "secret": "1111"
    }
}
```

</details>


<details>

<summary>Payment Webhook</summary>

Source verification:

<img width="930" height="192" alt="image" src="https://github.com/user-attachments/assets/cfcb1f63-4102-4b84-95d7-c16522725e67" />

Incoming Webhook Logs:

<img width="1682" height="607" alt="image" src="https://github.com/user-attachments/assets/1411a32c-195a-4e4c-b0ba-2fc062087d43" />

Outgoing Webhook:

<img width="1435" height="558" alt="image" src="https://github.com/user-attachments/assets/b98c4292-1855-4315-8962-62b7bfb4ba85" />

</details>

<details>

<summary>Refund Webhook</summary>

The `payment_refunded` webhook is only sent for a very specific use case - when a pix payment is automatically refunded because it was paid from a third-party account. for regular api-initiated refunds, facilitapay sends the same identified and wire_created webhooks as for payments, making it impossible to distinguish between payment and refund webhooks.

</details>

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

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `just clippy && just clippy_v2`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
