Bug: feat(webhooks): Provide outgoing webhook support for revenue recovery



Provide outgoing webhook support for revenue recovery. Based on the retry status send out outgoing webhooks to the url in business profile in v2.
diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs
index bb53eb1b6ee..52f75bbe8be 100644
--- a/crates/api_models/src/payments.rs
+++ b/crates/api_models/src/payments.rs
@@ -6170,6 +6170,11 @@ pub struct PaymentsResponse {
     #[serde(with = "common_utils::custom_serde::iso8601")]
     pub created: PrimitiveDateTime,
 
+    /// Time when the payment was last modified
+    #[schema(example = "2022-09-10T10:11:12Z")]
+    #[serde(with = "common_utils::custom_serde::iso8601")]
+    pub modified_at: PrimitiveDateTime,
+
     /// The payment method information provided for making a payment
     #[schema(value_type = Option<PaymentMethodDataResponseWithBilling>)]
     #[serde(serialize_with = "serialize_payment_method_data_response")]
@@ -6252,6 +6257,10 @@ pub struct PaymentsResponse {
 
     /// Additional data that might be required by hyperswitch based on the additional features.
     pub feature_metadata: Option<FeatureMetadata>,
+
+    /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
+    #[schema(value_type = Option<Object>, example = r#"{ "udf1": "some-value", "udf2": "some-value" }"#)]
+    pub metadata: Option<pii::SecretSerdeValue>,
 }
 
 #[cfg(feature = "v2")]
@@ -9662,6 +9671,10 @@ pub struct RecoveryPaymentsCreate {
 
     /// Type of action that needs to be taken after consuming the recovery payload. For example: scheduling a failed payment or stopping the invoice.
     pub action: common_payments_types::RecoveryAction,
+
+    /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
+    #[schema(value_type = Option<Object>, example = r#"{ "udf1": "some-value", "udf2": "some-value" }"#)]
+    pub metadata: Option<pii::SecretSerdeValue>,
 }
 
 /// Error details for the payment
diff --git a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs
index 6d942980e2f..83e4795f3ef 100644
--- a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs
+++ b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs
@@ -612,6 +612,7 @@ impl TryFrom<ChargebeeInvoiceBody> for revenue_recovery::RevenueRecoveryInvoiceD
             retry_count,
             next_billing_at: invoice_next_billing_time,
             billing_started_at,
+            metadata: None,
         })
     }
 }
diff --git a/crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs b/crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs
index a44a4d31a95..17b7fbd6ed6 100644
--- a/crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs
+++ b/crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs
@@ -422,6 +422,7 @@ impl TryFrom<StripebillingInvoiceBody> for revenue_recovery::RevenueRecoveryInvo
             retry_count: Some(item.data.object.attempt_count),
             next_billing_at,
             billing_started_at,
+            metadata: None,
         })
     }
 }
diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs
index 6249053c36b..c8ac34f8613 100644
--- a/crates/hyperswitch_domain_models/src/payments.rs
+++ b/crates/hyperswitch_domain_models/src/payments.rs
@@ -964,7 +964,7 @@ impl<F: Clone> PaymentConfirmData<F> {
 }
 
 #[cfg(feature = "v2")]
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct PaymentStatusData<F>
 where
     F: Clone,
diff --git a/crates/hyperswitch_domain_models/src/revenue_recovery.rs b/crates/hyperswitch_domain_models/src/revenue_recovery.rs
index 094905b1baf..8377fb06cba 100644
--- a/crates/hyperswitch_domain_models/src/revenue_recovery.rs
+++ b/crates/hyperswitch_domain_models/src/revenue_recovery.rs
@@ -1,6 +1,6 @@
 use api_models::{payments as api_payments, webhooks};
 use common_enums::enums as common_enums;
-use common_utils::{id_type, types as util_types};
+use common_utils::{id_type, pii, types as util_types};
 use time::PrimitiveDateTime;
 
 use crate::{
@@ -77,6 +77,8 @@ pub struct RevenueRecoveryInvoiceData {
     pub next_billing_at: Option<PrimitiveDateTime>,
     /// Invoice Starting Time
     pub billing_started_at: Option<PrimitiveDateTime>,
+    /// metadata of the merchant
+    pub metadata: Option<pii::SecretSerdeValue>,
 }
 
 #[derive(Clone, Debug)]
@@ -153,7 +155,7 @@ impl From<&RevenueRecoveryInvoiceData> for api_payments::PaymentsCreateIntentReq
             statement_descriptor: None,
             order_details: None,
             allowed_payment_method_types: None,
-            metadata: None,
+            metadata: data.metadata.clone(),
             connector_metadata: None,
             feature_metadata: None,
             payment_link_enabled: None,
@@ -178,6 +180,7 @@ impl From<&BillingConnectorInvoiceSyncResponse> for RevenueRecoveryInvoiceData {
             retry_count: data.retry_count,
             next_billing_at: data.ends_at,
             billing_started_at: data.created_at,
+            metadata: None,
         }
     }
 }
diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs
index 595b437675e..6c9a1babb7d 100644
--- a/crates/router/src/core/payments/transformers.rs
+++ b/crates/router/src/core/payments/transformers.rs
@@ -2390,6 +2390,7 @@ where
             customer_id: payment_intent.customer_id.clone(),
             connector: Some(connector),
             created: payment_intent.created_at,
+            modified_at: payment_intent.modified_at,
             payment_method_data,
             payment_method_type: Some(payment_attempt.payment_method_type),
             payment_method_subtype: Some(payment_attempt.payment_method_subtype),
@@ -2410,7 +2411,10 @@ where
             is_iframe_redirection_enabled: None,
             merchant_reference_id: payment_intent.merchant_reference_id.clone(),
             raw_connector_response,
-            feature_metadata: None,
+            feature_metadata: payment_intent
+                .feature_metadata
+                .map(|feature_metadata| feature_metadata.convert_back()),
+            metadata: payment_intent.metadata,
         };
 
         Ok(services::ApplicationResponse::JsonWithHeaders((
@@ -2501,6 +2505,7 @@ where
                 .map(From::from),
             shipping: self.payment_address.get_shipping().cloned().map(From::from),
             created: payment_intent.created_at,
+            modified_at: payment_intent.modified_at,
             payment_method_data,
             payment_method_type: Some(payment_attempt.payment_method_type),
             payment_method_subtype: Some(payment_attempt.payment_method_subtype),
@@ -2519,7 +2524,10 @@ where
             is_iframe_redirection_enabled: payment_intent.is_iframe_redirection_enabled,
             merchant_reference_id: payment_intent.merchant_reference_id.clone(),
             raw_connector_response,
-            feature_metadata: None,
+            feature_metadata: payment_intent
+                .feature_metadata
+                .map(|feature_metadata| feature_metadata.convert_back()),
+            metadata: payment_intent.metadata,
         };
 
         Ok(services::ApplicationResponse::JsonWithHeaders((
diff --git a/crates/router/src/core/revenue_recovery.rs b/crates/router/src/core/revenue_recovery.rs
index f714564e1f3..f9662a745dd 100644
--- a/crates/router/src/core/revenue_recovery.rs
+++ b/crates/router/src/core/revenue_recovery.rs
@@ -1,29 +1,45 @@
 pub mod api;
 pub mod transformers;
 pub mod types;
-use api_models::{enums, process_tracker::revenue_recovery, webhooks};
+use std::marker::PhantomData;
+
+use api_models::{
+    enums,
+    payments::{self as api_payments, PaymentsResponse},
+    process_tracker::revenue_recovery,
+    webhooks,
+};
 use common_utils::{
     self,
     errors::CustomResult,
-    ext_traits::{OptionExt, ValueExt},
+    ext_traits::{AsyncExt, OptionExt, ValueExt},
     id_type,
 };
 use diesel_models::{enums as diesel_enum, process_tracker::business_status};
 use error_stack::{self, ResultExt};
 use hyperswitch_domain_models::{
-    payments::PaymentIntent, revenue_recovery as domain_revenue_recovery,
-    ApiModelToDieselModelConvertor,
+    merchant_context,
+    payments::{PaymentIntent, PaymentStatusData},
+    revenue_recovery as domain_revenue_recovery, ApiModelToDieselModelConvertor,
 };
 use scheduler::errors as sch_errors;
 
 use crate::{
-    core::errors::{self, RouterResponse, RouterResult, StorageErrorExt},
+    core::{
+        errors::{self, RouterResponse, RouterResult, StorageErrorExt},
+        payments::{
+            self,
+            operations::{GetTrackerResponse, Operation},
+            transformers::GenerateResponse,
+        },
+        revenue_recovery::types::RevenueRecoveryOutgoingWebhook,
+    },
     db::StorageInterface,
     logger,
     routes::{app::ReqState, metrics, SessionState},
     services::ApplicationResponse,
     types::{
-        domain,
+        api as router_api_types, domain,
         storage::{self, revenue_recovery as pcr},
         transformers::{ForeignFrom, ForeignInto},
     },
@@ -220,7 +236,7 @@ pub async fn perform_execute_payment(
             .await;
 
             match record_attempt {
-                Ok(_) => {
+                Ok(record_attempt_response) => {
                     let action = Box::pin(types::Action::execute_payment(
                         state,
                         revenue_recovery_payment_data.merchant_account.get_id(),
@@ -230,6 +246,7 @@ pub async fn perform_execute_payment(
                         merchant_context,
                         revenue_recovery_payment_data,
                         &revenue_recovery_metadata,
+                        &record_attempt_response.id,
                     ))
                     .await?;
                     Box::pin(action.execute_payment_task_response_handler(
@@ -414,10 +431,12 @@ pub async fn perform_payments_sync(
         state,
         &tracking_data.global_payment_id,
         revenue_recovery_payment_data,
+        true,
+        true,
     )
     .await?;
 
-    let payment_attempt = psync_data.payment_attempt;
+    let payment_attempt = psync_data.payment_attempt.clone();
     let mut revenue_recovery_metadata = payment_intent
         .feature_metadata
         .as_ref()
@@ -426,6 +445,12 @@ pub async fn perform_payments_sync(
         .convert_back();
     let pcr_status: types::RevenueRecoveryPaymentsAttemptStatus =
         payment_attempt.status.foreign_into();
+
+    let new_revenue_recovery_payment_data = &pcr::RevenueRecoveryPaymentData {
+        psync_data: Some(psync_data),
+        ..revenue_recovery_payment_data.clone()
+    };
+
     Box::pin(
         pcr_status.update_pt_status_based_on_attempt_status_for_payments_sync(
             state,
@@ -433,7 +458,7 @@ pub async fn perform_payments_sync(
             process.clone(),
             profile,
             merchant_context,
-            revenue_recovery_payment_data,
+            new_revenue_recovery_payment_data,
             payment_attempt,
             &mut revenue_recovery_metadata,
         ),
@@ -457,6 +482,8 @@ pub async fn perform_calculate_workflow(
     let profile_id = revenue_recovery_payment_data.profile.get_id();
     let billing_mca_id = revenue_recovery_payment_data.billing_mca.get_id();
 
+    let mut event_type: Option<common_enums::EventType> = None;
+
     logger::info!(
         process_id = %process.id,
         payment_id = %tracking_data.global_payment_id.get_string_repr(),
@@ -488,6 +515,20 @@ pub async fn perform_calculate_workflow(
         }
     };
 
+    // External Payments which enter the calculate workflow for the first time will have active attempt id as None
+    // Then we dont need to send an webhook to the merchant as its not a failure from our side.
+    // Thus we dont need to a payment get call for such payments.
+    let active_payment_attempt_id = payment_intent.active_attempt_id.as_ref();
+
+    let payments_response = get_payment_response_using_payment_get_operation(
+        state,
+        &tracking_data.global_payment_id,
+        revenue_recovery_payment_data,
+        &merchant_context_from_revenue_recovery_payment_data,
+        active_payment_attempt_id,
+    )
+    .await?;
+
     // 2. Get best available token
     let best_time_to_schedule = match workflows::revenue_recovery::get_token_with_schedule_time_based_on_retry_algorithm_type(
         state,
@@ -517,6 +558,14 @@ pub async fn perform_calculate_workflow(
                 "Found best available token, creating EXECUTE_WORKFLOW task"
             );
 
+            // reset active attmept id and payment connector transmission before going to execute workflow
+            let  _ = Box::pin(reset_connector_transmission_and_active_attempt_id_before_pushing_to_execute_workflow(
+                state,
+                payment_intent,
+                revenue_recovery_payment_data,
+                active_payment_attempt_id
+            )).await?;
+
             // 3. If token found: create EXECUTE_WORKFLOW task and finish CALCULATE_WORKFLOW
             insert_execute_pcr_task_to_pt(
                 &tracking_data.billing_mca_id,
@@ -649,6 +698,8 @@ pub async fn perform_calculate_workflow(
                                     sch_errors::ProcessTrackerError::ProcessUpdateFailed
                                 })?;
 
+                            event_type = Some(common_enums::EventType::PaymentFailed);
+
                             logger::info!(
                                 process_id = %process.id,
                                 connector_customer_id = %connector_customer_id,
@@ -660,6 +711,34 @@ pub async fn perform_calculate_workflow(
             }
         }
     }
+
+    let _outgoing_webhook = event_type.and_then(|event_kind| {
+        payments_response.map(|resp| Some((event_kind, resp)))
+    })
+    .flatten()
+    .async_map(|(event_kind, response)| async move {
+        let _ = RevenueRecoveryOutgoingWebhook::send_outgoing_webhook_based_on_revenue_recovery_status(
+            state,
+            common_enums::EventClass::Payments,
+            event_kind,
+            payment_intent,
+            &merchant_context,
+            profile,
+            tracking_data.payment_attempt_id.get_string_repr().to_string(),
+            response
+        )
+        .await
+        .map_err(|e| {
+            logger::error!(
+                error = ?e,
+                "Failed to send outgoing webhook"
+            );
+            e
+        })
+        .ok();
+    }
+    ).await;
+
     Ok(())
 }
 
@@ -937,3 +1016,88 @@ pub async fn retrieve_revenue_recovery_process_tracker(
     };
     Ok(ApplicationResponse::Json(response))
 }
+
+pub async fn get_payment_response_using_payment_get_operation(
+    state: &SessionState,
+    payment_intent_id: &id_type::GlobalPaymentId,
+    revenue_recovery_payment_data: &pcr::RevenueRecoveryPaymentData,
+    merchant_context: &domain::MerchantContext,
+    active_payment_attempt_id: Option<&id_type::GlobalAttemptId>,
+) -> Result<Option<ApplicationResponse<PaymentsResponse>>, sch_errors::ProcessTrackerError> {
+    match active_payment_attempt_id {
+        Some(_) => {
+            let payment_response = api::call_psync_api(
+                state,
+                payment_intent_id,
+                revenue_recovery_payment_data,
+                false,
+                false,
+            )
+            .await?;
+            let payments_response = payment_response.generate_response(
+                state,
+                None,
+                None,
+                None,
+                merchant_context,
+                &revenue_recovery_payment_data.profile,
+                None,
+            )?;
+
+            Ok(Some(payments_response))
+        }
+        None => Ok(None),
+    }
+}
+
+// This function can be implemented to reset the connector transmission and active attempt ID
+// before pushing to the execute workflow.
+pub async fn reset_connector_transmission_and_active_attempt_id_before_pushing_to_execute_workflow(
+    state: &SessionState,
+    payment_intent: &PaymentIntent,
+    revenue_recovery_payment_data: &pcr::RevenueRecoveryPaymentData,
+    active_payment_attempt_id: Option<&id_type::GlobalAttemptId>,
+) -> Result<Option<()>, sch_errors::ProcessTrackerError> {
+    let mut revenue_recovery_metadata = payment_intent
+        .feature_metadata
+        .as_ref()
+        .and_then(|feature_metadata| feature_metadata.payment_revenue_recovery_metadata.clone())
+        .get_required_value("Payment Revenue Recovery Metadata")?
+        .convert_back();
+    match active_payment_attempt_id {
+        Some(_) => {
+            // update the connector payment transmission field to Unsuccessful and unset active attempt id
+            revenue_recovery_metadata.set_payment_transmission_field_for_api_request(
+                enums::PaymentConnectorTransmission::ConnectorCallUnsuccessful,
+            );
+
+            let payment_update_req =
+        api_payments::PaymentsUpdateIntentRequest::update_feature_metadata_and_active_attempt_with_api(
+            payment_intent
+                .feature_metadata
+                .clone()
+                .unwrap_or_default()
+                .convert_back()
+                .set_payment_revenue_recovery_metadata_using_api(
+                    revenue_recovery_metadata.clone(),
+                ),
+            enums::UpdateActiveAttempt::Unset,
+        );
+            logger::info!(
+                "Call made to payments update intent api , with the request body {:?}",
+                payment_update_req
+            );
+            api::update_payment_intent_api(
+                state,
+                payment_intent.id.clone(),
+                revenue_recovery_payment_data,
+                payment_update_req,
+            )
+            .await
+            .change_context(errors::RecoveryError::PaymentCallFailed)?;
+
+            Ok(Some(()))
+        }
+        None => Ok(None),
+    }
+}
diff --git a/crates/router/src/core/revenue_recovery/api.rs b/crates/router/src/core/revenue_recovery/api.rs
index f7172e2369f..fe919979086 100644
--- a/crates/router/src/core/revenue_recovery/api.rs
+++ b/crates/router/src/core/revenue_recovery/api.rs
@@ -32,12 +32,14 @@ pub async fn call_psync_api(
     state: &SessionState,
     global_payment_id: &id_type::GlobalPaymentId,
     revenue_recovery_data: &revenue_recovery_types::RevenueRecoveryPaymentData,
+    force_sync_bool: bool,
+    expand_attempts_bool: bool,
 ) -> RouterResult<payments_domain::PaymentStatusData<api_types::PSync>> {
     let operation = payments::operations::PaymentGet;
     let req = payments_api::PaymentsRetrieveRequest {
-        force_sync: true,
+        force_sync: force_sync_bool,
         param: None,
-        expand_attempts: true,
+        expand_attempts: expand_attempts_bool,
         return_raw_connector_response: None,
         merchant_connector_details: None,
     };
diff --git a/crates/router/src/core/revenue_recovery/transformers.rs b/crates/router/src/core/revenue_recovery/transformers.rs
index 9835999627f..df6e430a112 100644
--- a/crates/router/src/core/revenue_recovery/transformers.rs
+++ b/crates/router/src/core/revenue_recovery/transformers.rs
@@ -56,6 +56,7 @@ impl ForeignFrom<api_models::payments::RecoveryPaymentsCreate>
             retry_count: None,
             next_billing_at: None,
             billing_started_at: data.billing_started_at,
+            metadata: data.metadata,
         }
     }
 }
diff --git a/crates/router/src/core/revenue_recovery/types.rs b/crates/router/src/core/revenue_recovery/types.rs
index 40532480d38..0b7fb4ad062 100644
--- a/crates/router/src/core/revenue_recovery/types.rs
+++ b/crates/router/src/core/revenue_recovery/types.rs
@@ -17,11 +17,12 @@ use diesel_models::{
 };
 use error_stack::{self, ResultExt};
 use hyperswitch_domain_models::{
+    api::ApplicationResponse,
     business_profile, merchant_connector_account,
     merchant_context::{Context, MerchantContext},
     payments::{
         self as domain_payments, payment_attempt::PaymentAttempt, PaymentConfirmData,
-        PaymentIntent, PaymentIntentData,
+        PaymentIntent, PaymentIntentData, PaymentStatusData,
     },
     router_data_v2::{self, flow_common_types},
     router_flow_types,
@@ -35,9 +36,11 @@ use super::errors::StorageErrorExt;
 use crate::{
     core::{
         errors::{self, RouterResult},
-        payments::{self, helpers, operations::Operation},
+        payments::{self, helpers, operations::Operation, transformers::GenerateResponse},
         revenue_recovery::{self as revenue_recovery_core, pcr, perform_calculate_workflow},
-        webhooks::recovery_incoming as recovery_incoming_flow,
+        webhooks::{
+            create_event_and_trigger_outgoing_webhook, recovery_incoming as recovery_incoming_flow,
+        },
     },
     db::StorageInterface,
     logger,
@@ -137,6 +140,12 @@ impl RevenueRecoveryPaymentsAttemptStatus {
 
         let retry_count = process_tracker.retry_count;
 
+        let psync_response = revenue_recovery_payment_data
+            .psync_data
+            .as_ref()
+            .ok_or(errors::RecoveryError::ValueNotFound)
+            .attach_printable("Psync data not found in revenue recovery payment data")?;
+
         match self {
             Self::Succeeded => {
                 // finish psync task as the payment was a success
@@ -146,6 +155,9 @@ impl RevenueRecoveryPaymentsAttemptStatus {
                         business_status::PSYNC_WORKFLOW_COMPLETE,
                     )
                     .await?;
+
+                let event_status = common_enums::EventType::PaymentSucceeded;
+
                 // publish events to kafka
                 if let Err(e) = recovery_incoming_flow::RecoveryPaymentTuple::publish_revenue_recovery_event_to_kafka(
                     state,
@@ -177,6 +189,27 @@ impl RevenueRecoveryPaymentsAttemptStatus {
                 )
                 .await;
 
+                let payments_response = psync_response
+                    .clone()
+                    .generate_response(state, None, None, None, &merchant_context, profile, None)
+                    .change_context(errors::RecoveryError::PaymentsResponseGenerationFailed)
+                    .attach_printable("Failed while generating response for payment")?;
+
+                RevenueRecoveryOutgoingWebhook::send_outgoing_webhook_based_on_revenue_recovery_status(
+                    state,
+                    common_enums::EventClass::Payments,
+                    event_status,
+                    payment_intent,
+                    &merchant_context,
+                    profile,
+                    recovery_payment_attempt
+                        .attempt_id
+                        .get_string_repr()
+                        .to_string(),
+                    payments_response
+                )
+                .await?;
+
                 // Record a successful transaction back to Billing Connector
                 // TODO: Add support for retrying failed outgoing recordback webhooks
                 record_back_to_billing_connector(
@@ -233,14 +266,15 @@ impl RevenueRecoveryPaymentsAttemptStatus {
                 .await;
 
                 // Reopen calculate workflow on payment failure
-                reopen_calculate_workflow_on_payment_failure(
+                Box::pin(reopen_calculate_workflow_on_payment_failure(
                     state,
                     &process_tracker,
                     profile,
                     merchant_context,
                     payment_intent,
                     revenue_recovery_payment_data,
-                )
+                    psync_response.payment_attempt.get_id(),
+                ))
                 .await?;
             }
             Self::Processing => {
@@ -308,6 +342,8 @@ impl Decision {
                     state,
                     payment_id,
                     revenue_recovery_data,
+                    true,
+                    true,
                 )
                 .await
                 .change_context(errors::RecoveryError::PaymentCallFailed)
@@ -324,6 +360,8 @@ impl Decision {
                     state,
                     payment_id,
                     revenue_recovery_data,
+                    true,
+                    true,
                 )
                 .await
                 .change_context(errors::RecoveryError::PaymentCallFailed)
@@ -368,6 +406,7 @@ impl Action {
         merchant_context: domain::MerchantContext,
         revenue_recovery_payment_data: &storage::revenue_recovery::RevenueRecoveryPaymentData,
         revenue_recovery_metadata: &PaymentRevenueRecoveryMetadata,
+        latest_attempt_id: &id_type::GlobalAttemptId,
     ) -> RecoveryResult<Self> {
         let connector_customer_id = payment_intent
             .extract_connector_customer_id_from_payment_intent()
@@ -426,7 +465,6 @@ impl Action {
                     hyperswitch_domain_models::revenue_recovery::RecoveryPaymentIntent::from(
                         payment_intent,
                     );
-
                 // handle proxy api's response
                 match response {
                     Ok(payment_data) => match payment_data.payment_attempt.status.foreign_into() {
@@ -444,16 +482,16 @@ impl Action {
 
                             // publish events to kafka
                             if let Err(e) = recovery_incoming_flow::RecoveryPaymentTuple::publish_revenue_recovery_event_to_kafka(
-                            state,
-                            &recovery_payment_tuple,
-                            Some(process.retry_count+1)
-                        )
-                        .await{
-                            router_env::logger::error!(
-                                "Failed to publish revenue recovery event to kafka: {:?}",
-                                e
-                            );
-                        };
+                                state,
+                                &recovery_payment_tuple,
+                                Some(process.retry_count+1)
+                            )
+                            .await{
+                                router_env::logger::error!(
+                                    "Failed to publish revenue recovery event to kafka: {:?}",
+                                    e
+                                );
+                            };
 
                             let is_hard_decline = revenue_recovery::check_hard_decline(
                                 state,
@@ -474,10 +512,40 @@ impl Action {
 
                             // unlocking the token
                             let _unlock_the_connector_customer_id = storage::revenue_recovery_redis_operation::RedisTokenManager::unlock_connector_customer_status(
-                    state,
-                    &connector_customer_id,
-                )
-                .await;
+                                state,
+                                &connector_customer_id,
+                            )
+                            .await;
+
+                            let event_status = common_enums::EventType::PaymentSucceeded;
+
+                            let payments_response = payment_data
+                                .clone()
+                                .generate_response(
+                                    state,
+                                    None,
+                                    None,
+                                    None,
+                                    &merchant_context,
+                                    profile,
+                                    None,
+                                )
+                                .change_context(
+                                    errors::RecoveryError::PaymentsResponseGenerationFailed,
+                                )
+                                .attach_printable("Failed while generating response for payment")?;
+
+                            RevenueRecoveryOutgoingWebhook::send_outgoing_webhook_based_on_revenue_recovery_status(
+                                state,
+                                common_enums::EventClass::Payments,
+                                event_status,
+                                payment_intent,
+                                &merchant_context,
+                                profile,
+                                payment_data.payment_attempt.id.get_string_repr().to_string(),
+                                payments_response
+                            )
+                            .await?;
 
                             Ok(Self::SuccessfulPayment(
                                 payment_data.payment_attempt.clone(),
@@ -541,14 +609,15 @@ impl Action {
                             .await;
 
                             // Reopen calculate workflow on payment failure
-                            reopen_calculate_workflow_on_payment_failure(
+                            Box::pin(reopen_calculate_workflow_on_payment_failure(
                                 state,
                                 process,
                                 profile,
                                 merchant_context,
                                 payment_intent,
                                 revenue_recovery_payment_data,
-                            )
+                                latest_attempt_id,
+                            ))
                             .await?;
 
                             // Return terminal failure to finish the current execute workflow
@@ -576,6 +645,8 @@ impl Action {
                     state,
                     payment_intent.get_id(),
                     revenue_recovery_payment_data,
+                    true,
+                    true,
                 )
                 .await;
 
@@ -690,36 +761,6 @@ impl Action {
                 Ok(())
             }
             Self::TerminalFailure(payment_attempt) => {
-                // update the connector payment transmission field to Unsuccessful and unset active attempt id
-                revenue_recovery_metadata.set_payment_transmission_field_for_api_request(
-                    enums::PaymentConnectorTransmission::ConnectorCallUnsuccessful,
-                );
-
-                let payment_update_req =
-                PaymentsUpdateIntentRequest::update_feature_metadata_and_active_attempt_with_api(
-                    payment_intent
-                        .feature_metadata
-                        .clone()
-                        .unwrap_or_default()
-                        .convert_back()
-                        .set_payment_revenue_recovery_metadata_using_api(
-                            revenue_recovery_metadata.clone(),
-                        ),
-                    api_enums::UpdateActiveAttempt::Unset,
-                );
-                logger::info!(
-                    "Call made to payments update intent api , with the request body {:?}",
-                    payment_update_req
-                );
-                revenue_recovery_core::api::update_payment_intent_api(
-                    state,
-                    payment_intent.id.clone(),
-                    revenue_recovery_payment_data,
-                    payment_update_req,
-                )
-                .await
-                .change_context(errors::RecoveryError::PaymentCallFailed)?;
-
                 db.as_scheduler()
                     .finish_process_with_business_status(
                         execute_task_process.clone(),
@@ -794,6 +835,8 @@ impl Action {
             state,
             payment_intent.get_id(),
             revenue_recovery_payment_data,
+            true,
+            true,
         )
         .await;
         let used_token = get_payment_processor_token_id_from_payment_attempt(&payment_attempt);
@@ -856,14 +899,15 @@ impl Action {
                     .await;
 
                     // Reopen calculate workflow on payment failure
-                    reopen_calculate_workflow_on_payment_failure(
+                    Box::pin(reopen_calculate_workflow_on_payment_failure(
                         state,
                         process,
                         profile,
                         merchant_context,
                         payment_intent,
                         revenue_recovery_payment_data,
-                    )
+                        payment_attempt.get_id(),
+                    ))
                     .await?;
 
                     Ok(Self::TerminalFailure(payment_attempt.clone()))
@@ -937,37 +981,6 @@ impl Action {
                     .change_context(errors::RecoveryError::ProcessTrackerFailure)
                     .attach_printable("Failed to update the process tracker")?;
 
-                // update the connector payment transmission field to Unsuccessful and unset active attempt id
-                revenue_recovery_metadata.set_payment_transmission_field_for_api_request(
-                    enums::PaymentConnectorTransmission::ConnectorCallUnsuccessful,
-                );
-
-                let payment_update_req =
-                PaymentsUpdateIntentRequest::update_feature_metadata_and_active_attempt_with_api(
-                    payment_intent
-                        .feature_metadata
-                        .clone()
-                        .unwrap_or_default()
-                        .convert_back()
-                        .set_payment_revenue_recovery_metadata_using_api(
-                            revenue_recovery_metadata.clone(),
-                        ),
-                    api_enums::UpdateActiveAttempt::Unset,
-                );
-                logger::info!(
-                    "Call made to payments update intent api , with the request body {:?}",
-                    payment_update_req
-                );
-
-                revenue_recovery_core::api::update_payment_intent_api(
-                    state,
-                    payment_intent.id.clone(),
-                    revenue_recovery_payment_data,
-                    payment_update_req,
-                )
-                .await
-                .change_context(errors::RecoveryError::PaymentCallFailed)?;
-
                 // fetch the execute task
                 let task = revenue_recovery_core::EXECUTE_WORKFLOW;
                 let runner = storage::ProcessTrackerRunner::PassiveRecoveryWorkflow;
@@ -990,37 +1003,6 @@ impl Action {
             }
 
             Self::TerminalFailure(payment_attempt) => {
-                // update the connector payment transmission field to Unsuccessful and unset active attempt id
-                revenue_recovery_metadata.set_payment_transmission_field_for_api_request(
-                    enums::PaymentConnectorTransmission::ConnectorCallUnsuccessful,
-                );
-
-                let payment_update_req =
-                PaymentsUpdateIntentRequest::update_feature_metadata_and_active_attempt_with_api(
-                    payment_intent
-                        .feature_metadata
-                        .clone()
-                        .unwrap_or_default()
-                        .convert_back()
-                        .set_payment_revenue_recovery_metadata_using_api(
-                            revenue_recovery_metadata.clone(),
-                        ),
-                    api_enums::UpdateActiveAttempt::Unset,
-                );
-                logger::info!(
-                    "Call made to payments update intent api , with the request body {:?}",
-                    payment_update_req
-                );
-
-                revenue_recovery_core::api::update_payment_intent_api(
-                    state,
-                    payment_intent.id.clone(),
-                    revenue_recovery_payment_data,
-                    payment_update_req,
-                )
-                .await
-                .change_context(errors::RecoveryError::PaymentCallFailed)?;
-
                 // TODO: Add support for retrying failed outgoing recordback webhooks
                 // finish the current psync task
                 db.as_scheduler()
@@ -1150,12 +1132,27 @@ pub async fn reopen_calculate_workflow_on_payment_failure(
     merchant_context: domain::MerchantContext,
     payment_intent: &PaymentIntent,
     revenue_recovery_payment_data: &storage::revenue_recovery::RevenueRecoveryPaymentData,
+    latest_attempt_id: &id_type::GlobalAttemptId,
 ) -> RecoveryResult<()> {
     let db = &*state.store;
     let id = payment_intent.id.clone();
     let task = revenue_recovery_core::CALCULATE_WORKFLOW;
     let runner = storage::ProcessTrackerRunner::PassiveRecoveryWorkflow;
 
+    let old_tracking_data: pcr::RevenueRecoveryWorkflowTrackingData =
+        serde_json::from_value(process.tracking_data.clone())
+            .change_context(errors::RecoveryError::ValueNotFound)
+            .attach_printable("Failed to deserialize the tracking data from process tracker")?;
+
+    let new_tracking_data = pcr::RevenueRecoveryWorkflowTrackingData {
+        payment_attempt_id: latest_attempt_id.clone(),
+        ..old_tracking_data
+    };
+
+    let tracking_data = serde_json::to_value(new_tracking_data)
+        .change_context(errors::RecoveryError::ValueNotFound)
+        .attach_printable("Failed to serialize the tracking data for process tracker")?;
+
     // Construct the process tracker ID for CALCULATE_WORKFLOW
     let process_tracker_id = format!("{}_{}_{}", runner, task, id.get_string_repr());
 
@@ -1201,7 +1198,7 @@ pub async fn reopen_calculate_workflow_on_payment_failure(
                 name: Some(task.to_string()),
                 retry_count: Some(new_retry_count),
                 schedule_time: Some(new_schedule_time),
-                tracking_data: Some(process.clone().tracking_data),
+                tracking_data: Some(tracking_data),
                 business_status: Some(String::from(business_status::PENDING)),
                 status: Some(common_enums::ProcessTrackerStatus::Pending),
                 updated_at: Some(common_utils::date_time::now()),
@@ -1301,7 +1298,7 @@ pub async fn reopen_calculate_workflow_on_payment_failure(
                 .attach_printable("Failed to deserialize the tracking data from process tracker")?;
 
             // Call the existing perform_calculate_workflow function
-            perform_calculate_workflow(
+            Box::pin(perform_calculate_workflow(
                 state,
                 process,
                 profile,
@@ -1309,7 +1306,7 @@ pub async fn reopen_calculate_workflow_on_payment_failure(
                 &tracking_data,
                 revenue_recovery_payment_data,
                 payment_intent,
-            )
+            ))
             .await
             .change_context(errors::RecoveryError::ProcessTrackerFailure)
             .attach_printable("Failed to perform calculate workflow")?;
@@ -1476,3 +1473,50 @@ pub fn get_payment_processor_token_id_from_payment_attempt(
 
     used_token
 }
+
+pub struct RevenueRecoveryOutgoingWebhook;
+
+impl RevenueRecoveryOutgoingWebhook {
+    #[allow(clippy::too_many_arguments)]
+    pub async fn send_outgoing_webhook_based_on_revenue_recovery_status(
+        state: &SessionState,
+        event_class: common_enums::EventClass,
+        event_status: common_enums::EventType,
+        payment_intent: &PaymentIntent,
+        merchant_context: &domain::MerchantContext,
+        profile: &domain::Profile,
+        payment_attempt_id: String,
+        payments_response: ApplicationResponse<api_models::payments::PaymentsResponse>,
+    ) -> RecoveryResult<()> {
+        match payments_response {
+            ApplicationResponse::JsonWithHeaders((response, _headers)) => {
+                let outgoing_webhook_content =
+                    api_models::webhooks::OutgoingWebhookContent::PaymentDetails(Box::new(
+                        response,
+                    ));
+                create_event_and_trigger_outgoing_webhook(
+                    state.clone(),
+                    profile.clone(),
+                    merchant_context.get_merchant_key_store(),
+                    event_status,
+                    event_class,
+                    payment_attempt_id,
+                    enums::EventObjectType::PaymentDetails,
+                    outgoing_webhook_content,
+                    payment_intent.created_at,
+                )
+                .await
+                .change_context(errors::RecoveryError::InvalidTask)
+                .attach_printable("Failed to send out going webhook")?;
+
+                Ok(())
+            }
+
+            _other_variant => {
+                // Handle other successful response types if needed
+                logger::warn!("Unexpected application response variant for outgoing webhook");
+                Err(errors::RecoveryError::RevenueRecoveryOutgoingWebhookFailed.into())
+            }
+        }
+    }
+}
diff --git a/crates/router/src/types/storage/revenue_recovery.rs b/crates/router/src/types/storage/revenue_recovery.rs
index 48110eb92ed..4cde3bd99f8 100644
--- a/crates/router/src/types/storage/revenue_recovery.rs
+++ b/crates/router/src/types/storage/revenue_recovery.rs
@@ -7,7 +7,7 @@ use external_services::grpc_client::{self as external_grpc_client, GrpcHeaders};
 use hyperswitch_domain_models::{
     business_profile, merchant_account, merchant_connector_account, merchant_key_store,
     payment_method_data::{Card, PaymentMethodData},
-    payments::{payment_attempt::PaymentAttempt, PaymentIntent},
+    payments::{payment_attempt::PaymentAttempt, PaymentIntent, PaymentStatusData},
 };
 use masking::PeekInterface;
 use router_env::logger;
@@ -32,6 +32,7 @@ pub struct RevenueRecoveryPaymentData {
     pub key_store: merchant_key_store::MerchantKeyStore,
     pub billing_mca: merchant_connector_account::MerchantConnectorAccount,
     pub retry_algorithm: enums::RevenueRecoveryAlgorithmType,
+    pub psync_data: Option<PaymentStatusData<types::api::PSync>>,
 }
 impl RevenueRecoveryPaymentData {
     pub async fn get_schedule_time_based_on_retry_type(
diff --git a/crates/router/src/workflows/revenue_recovery.rs b/crates/router/src/workflows/revenue_recovery.rs
index 6a6d9511062..e87b26e4c15 100644
--- a/crates/router/src/workflows/revenue_recovery.rs
+++ b/crates/router/src/workflows/revenue_recovery.rs
@@ -232,6 +232,7 @@ pub(crate) async fn extract_data_and_perform_action(
         retry_algorithm: profile
             .revenue_recovery_retry_algorithm_type
             .unwrap_or(tracking_data.revenue_recovery_retry),
+        psync_data: None,
     };
     Ok(pcr_payment_data)
 }
diff --git a/crates/storage_impl/src/errors.rs b/crates/storage_impl/src/errors.rs
index a2b4dad779b..3aade73383c 100644
--- a/crates/storage_impl/src/errors.rs
+++ b/crates/storage_impl/src/errors.rs
@@ -288,6 +288,10 @@ pub enum RecoveryError {
     RecordBackToBillingConnectorFailed,
     #[error("Failed to fetch billing connector account id")]
     BillingMerchantConnectorAccountIdNotFound,
+    #[error("Failed to generate payment sync data")]
+    PaymentsResponseGenerationFailed,
+    #[error("Outgoing Webhook Failed")]
+    RevenueRecoveryOutgoingWebhookFailed,
 }
 #[derive(Debug, Clone, thiserror::Error)]
 pub enum HealthCheckDecisionEngineError {

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

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

## Description
This PR enables outgoing webhook support for revenue recovery. 

When the retries get succeeded that are done using the revenue recovery system, an outgoing webhook needs to be triggered to the url in business profile. This pr adds that support.



### Additional Changes

- [x] 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.
PaymentResponse -> added modified at in api_models


Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context



## How did you test it?
1. create a billing profile with webhook.site url to monitor the webhooks.
2. Create an payment mca using this
```
curl --location 'http://localhost:8080/v2/connector-accounts' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'x-merchant-id:  {{merchant-id}}'
--header 'x-profile-id:  {{profile-id}}' \
--header 'Authorization: {{api-key}}'
--header 'api-key:{{api-key}}' \
--data '{
    "connector_type": "payment_processor",
    "connector_name": "stripe",
    "connector_account_details": {
        "auth_type": "HeaderKey",
        "api_key": "{{api_key}}"
    },
    "payment_methods_enabled": [
        {
            "payment_method_type": "card",
            "payment_method_subtypes": [
                {
                    "payment_method_subtype": "credit",
                    "payment_experience": null,
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "accepted_currencies": null,
                    "accepted_countries": null,
                    "minimum_amount": -1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                {
                    "payment_method_subtype": "debit",
                    "payment_experience": null,
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "accepted_currencies": null,
                    "accepted_countries": null,
                    "minimum_amount": -1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                 {
                    "payment_method_subtype": "card",
                    "payment_experience": null,
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "accepted_currencies": null,
                    "accepted_countries": null,
                    "minimum_amount": -1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        }
    ],
    "frm_configs": null,
    "connector_webhook_details": {
        "merchant_secret": ""
    },
    "metadata": {
        "report_group": "Hello",
        "merchant_config_currency": "USD"
    },
    "profile_id": "{{profile_id}}"
}'
```
3. create an billing mca
```

curl --location 'http://localhost:8080/v2/connector-accounts' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'x-merchant-id:  {{merchant-id}}'
--header 'x-profile-id:  {{profile-id}}' \
--header 'Authorization: {{api-key}}'
--header 'api-key:{{api-key}}' \
--data '{
    "connector_type": "billing_processor",
    "connector_name": "custombilling",
    "connector_account_details": {
        "auth_type": "NoKey"
    },
    "feature_metadata" : {
        "revenue_recovery" : {
            "max_retry_count" : 9,
            "billing_connector_retry_threshold" : 0,
            "billing_account_reference" :{
                "{{payment_mca}}" : "{{payment_mca}}"
            },
            "switch_payment_method_config" : {
                "retry_threshold" : 0,
                "time_threshold_after_creation": 0
            }
        }
    },
    "profile_id": "{{profile_id}}"
}'
```
4. switch revenue recovery config to cascading
```
curl --location --request PUT 'http://localhost:8080/v2/profiles/pro_DD6ZRORqBtEwR3ITjseM' \
--header 'x-merchant-id: {{erchant-id}}' \
--header 'Authorization:  {{api-key}}' \
--header 'Content-Type: application/json' \
--header 'api-key:  {{api-key}}' \
--data '{
    "revenue_recovery_retry_algorithm_type": "cascading"
}   '
```
5.   Go to stripe dashboard and create a customer and attach a card for that customer
6. Hit this recovery api with that customer_id and payment_method_id
```
curl --location 'http://localhost:8080/v2/payments/recovery' \
--header 'Authorization: {{api-key}}'
--header 'x-profile-id: {{profile-id}}
--header 'x-merchant-id: {{merchant-id}}'
--header 'Content-Type: application/json' \
--header 'api-key:{{api_key}}' \
--data '{
    "amount_details": {
      "order_amount": 2250,
      "currency": "USD"
    },
    "merchant_reference_id": "test_ffhgfewf3476f5",
    "connector_transaction_id": "1831984",
    "connector_customer_id": "{{customer_id_from_stripe}}",
    "error": {
      "code": "110",
      "message": "Insufficient Funds"
    },
    "billing": {
      "address": {
        "state": "CA",
        "country": "US"
      }
    },
    "payment_method_type": "card",
    "payment_method_sub_type": "credit",
    "payment_method_data": {
      "primary_processor_payment_method_token": "{{payment_method_id_from_stripe}}",
      "additional_payment_method_info": {
        "card_exp_month": "12",
        "card_exp_year": "25",
        "last_four_digits": 1997,
        "card_network": "Visa",
        "card_issuer": "Wells Fargo NA",
        "card_type": "credit"
      }
    },
    "billing_started_at": "2024-05-29T08:10:58Z",
    "transaction_created_at": "2024-05-29T08:10:58Z",
    "attempt_status": "failure",
    "action": "schedule_failed_payment",
    "billing_merchant_connector_id": "{{billing_mca}}
    "payment_merchant_connector_id": "{{payment_mca}}"
  }'
```
7. There will be a process tracker entry now and wait till it get triggered. Based on the test card if we mentioned a successful card's payment method id then we will get a webhook to webhook.site url. 
Sample screen shot and sample webhook body are mentioned below
<img width="1727" height="951" alt="Screenshot 2025-09-08 at 1 55 58 AM" src="https://github.com/user-attachments/assets/77c67c1b-ba07-4d48-8109-be0de40837d6" />
 Sample body of the webhook

```
{
  "merchant_id": "cloth_seller_I7nS4iwZnxRDJtGfiNTy",
  "event_id": "evt_019925c838c877c282d3eaf5b86e9036",
  "event_type": "payment_succeeded",
  "content": {
    "type": "payment_details",
    "object": {
      "id": "12345_pay_019925c5959a79418301a9460b92edb1",
      "status": "succeeded",
      "amount": {
        "order_amount": 2250,
        "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": 2250,
        "amount_to_capture": null,
        "amount_capturable": 0,
        "amount_captured": 2250
      },
      "customer_id": null,
      "connector": "stripe",
      "created": "2025-09-07T20:02:09.947Z",
      "modified_at": "2025-09-07T20:05:02.777Z",
      "payment_method_data": {
        "billing": null
      },
      "payment_method_type": "card",
      "payment_method_subtype": "credit",
      "connector_transaction_id": "pi_3S4opd2KXBHSNjod0tD34Dmw",
      "connector_reference_id": "pi_3S4opd2KXBHSNjod0tD34Dmw",
      "merchant_connector_id": "mca_MbQwWzi4tFItmgYAmshC",
      "browser_info": null,
      "error": null,
      "shipping": null,
      "billing": null,
      "attempts": null,
      "connector_token_details": {
        "token": "pm_1S4olu2KXBHSNjodBbiedqY9",
        "connector_token_request_reference_id": null
      },
      "payment_method_id": null,
      "next_action": null,
      "return_url": "https://google.com/success",
      "authentication_type": "no_three_ds",
      "authentication_type_applied": "no_three_ds",
      "is_iframe_redirection_enabled": null,
      "merchant_reference_id": "test_ffhgfewf3476f5",
      "raw_connector_response": null,
      "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "revenue_recovery": {
          "total_retry_count": 2,
          "payment_connector_transmission": "ConnectorCallSucceeded",
          "billing_connector_id": "mca_ppEVMjRWgTiGyCiwFtg9",
          "active_attempt_payment_connector_id": "mca_MbQwWzi4tFItmgYAmshC",
          "billing_connector_payment_details": {
            "payment_processor_token": "pm_1S4olu2KXBHSNjodBbiedqY9",
            "connector_customer_id": "cus_T0qSE723C5Xxic"
          },
          "payment_method_type": "card",
          "payment_method_subtype": "credit",
          "connector": "stripe",
          "billing_connector_payment_method_details": {
            "type": "card",
            "value": {
              "card_network": "Visa",
              "card_issuer": "Wells Fargo NA"
            }
          },
          "invoice_next_billing_time": null,
          "invoice_billing_started_at_time": null,
          "first_payment_attempt_pg_error_code": "resource_missing",
          "first_payment_attempt_network_decline_code": null,
          "first_payment_attempt_network_advice_code": null
        }
      }
    }
  },
  "timestamp": "2025-09-07T20:05:02.792Z"
}
```

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