pub trait HealthCheckInterface {
    async fn health_check_db(&self) -> CustomResult<(), HealthCheckDBError>;
    async fn health_check_redis(&self, conf: &Settings) -> CustomResult<(), HealthCheckRedisError>;
}


pub fn is_network_token_payment_method_data(&self) -> bool {
        matches!(self, Self::NetworkToken(_))
    }


File: crates/router/src/core/payments/operations/payment_confirm_intent.rs
 Public structs: 1

use api_models::{enums::FrmSuggestion, payments::PaymentsConfirmIntentRequest};
use async_trait::async_trait;
use common_utils::{ext_traits::Encode, fp_utils::when, id_type, types::keymanager::ToEncryptable};
use error_stack::ResultExt;
use hyperswitch_domain_models::payments::PaymentConfirmData;
use hyperswitch_interfaces::api::ConnectorSpecifications;
use masking::{ExposeOptionInterface, PeekInterface};
use router_env::{instrument, tracing};

use super::{Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
    core::{
        admin,
        errors::{self, CustomResult, RouterResult, StorageErrorExt},
        payment_methods,
        payments::{
            self, call_decision_manager, helpers,
            operations::{self, ValidateStatusForOperation},
            populate_surcharge_details, CustomerDetails, OperationSessionSetters, PaymentAddress,
            PaymentData,
        },
        utils as core_utils,
    },
    routes::{app::ReqState, SessionState},
    services::{self, connector_integration_interface::ConnectorEnum},
    types::{
        self,
        api::{self, ConnectorCallType, PaymentIdTypeExt},
        domain::{self, types as domain_types},
        storage::{self, enums as storage_enums},
    },
    utils::{self, OptionExt},
};

#[derive(Debug, Clone, Copy)]
pub struct PaymentIntentConfirm;

impl ValidateStatusForOperation for PaymentIntentConfirm {
    /// Validate if the current operation can be performed on the current status of the payment intent
    fn validate_status_for_operation(
        &self,
        intent_status: common_enums::IntentStatus,
    ) -> Result<(), errors::ApiErrorResponse> {
        match intent_status {
            common_enums::IntentStatus::RequiresPaymentMethod => Ok(()),
            common_enums::IntentStatus::Succeeded
            | common_enums::IntentStatus::Conflicted
            | 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::RequiresCapture
            | common_enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture
            | common_enums::IntentStatus::PartiallyCaptured
            | common_enums::IntentStatus::RequiresConfirmation
            | common_enums::IntentStatus::PartiallyCapturedAndCapturable
            | common_enums::IntentStatus::Expired => {
                Err(errors::ApiErrorResponse::PaymentUnexpectedState {
                    current_flow: format!("{self:?}"),
                    field_name: "status".to_string(),
                    current_value: intent_status.to_string(),
                    states: ["requires_payment_method".to_string()].join(", "),
                })
            }
        }
    }
}

type BoxedConfirmOperation<'b, F> =
    super::BoxedOperation<'b, F, PaymentsConfirmIntentRequest, PaymentConfirmData<F>>;

// TODO: change the macro to include changes for v2
// TODO: PaymentData in the macro should be an input
impl<F: Send + Clone + Sync> Operation<F, PaymentsConfirmIntentRequest> for &PaymentIntentConfirm {
    type Data = PaymentConfirmData<F>;
    fn to_validate_request(
        &self,
    ) -> RouterResult<
        &(dyn ValidateRequest<F, PaymentsConfirmIntentRequest, Self::Data> + Send + Sync),
    > {
        Ok(*self)
    }
    fn to_get_tracker(
        &self,
    ) -> RouterResult<&(dyn GetTracker<F, Self::Data, PaymentsConfirmIntentRequest> + Send + Sync)>
    {
        Ok(*self)
    }
    fn to_domain(
        &self,
    ) -> RouterResult<&(dyn Domain<F, PaymentsConfirmIntentRequest, Self::Data>)> {
        Ok(*self)
    }
    fn to_update_tracker(
        &self,
    ) -> RouterResult<&(dyn UpdateTracker<F, Self::Data, PaymentsConfirmIntentRequest> + Send + Sync)>
    {
        Ok(*self)
    }
}
#[automatically_derived]
impl<F: Send + Clone + Sync> Operation<F, PaymentsConfirmIntentRequest> for PaymentIntentConfirm {
    type Data = PaymentConfirmData<F>;
    fn to_validate_request(
        &self,
    ) -> RouterResult<
        &(dyn ValidateRequest<F, PaymentsConfirmIntentRequest, Self::Data> + Send + Sync),
    > {
        Ok(self)
    }
    fn to_get_tracker(
        &self,
    ) -> RouterResult<&(dyn GetTracker<F, Self::Data, PaymentsConfirmIntentRequest> + Send + Sync)>
    {
        Ok(self)
    }
    fn to_domain(&self) -> RouterResult<&dyn Domain<F, PaymentsConfirmIntentRequest, Self::Data>> {
        Ok(self)
    }
    fn to_update_tracker(
        &self,
    ) -> RouterResult<&(dyn UpdateTracker<F, Self::Data, PaymentsConfirmIntentRequest> + Send + Sync)>
    {
        Ok(self)
    }
}

impl<F: Send + Clone + Sync> ValidateRequest<F, PaymentsConfirmIntentRequest, PaymentConfirmData<F>>
    for PaymentIntentConfirm
{
    #[instrument(skip_all)]
    fn validate_request<'a, 'b>(
        &'b self,
        request: &PaymentsConfirmIntentRequest,
        merchant_context: &'a domain::MerchantContext,
    ) -> RouterResult<operations::ValidateResult> {
        let validate_result = operations::ValidateResult {
            merchant_id: merchant_context.get_merchant_account().get_id().to_owned(),
            storage_scheme: merchant_context.get_merchant_account().storage_scheme,
            requeue: false,
        };

        Ok(validate_result)
    }
}

#[async_trait]
impl<F: Send + Clone + Sync> GetTracker<F, PaymentConfirmData<F>, PaymentsConfirmIntentRequest>
    for PaymentIntentConfirm
{
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_id: &id_type::GlobalPaymentId,
        request: &PaymentsConfirmIntentRequest,
        merchant_context: &domain::MerchantContext,
        profile: &domain::Profile,
        header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<operations::GetTrackerResponse<PaymentConfirmData<F>>> {
        let db = &*state.store;
        let key_manager_state = &state.into();

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

        // TODO (#7195): Add platform merchant account validation once publishable key auth is solved

        self.validate_status_for_operation(payment_intent.status)?;

        let cell_id = state.conf.cell_information.id.clone();

        let batch_encrypted_data = domain_types::crypto_operation(
            key_manager_state,
            common_utils::type_name!(hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt),
            domain_types::CryptoOperation::BatchEncrypt(
                hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::to_encryptable(
                    hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt {
                        payment_method_billing_address: request.payment_method_data.billing.as_ref().map(|address| address.clone().encode_to_value()).transpose().change_context(errors::ApiErrorResponse::InternalServerError).attach_printable("Failed to encode payment_method_billing address")?.map(masking::Secret::new),
                    },
                ),
            ),
            common_utils::types::keymanager::Identifier::Merchant(merchant_context.get_merchant_account().get_id().to_owned()),
            merchant_context.get_merchant_key_store().key.peek(),
        )
        .await
        .and_then(|val| val.try_into_batchoperation())
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while encrypting payment intent details".to_string())?;

        let encrypted_data =
             hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::from_encryptable(batch_encrypted_data)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed while encrypting payment intent details")?;

        let payment_attempt_domain_model =
            hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt::create_domain_model(
                &payment_intent,
                cell_id,
                storage_scheme,
                request,
                encrypted_data
            )
            .await?;

        let payment_attempt: hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt =
            db.insert_payment_attempt(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                payment_attempt_domain_model,
                storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Could not insert payment attempt")?;

        let payment_method_data = request
            .payment_method_data
            .payment_method_data
            .clone()
            .map(hyperswitch_domain_models::payment_method_data::PaymentMethodData::from);

        if request.payment_token.is_some() {
            when(
                !matches!(
                    payment_method_data,
                    Some(domain::payment_method_data::PaymentMethodData::CardToken(_))
                ),
                || {
                    Err(errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "payment_method_data",
                    })
                    .attach_printable(
                        "payment_method_data should be card_token when a token is passed",
                    )
                },
            )?;
        };

        let payment_address = hyperswitch_domain_models::payment_address::PaymentAddress::new(
            payment_intent
                .shipping_address
                .clone()
                .map(|address| address.into_inner()),
            payment_intent
                .billing_address
                .clone()
                .map(|address| address.into_inner()),
            payment_attempt
                .payment_method_billing_address
                .clone()
                .map(|address| address.into_inner()),
            Some(true),
        );

        let merchant_connector_details = request.merchant_connector_details.clone();

        let payment_data = PaymentConfirmData {
            flow: std::marker::PhantomData,
            payment_intent,
            payment_attempt,
            payment_method_data,
            payment_address,
            mandate_data: None,
            payment_method: None,
            merchant_connector_details,
            external_vault_pmd: None,
        };

        let get_trackers_response = operations::GetTrackerResponse { payment_data };

        Ok(get_trackers_response)
    }
}

#[async_trait]
impl<F: Clone + Send + Sync> Domain<F, PaymentsConfirmIntentRequest, PaymentConfirmData<F>>
    for PaymentIntentConfirm
{
    async fn get_customer_details<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut PaymentConfirmData<F>,
        merchant_key_store: &domain::MerchantKeyStore,
        storage_scheme: storage_enums::MerchantStorageScheme,
    ) -> CustomResult<(BoxedConfirmOperation<'a, F>, Option<domain::Customer>), errors::StorageError>
    {
        match payment_data.payment_intent.customer_id.clone() {
            Some(id) => {
                let customer = state
                    .store
                    .find_customer_by_global_id(
                        &state.into(),
                        &id,
                        merchant_key_store,
                        storage_scheme,
                    )
                    .await?;

                Ok((Box::new(self), Some(customer)))
            }
            None => Ok((Box::new(self), None)),
        }
    }

    async fn run_decision_manager<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut PaymentConfirmData<F>,
        business_profile: &domain::Profile,
    ) -> CustomResult<(), errors::ApiErrorResponse> {
        let authentication_type = payment_data.payment_intent.authentication_type;

        let authentication_type = match business_profile.three_ds_decision_manager_config.as_ref() {
            Some(three_ds_decision_manager_config) => call_decision_manager(
                state,
                three_ds_decision_manager_config.clone(),
                payment_data,
            )?,
            None => authentication_type,
        };

        if let Some(auth_type) = authentication_type {
            payment_data.payment_attempt.authentication_type = auth_type;
        }

        Ok(())
    }

    #[instrument(skip_all)]
    async fn make_pm_data<'a>(
        &'a self,
        state: &'a SessionState,
        payment_data: &mut PaymentConfirmData<F>,
        storage_scheme: storage_enums::MerchantStorageScheme,
        key_store: &domain::MerchantKeyStore,
        customer: &Option<domain::Customer>,
        business_profile: &domain::Profile,
        _should_retry_with_pan: bool,
    ) -> RouterResult<(
        BoxedConfirmOperation<'a, F>,
        Option<domain::PaymentMethodData>,
        Option<String>,
    )> {
        Ok((Box::new(self), None, None))
    }

    #[cfg(feature = "v2")]
    async fn perform_routing<'a>(
        &'a self,
        merchant_context: &domain::MerchantContext,
        business_profile: &domain::Profile,
        state: &SessionState,
        payment_data: &mut PaymentConfirmData<F>,
    ) -> CustomResult<ConnectorCallType, errors::ApiErrorResponse> {
        payments::connector_selection(
            state,
            merchant_context,
            business_profile,
            payment_data,
            None,
        )
        .await
    }

    #[instrument(skip_all)]
    async fn populate_payment_data<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut PaymentConfirmData<F>,
        _merchant_context: &domain::MerchantContext,
        business_profile: &domain::Profile,
        connector_data: &api::ConnectorData,
    ) -> CustomResult<(), errors::ApiErrorResponse> {
        let connector_request_reference_id = connector_data
            .connector
            .generate_connector_request_reference_id(
                &payment_data.payment_intent,
                &payment_data.payment_attempt,
            );
        payment_data.set_connector_request_reference_id(Some(connector_request_reference_id));
        Ok(())
    }

    #[cfg(feature = "v2")]
    async fn create_or_fetch_payment_method<'a>(
        &'a self,
        state: &SessionState,
        merchant_context: &domain::MerchantContext,
        business_profile: &domain::Profile,
        payment_data: &mut PaymentConfirmData<F>,
    ) -> CustomResult<(), errors::ApiErrorResponse> {
        let (payment_method, payment_method_data) = match (
            &payment_data.payment_attempt.payment_token,
            &payment_data.payment_method_data,
            &payment_data.payment_attempt.customer_acceptance,
        ) {
            (
                Some(payment_token),
                Some(domain::payment_method_data::PaymentMethodData::CardToken(card_token)),
                None,
            ) => {
                let (card_cvc, card_holder_name) = {
                    (
                        card_token
                            .card_cvc
                            .clone()
                            .ok_or(errors::ApiErrorResponse::InvalidDataValue {
                                field_name: "card_cvc",
                            })
                            .or(
                                payment_methods::vault::retrieve_and_delete_cvc_from_payment_token(
                                    state,
                                    payment_token,
                                    payment_data.payment_attempt.payment_method_type,
                                    merchant_context.get_merchant_key_store().key.get_inner(),
                                )
                                .await,
                            )
                            .attach_printable("card_cvc not provided")?,
                        card_token.card_holder_name.clone(),
                    )
                };

                let (payment_method, vault_data) =
                    payment_methods::vault::retrieve_payment_method_from_vault_using_payment_token(
                        state,
                        merchant_context,
                        business_profile,
                        payment_token,
                        &payment_data.payment_attempt.payment_method_type,
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed to retrieve payment method from vault")?;

                match vault_data {
                    domain::vault::PaymentMethodVaultingData::Card(card_detail) => {
                        let pm_data_from_vault =
                            domain::payment_method_data::PaymentMethodData::Card(
                                domain::payment_method_data::Card::from((
                                    card_detail,
                                    card_cvc,
                                    card_holder_name,
                                )),
                            );

                        (Some(payment_method), Some(pm_data_from_vault))
                    }
                    _ => Err(errors::ApiErrorResponse::NotImplemented {
                        message: errors::NotImplementedMessage::Reason(
                            "Non-card Tokenization not implemented".to_string(),
                        ),
                    })?,
                }
            }

            (Some(_payment_token), _, _) => Err(errors::ApiErrorResponse::InvalidDataValue {
                field_name: "payment_method_data",
            })
            .attach_printable("payment_method_data should be card_token when a token is passed")?,

            (None, Some(domain::PaymentMethodData::Card(card)), Some(_customer_acceptance)) => {
                let customer_id = match &payment_data.payment_intent.customer_id {
                    Some(customer_id) => customer_id.clone(),
                    None => {
                        return Err(errors::ApiErrorResponse::InvalidDataValue {
                            field_name: "customer_id",
                        })
                        .attach_printable("customer_id not provided");
                    }
                };

                let pm_create_data =
                    api::PaymentMethodCreateData::Card(api::CardDetail::from(card.clone()));

                let req = api::PaymentMethodCreate {
                    payment_method_type: payment_data.payment_attempt.payment_method_type,
                    payment_method_subtype: payment_data.payment_attempt.payment_method_subtype,
                    metadata: None,
                    customer_id,
                    payment_method_data: pm_create_data,
                    billing: None,
                    psp_tokenization: None,
                    network_tokenization: None,
                };

                let (_pm_response, payment_method) =
                    Box::pin(payment_methods::create_payment_method_core(
                        state,
                        &state.get_req_state(),
                        req,
                        merchant_context,
                        business_profile,
                    ))
                    .await?;

                // Don't modify payment_method_data in this case, only the payment_method and payment_method_id
                (Some(payment_method), None)
            }
            _ => (None, None), // Pass payment_data unmodified for any other case
        };

        if let Some(pm_data) = payment_method_data {
            payment_data.update_payment_method_data(pm_data);
        }
        if let Some(pm) = payment_method {
            payment_data.update_payment_method_and_pm_id(pm.get_id().clone(), pm);
        }

        Ok(())
    }

    #[cfg(feature = "v2")]
    async fn get_connector_from_request<'a>(
        &'a self,
        state: &SessionState,
        request: &PaymentsConfirmIntentRequest,
        payment_data: &mut PaymentConfirmData<F>,
    ) -> CustomResult<api::ConnectorData, errors::ApiErrorResponse> {
        let connector_data = helpers::get_connector_data_from_request(
            state,
            request.merchant_connector_details.clone(),
        )
        .await?;
        payment_data
            .set_connector_in_payment_attempt(Some(connector_data.connector_name.to_string()));
        Ok(connector_data)
    }
}

#[async_trait]
impl<F: Clone + Sync> UpdateTracker<F, PaymentConfirmData<F>, PaymentsConfirmIntentRequest>
    for PaymentIntentConfirm
{
    #[instrument(skip_all)]
    async fn update_trackers<'b>(
        &'b self,
        state: &'b SessionState,
        req_state: ReqState,
        mut payment_data: PaymentConfirmData<F>,
        customer: Option<domain::Customer>,
        storage_scheme: storage_enums::MerchantStorageScheme,
        updated_customer: Option<storage::CustomerUpdate>,
        key_store: &domain::MerchantKeyStore,
        frm_suggestion: Option<FrmSuggestion>,
        header_payload: hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<(BoxedConfirmOperation<'b, F>, PaymentConfirmData<F>)>
    where
        F: 'b + Send,
    {
        let db = &*state.store;
        let key_manager_state = &state.into();

        let intent_status = common_enums::IntentStatus::Processing;
        let attempt_status = common_enums::AttemptStatus::Pending;

        let connector = payment_data
            .payment_attempt
            .connector
            .clone()
            .get_required_value("connector")
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Connector is none when constructing response")?;

        // If `merchant_connector_details` are present in the payment request, `merchant_connector_id` will not be populated.
        let merchant_connector_id = match &payment_data.merchant_connector_details {
            Some(_details) => None,
            None => Some(
                payment_data
                    .payment_attempt
                    .merchant_connector_id
                    .clone()
                    .get_required_value("merchant_connector_id")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Merchant connector id is none when constructing response")?,
            ),
        };

        let payment_intent_update =
            hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::ConfirmIntent {
                status: intent_status,
                updated_by: storage_scheme.to_string(),
                active_attempt_id: Some(payment_data.payment_attempt.id.clone()),
            };

        let authentication_type = payment_data.payment_attempt.authentication_type;

        let connector_request_reference_id = payment_data
            .payment_attempt
            .connector_request_reference_id
            .clone();

        // Updates payment_attempt for cases where authorize flow is not performed.
        let connector_response_reference_id = payment_data
            .payment_attempt
            .connector_response_reference_id
            .clone();

        let payment_attempt_update = match &payment_data.payment_method {
            // In the case of a tokenized payment method, we update the payment attempt with the tokenized payment method details.
            Some(payment_method) => {
                hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntentTokenized {
                    status: attempt_status,
                    updated_by: storage_scheme.to_string(),
                    connector,
                    merchant_connector_id: merchant_connector_id.ok_or_else( || {
                        error_stack::report!(errors::ApiErrorResponse::InternalServerError)
                            .attach_printable("Merchant connector id is none when constructing response")
                    })?,
                    authentication_type,
                    payment_method_id : payment_method.get_id().clone()
                }
            }
            None => {
                hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntent {
                    status: attempt_status,
                    updated_by: storage_scheme.to_string(),
                    connector,
                    merchant_connector_id,
                    authentication_type,
                    connector_request_reference_id,
                    connector_response_reference_id,
                }
            }
        };

        let updated_payment_intent = db
            .update_payment_intent(
                key_manager_state,
                payment_data.payment_intent.clone(),
                payment_intent_update,
                key_store,
                storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to update payment intent")?;

        payment_data.payment_intent = updated_payment_intent;

        let updated_payment_attempt = db
            .update_payment_attempt(
                key_manager_state,
                key_store,
                payment_data.payment_attempt.clone(),
                payment_attempt_update,
                storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to update payment attempt")?;

        payment_data.payment_attempt = updated_payment_attempt;

        if let Some((customer, updated_customer)) = customer.zip(updated_customer) {
            let customer_id = customer.get_id().clone();
            let customer_merchant_id = customer.merchant_id.clone();

            let _updated_customer = db
                .update_customer_by_global_id(
                    key_manager_state,
                    &customer_id,
                    customer,
                    updated_customer,
                    key_store,
                    storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to update customer during `update_trackers`")?;
        }

        Ok((Box::new(self), payment_data))
    }
}


pub struct FieldError {
    pub field: String,
    pub error: String,
}


OpenAPI Block Path: components.schemas.OutgoingWebhookRequestContent
{
  "type": "object",
  "description": "The request information (headers and body) sent in the webhook.",
  "required": [
    "body",
    "headers"
  ],
  "properties": {
    "body": {
      "type": "string",
      "description": "The request body sent in the webhook."
    },
    "headers": {
      "type": "array",
      "items": {
        "type": "array",
        "items": {
          "allOf": [
            {
              "type": "string"
            },
            {
              "type": "string"
            }
          ]
        }
      },
      "description": "The request headers sent in the webhook.",
      "example": [
        [
          "content-type",
          "application/json"
        ],
        [
          "content-length",
          "1024"
        ]
      ]
    }
  }
}


pub struct KafkaFraudCheck<'a> {
    pub frm_id: &'a String,
    pub payment_id: &'a common_utils::id_type::PaymentId,
    pub merchant_id: &'a common_utils::id_type::MerchantId,
    pub attempt_id: &'a String,
    #[serde(with = "time::serde::timestamp")]
    pub created_at: OffsetDateTime,
    pub frm_name: &'a String,
    pub frm_transaction_id: Option<&'a String>,
    pub frm_transaction_type: FraudCheckType,
    pub frm_status: FraudCheckStatus,
    pub frm_score: Option<i32>,
    pub frm_reason: Option<serde_json::Value>,
    pub frm_error: Option<&'a String>,
    pub payment_details: Option<serde_json::Value>,
    pub metadata: Option<serde_json::Value>,
    #[serde(with = "time::serde::timestamp")]
    pub modified_at: OffsetDateTime,
    pub last_step: FraudCheckLastStep,
    pub payment_capture_method: Option<storage_enums::CaptureMethod>, // In postFrm, we are updating capture method from automatic to manual. To store the merchant actual capture method, we are storing the actual capture method in payment_capture_method. It will be useful while approving the FRM decision.
}


pub async fn get_token_based_on_retry_type(
        state: &SessionState,
        connector_customer_id: &str,
        retry_algorithm_type: RevenueRecoveryAlgorithmType,
        last_token_used: Option<&str>,
    ) -> CustomResult<Option<PaymentProcessorTokenStatus>, errors::StorageError> {
        let mut token = None;
        match retry_algorithm_type {
            RevenueRecoveryAlgorithmType::Monitoring => {
                logger::error!("Monitoring type found for Revenue Recovery retry payment");
            }

            RevenueRecoveryAlgorithmType::Cascading => {
                token = match last_token_used {
                    Some(token_id) => {
                        Self::get_payment_processor_token_using_token_id(
                            state,
                            connector_customer_id,
                            token_id,
                        )
                        .await?
                    }
                    None => None,
                };
            }

            RevenueRecoveryAlgorithmType::Smart => {
                token = Self::get_payment_processor_token_with_schedule_time(
                    state,
                    connector_customer_id,
                )
                .await?;
            }
        }

        token = token.and_then(|t| {
            t.is_hard_decline
                .unwrap_or(false)
                .then(|| {
                    logger::error!("Token is hard declined");
                })
                .map_or(Some(t), |_| None)
        });

        Ok(token)
    }


pub struct ChatResponse {
    pub response: Secret<serde_json::Value>,
    pub merchant_id: id_type::MerchantId,
    pub status: String,
    #[serde(skip_serializing)]
    pub query_executed: Option<Secret<String>>,
    #[serde(skip_serializing)]
    pub row_count: Option<i32>,
}


pub struct Item {
    pub name: String,
    pub unit_price: FloatMajorUnit,
    pub description: Option<String>,
    pub quantity: i64,
}


File: crates/router/src/core/webhooks/outgoing.rs

use std::collections::HashMap;

use api_models::{
    webhook_events::{OutgoingWebhookRequestContent, OutgoingWebhookResponseContent},
    webhooks,
};
use common_utils::{
    ext_traits::{Encode, StringExt},
    request::RequestContent,
    type_name,
    types::keymanager::{Identifier, KeyManagerState},
};
use diesel_models::process_tracker::business_status;
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::type_encryption::{crypto_operation, CryptoOperation};
use hyperswitch_interfaces::consts;
use masking::{ExposeInterface, Mask, PeekInterface, Secret};
use router_env::{
    instrument,
    tracing::{self, Instrument},
};

use super::{types, utils, MERCHANT_ID};
#[cfg(feature = "stripe")]
use crate::compatibility::stripe::webhooks as stripe_webhooks;
use crate::{
    core::{
        errors::{self, CustomResult},
        metrics,
    },
    db::StorageInterface,
    events::outgoing_webhook_logs::{
        OutgoingWebhookEvent, OutgoingWebhookEventContent, OutgoingWebhookEventMetric,
    },
    logger,
    routes::{app::SessionStateInfo, SessionState},
    services,
    types::{
        api,
        domain::{self},
        storage::{self, enums},
        transformers::ForeignFrom,
    },
    utils::{OptionExt, ValueExt},
    workflows::outgoing_webhook_retry,
};

#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub(crate) async fn create_event_and_trigger_outgoing_webhook(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    business_profile: domain::Profile,
    event_type: enums::EventType,
    event_class: enums::EventClass,
    primary_object_id: String,
    primary_object_type: enums::EventObjectType,
    content: api::OutgoingWebhookContent,
    primary_object_created_at: Option<time::PrimitiveDateTime>,
) -> CustomResult<(), errors::ApiErrorResponse> {
    let delivery_attempt = enums::WebhookDeliveryAttempt::InitialAttempt;
    let idempotent_event_id =
        utils::get_idempotent_event_id(&primary_object_id, event_type, delivery_attempt);
    let webhook_url_result = get_webhook_url_from_business_profile(&business_profile);

    if !state.conf.webhooks.outgoing_enabled
        || webhook_url_result.is_err()
        || webhook_url_result.as_ref().is_ok_and(String::is_empty)
    {
        logger::debug!(
            business_profile_id=?business_profile.get_id(),
            %idempotent_event_id,
            "Outgoing webhooks are disabled in application configuration, or merchant webhook URL \
             could not be obtained; skipping outgoing webhooks for event"
        );
        return Ok(());
    }

    let event_id = utils::generate_event_id();
    let merchant_id = business_profile.merchant_id.clone();
    let now = common_utils::date_time::now();

    let outgoing_webhook = api::OutgoingWebhook {
        merchant_id: merchant_id.clone(),
        event_id: event_id.clone(),
        event_type,
        content: content.clone(),
        timestamp: now,
    };

    let request_content =
        get_outgoing_webhook_request(&merchant_context, outgoing_webhook, &business_profile)
            .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
            .attach_printable("Failed to construct outgoing webhook request content")?;

    let event_metadata = storage::EventMetadata::foreign_from(&content);
    let key_manager_state = &(&state).into();
    let new_event = domain::Event {
        event_id: event_id.clone(),
        event_type,
        event_class,
        is_webhook_notified: false,
        primary_object_id,
        primary_object_type,
        created_at: now,
        merchant_id: Some(business_profile.merchant_id.clone()),
        business_profile_id: Some(business_profile.get_id().to_owned()),
        primary_object_created_at,
        idempotent_event_id: Some(idempotent_event_id.clone()),
        initial_attempt_id: Some(event_id.clone()),
        request: Some(
            crypto_operation(
                key_manager_state,
                type_name!(domain::Event),
                CryptoOperation::Encrypt(
                    request_content
                        .encode_to_string_of_json()
                        .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
                        .attach_printable("Failed to encode outgoing webhook request content")
                        .map(Secret::new)?,
                ),
                Identifier::Merchant(
                    merchant_context
                        .get_merchant_key_store()
                        .merchant_id
                        .clone(),
                ),
                merchant_context
                    .get_merchant_key_store()
                    .key
                    .get_inner()
                    .peek(),
            )
            .await
            .and_then(|val| val.try_into_operation())
            .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
            .attach_printable("Failed to encrypt outgoing webhook request content")?,
        ),
        response: None,
        delivery_attempt: Some(delivery_attempt),
        metadata: Some(event_metadata),
        is_overall_delivery_successful: Some(false),
    };

    let lock_value = utils::perform_redis_lock(
        &state,
        &idempotent_event_id,
        merchant_context.get_merchant_account().get_id().to_owned(),
    )
    .await?;

    if lock_value.is_none() {
        return Ok(());
    }

    if (state
        .store
        .find_event_by_merchant_id_idempotent_event_id(
            key_manager_state,
            &merchant_id,
            &idempotent_event_id,
            merchant_context.get_merchant_key_store(),
        )
        .await)
        .is_ok()
    {
        logger::debug!(
            "Event with idempotent ID `{idempotent_event_id}` already exists in the database"
        );
        utils::free_redis_lock(
            &state,
            &idempotent_event_id,
            merchant_context.get_merchant_account().get_id().to_owned(),
            lock_value,
        )
        .await?;
        return Ok(());
    }

    let event_insert_result = state
        .store
        .insert_event(
            key_manager_state,
            new_event,
            merchant_context.get_merchant_key_store(),
        )
        .await;

    let event = match event_insert_result {
        Ok(event) => Ok(event),
        Err(error) => {
            logger::error!(event_insertion_failure=?error);
            Err(error
                .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
                .attach_printable("Failed to insert event in events table"))
        }
    }?;

    utils::free_redis_lock(
        &state,
        &idempotent_event_id,
        merchant_context.get_merchant_account().get_id().to_owned(),
        lock_value,
    )
    .await?;

    let process_tracker = add_outgoing_webhook_retry_task_to_process_tracker(
        &*state.store,
        &business_profile,
        &event,
    )
    .await
    .inspect_err(|error| {
        logger::error!(
            ?error,
            "Failed to add outgoing webhook retry task to process tracker"
        );
    })
    .ok();

    let cloned_key_store = merchant_context.get_merchant_key_store().clone();
    // Using a tokio spawn here and not arbiter because not all caller of this function
    // may have an actix arbiter
    tokio::spawn(
        async move {
            Box::pin(trigger_webhook_and_raise_event(
                state,
                business_profile,
                &cloned_key_store,
                event,
                request_content,
                delivery_attempt,
                Some(content),
                process_tracker,
            ))
            .await;
        }
        .in_current_span(),
    );

    Ok(())
}

#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub(crate) async fn trigger_webhook_and_raise_event(
    state: SessionState,
    business_profile: domain::Profile,
    merchant_key_store: &domain::MerchantKeyStore,
    event: domain::Event,
    request_content: OutgoingWebhookRequestContent,
    delivery_attempt: enums::WebhookDeliveryAttempt,
    content: Option<api::OutgoingWebhookContent>,
    process_tracker: Option<storage::ProcessTracker>,
) {
    logger::debug!(
        event_id=%event.event_id,
        idempotent_event_id=?event.idempotent_event_id,
        initial_attempt_id=?event.initial_attempt_id,
        "Attempting to send webhook"
    );

    let merchant_id = business_profile.merchant_id.clone();
    let trigger_webhook_result = trigger_webhook_to_merchant(
        state.clone(),
        business_profile,
        merchant_key_store,
        event.clone(),
        request_content,
        delivery_attempt,
        process_tracker,
    )
    .await;

    let _ = raise_webhooks_analytics_event(
        state,
        trigger_webhook_result,
        content,
        merchant_id,
        event,
        merchant_key_store,
    )
    .await;
}

async fn trigger_webhook_to_merchant(
    state: SessionState,
    business_profile: domain::Profile,
    merchant_key_store: &domain::MerchantKeyStore,
    event: domain::Event,
    request_content: OutgoingWebhookRequestContent,
    delivery_attempt: enums::WebhookDeliveryAttempt,
    process_tracker: Option<storage::ProcessTracker>,
) -> CustomResult<(), errors::WebhooksFlowError> {
    let webhook_url = match (
        get_webhook_url_from_business_profile(&business_profile),
        process_tracker.clone(),
    ) {
        (Ok(webhook_url), _) => Ok(webhook_url),
        (Err(error), Some(process_tracker)) => {
            if !error
                .current_context()
                .is_webhook_delivery_retryable_error()
            {
                logger::debug!("Failed to obtain merchant webhook URL, aborting retries");
                state
                    .store
                    .as_scheduler()
                    .finish_process_with_business_status(process_tracker, business_status::FAILURE)
                    .await
                    .change_context(
                        errors::WebhooksFlowError::OutgoingWebhookProcessTrackerTaskUpdateFailed,
                    )?;
            }
            Err(error)
        }
        (Err(error), None) => Err(error),
    }?;

    let event_id = event.event_id;

    let headers = request_content
        .headers
        .into_iter()
        .map(|(name, value)| (name, value.into_masked()))
        .collect();
    let request = services::RequestBuilder::new()
        .method(services::Method::Post)
        .url(&webhook_url)
        .attach_default_headers()
        .headers(headers)
        .set_body(RequestContent::RawBytes(
            request_content.body.expose().into_bytes(),
        ))
        .build();

    let response = state
        .api_client
        .send_request(&state, request, None, false)
        .await;

    metrics::WEBHOOK_OUTGOING_COUNT.add(
        1,
        router_env::metric_attributes!((MERCHANT_ID, business_profile.merchant_id.clone())),
    );
    logger::debug!(outgoing_webhook_response=?response);

    match delivery_attempt {
        enums::WebhookDeliveryAttempt::InitialAttempt => match response {
            Err(client_error) => {
                api_client_error_handler(
                    state.clone(),
                    merchant_key_store.clone(),
                    &business_profile.merchant_id,
                    &event_id,
                    client_error,
                    delivery_attempt,
                    ScheduleWebhookRetry::NoSchedule,
                )
                .await?
            }
            Ok(response) => {
                let status_code = response.status();
                let updated_event = update_event_in_storage(
                    state.clone(),
                    merchant_key_store.clone(),
                    &business_profile.merchant_id,
                    &event_id,
                    response,
                )
                .await?;

                if status_code.is_success() {
                    update_overall_delivery_status_in_storage(
                        state.clone(),
                        merchant_key_store.clone(),
                        &business_profile.merchant_id,
                        updated_event,
                    )
                    .await?;

                    success_response_handler(
                        state.clone(),
                        &business_profile.merchant_id,
                        process_tracker,
                        business_status::INITIAL_DELIVERY_ATTEMPT_SUCCESSFUL,
                    )
                    .await?;
                } else {
                    error_response_handler(
                        state.clone(),
                        &business_profile.merchant_id,
                        delivery_attempt,
                        status_code.as_u16(),
                        "Ignoring error when sending webhook to merchant",
                        ScheduleWebhookRetry::NoSchedule,
                    )
                    .await?;
                }
            }
        },
        enums::WebhookDeliveryAttempt::AutomaticRetry => {
            let process_tracker = process_tracker
                .get_required_value("process_tracker")
                .change_context(errors::WebhooksFlowError::OutgoingWebhookRetrySchedulingFailed)
                .attach_printable("`process_tracker` is unavailable in automatic retry flow")?;
            match response {
                Err(client_error) => {
                    api_client_error_handler(
                        state.clone(),
                        merchant_key_store.clone(),
                        &business_profile.merchant_id,
                        &event_id,
                        client_error,
                        delivery_attempt,
                        ScheduleWebhookRetry::WithProcessTracker(Box::new(process_tracker)),
                    )
                    .await?;
                }
                Ok(response) => {
                    let status_code = response.status();
                    let updated_event = update_event_in_storage(
                        state.clone(),
                        merchant_key_store.clone(),
                        &business_profile.merchant_id,
                        &event_id,
                        response,
                    )
                    .await?;

                    if status_code.is_success() {
                        update_overall_delivery_status_in_storage(
                            state.clone(),
                            merchant_key_store.clone(),
                            &business_profile.merchant_id,
                            updated_event,
                        )
                        .await?;

                        success_response_handler(
                            state.clone(),
                            &business_profile.merchant_id,
                            Some(process_tracker),
                            "COMPLETED_BY_PT",
                        )
                        .await?;
                    } else {
                        error_response_handler(
                            state.clone(),
                            &business_profile.merchant_id,
                            delivery_attempt,
                            status_code.as_u16(),
                            "An error occurred when sending webhook to merchant",
                            ScheduleWebhookRetry::WithProcessTracker(Box::new(process_tracker)),
                        )
                        .await?;
                    }
                }
            }
        }
        enums::WebhookDeliveryAttempt::ManualRetry => match response {
            Err(client_error) => {
                api_client_error_handler(
                    state.clone(),
                    merchant_key_store.clone(),
                    &business_profile.merchant_id,
                    &event_id,
                    client_error,
                    delivery_attempt,
                    ScheduleWebhookRetry::NoSchedule,
                )
                .await?
            }
            Ok(response) => {
                let status_code = response.status();
                let _updated_event = update_event_in_storage(
                    state.clone(),
                    merchant_key_store.clone(),
                    &business_profile.merchant_id,
                    &event_id,
                    response,
                )
                .await?;

                if status_code.is_success() {
                    increment_webhook_outgoing_received_count(&business_profile.merchant_id);
                } else {
                    error_response_handler(
                        state,
                        &business_profile.merchant_id,
                        delivery_attempt,
                        status_code.as_u16(),
                        "Ignoring error when sending webhook to merchant",
                        ScheduleWebhookRetry::NoSchedule,
                    )
                    .await?;
                }
            }
        },
    }

    Ok(())
}

async fn raise_webhooks_analytics_event(
    state: SessionState,
    trigger_webhook_result: CustomResult<(), errors::WebhooksFlowError>,
    content: Option<api::OutgoingWebhookContent>,
    merchant_id: common_utils::id_type::MerchantId,
    event: domain::Event,
    merchant_key_store: &domain::MerchantKeyStore,
) {
    let key_manager_state: &KeyManagerState = &(&state).into();
    let event_id = event.event_id;

    let error = if let Err(error) = trigger_webhook_result {
        logger::error!(?error, "Failed to send webhook to merchant");

        serde_json::to_value(error.current_context())
            .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
            .inspect_err(|error| {
                logger::error!(?error, "Failed to serialize outgoing webhook error as JSON");
            })
            .ok()
    } else {
        None
    };

    let outgoing_webhook_event_content = content
        .as_ref()
        .and_then(api::OutgoingWebhookContent::get_outgoing_webhook_event_content)
        .or_else(|| get_outgoing_webhook_event_content_from_event_metadata(event.metadata));

    // Fetch updated_event from db
    let updated_event = state
        .store
        .find_event_by_merchant_id_event_id(
            key_manager_state,
            &merchant_id,
            &event_id,
            merchant_key_store,
        )
        .await
        .attach_printable_lazy(|| format!("event not found for id: {}", &event_id))
        .map_err(|error| {
            logger::error!(?error);
            error
        })
        .ok();

    // Get status_code from webhook response
    let status_code = updated_event.and_then(|updated_event| {
        let webhook_response: Option<OutgoingWebhookResponseContent> =
            updated_event.response.and_then(|res| {
                res.peek()
                    .parse_struct("OutgoingWebhookResponseContent")
                    .map_err(|error| {
                        logger::error!(?error, "Error deserializing webhook response");
                        error
                    })
                    .ok()
            });
        webhook_response.and_then(|res| res.status_code)
    });

    let webhook_event = OutgoingWebhookEvent::new(
        state.tenant.tenant_id.clone(),
        merchant_id,
        event_id,
        event.event_type,
        outgoing_webhook_event_content,
        error,
        event.initial_attempt_id,
        status_code,
        event.delivery_attempt,
    );
    state.event_handler().log_event(&webhook_event);
}

pub(crate) async fn add_outgoing_webhook_retry_task_to_process_tracker(
    db: &dyn StorageInterface,
    business_profile: &domain::Profile,
    event: &domain::Event,
) -> CustomResult<storage::ProcessTracker, errors::StorageError> {
    let schedule_time = outgoing_webhook_retry::get_webhook_delivery_retry_schedule_time(
        db,
        &business_profile.merchant_id,
        0,
    )
    .await
    .ok_or(errors::StorageError::ValueNotFound(
        "Process tracker schedule time".into(), // Can raise a better error here
    ))
    .attach_printable("Failed to obtain initial process tracker schedule time")?;

    let tracking_data = types::OutgoingWebhookTrackingData {
        merchant_id: business_profile.merchant_id.clone(),
        business_profile_id: business_profile.get_id().to_owned(),
        event_type: event.event_type,
        event_class: event.event_class,
        primary_object_id: event.primary_object_id.clone(),
        primary_object_type: event.primary_object_type,
        initial_attempt_id: event.initial_attempt_id.clone(),
    };

    let runner = storage::ProcessTrackerRunner::OutgoingWebhookRetryWorkflow;
    let task = "OUTGOING_WEBHOOK_RETRY";
    let tag = ["OUTGOING_WEBHOOKS"];
    let process_tracker_id = scheduler::utils::get_process_tracker_id(
        runner,
        task,
        &event.event_id,
        &business_profile.merchant_id,
    );
    let process_tracker_entry = storage::ProcessTrackerNew::new(
        process_tracker_id,
        task,
        runner,
        tag,
        tracking_data,
        None,
        schedule_time,
        common_types::consts::API_VERSION,
    )
    .map_err(errors::StorageError::from)?;

    let attributes = router_env::metric_attributes!(("flow", "OutgoingWebhookRetry"));
    match db.insert_process(process_tracker_entry).await {
        Ok(process_tracker) => {
            crate::routes::metrics::TASKS_ADDED_COUNT.add(1, attributes);
            Ok(process_tracker)
        }
        Err(error) => {
            crate::routes::metrics::TASK_ADDITION_FAILURES_COUNT.add(1, attributes);
            Err(error)
        }
    }
}

fn get_webhook_url_from_business_profile(
    business_profile: &domain::Profile,
) -> CustomResult<String, errors::WebhooksFlowError> {
    let webhook_details = business_profile
        .webhook_details
        .clone()
        .get_required_value("webhook_details")
        .change_context(errors::WebhooksFlowError::MerchantWebhookDetailsNotFound)?;

    webhook_details
        .webhook_url
        .get_required_value("webhook_url")
        .change_context(errors::WebhooksFlowError::MerchantWebhookUrlNotConfigured)
        .map(ExposeInterface::expose)
}

pub(crate) fn get_outgoing_webhook_request(
    merchant_context: &domain::MerchantContext,
    outgoing_webhook: api::OutgoingWebhook,
    business_profile: &domain::Profile,
) -> CustomResult<OutgoingWebhookRequestContent, errors::WebhooksFlowError> {
    #[inline]
    fn get_outgoing_webhook_request_inner<WebhookType: types::OutgoingWebhookType>(
        outgoing_webhook: api::OutgoingWebhook,
        business_profile: &domain::Profile,
    ) -> CustomResult<OutgoingWebhookRequestContent, errors::WebhooksFlowError> {
        let mut headers = vec![
            (
                reqwest::header::CONTENT_TYPE.to_string(),
                mime::APPLICATION_JSON.essence_str().into(),
            ),
            (
                reqwest::header::USER_AGENT.to_string(),
                consts::USER_AGENT.to_string().into(),
            ),
        ];

        let transformed_outgoing_webhook = WebhookType::from(outgoing_webhook);
        let payment_response_hash_key = business_profile.payment_response_hash_key.clone();
        let custom_headers = business_profile
            .outgoing_webhook_custom_http_headers
            .clone()
            .map(|headers| {
                headers
                    .into_inner()
                    .expose()
                    .parse_value::<HashMap<String, String>>("HashMap<String,String>")
                    .change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed)
                    .attach_printable("Failed to deserialize outgoing webhook custom HTTP headers")
            })
            .transpose()?;
        if let Some(ref map) = custom_headers {
            headers.extend(
                map.iter()
                    .map(|(key, value)| (key.clone(), value.clone().into_masked())),
            );
        };
        let outgoing_webhooks_signature = transformed_outgoing_webhook
            .get_outgoing_webhooks_signature(payment_response_hash_key)?;

        if let Some(signature) = outgoing_webhooks_signature.signature {
            WebhookType::add_webhook_header(&mut headers, signature)
        }

        Ok(OutgoingWebhookRequestContent {
            body: outgoing_webhooks_signature.payload,
            headers: headers
                .into_iter()
                .map(|(name, value)| (name, Secret::new(value.into_inner())))
                .collect(),
        })
    }

    match merchant_context
        .get_merchant_account()
        .get_compatible_connector()
    {
        #[cfg(feature = "stripe")]
        Some(api_models::enums::Connector::Stripe) => get_outgoing_webhook_request_inner::<
            stripe_webhooks::StripeOutgoingWebhook,
        >(outgoing_webhook, business_profile),
        _ => get_outgoing_webhook_request_inner::<webhooks::OutgoingWebhook>(
            outgoing_webhook,
            business_profile,
        ),
    }
}

#[derive(Debug)]
enum ScheduleWebhookRetry {
    WithProcessTracker(Box<storage::ProcessTracker>),
    NoSchedule,
}

async fn update_event_if_client_error(
    state: SessionState,
    merchant_key_store: domain::MerchantKeyStore,
    merchant_id: &common_utils::id_type::MerchantId,
    event_id: &str,
    error_message: String,
) -> CustomResult<domain::Event, errors::WebhooksFlowError> {
    let is_webhook_notified = false;
    let key_manager_state = &(&state).into();
    let response_to_store = OutgoingWebhookResponseContent {
        body: None,
        headers: None,
        status_code: None,
        error_message: Some(error_message),
    };

    let event_update = domain::EventUpdate::UpdateResponse {
        is_webhook_notified,
        response: Some(
            crypto_operation(
                key_manager_state,
                type_name!(domain::Event),
                CryptoOperation::Encrypt(
                    response_to_store
                        .encode_to_string_of_json()
                        .change_context(
                            errors::WebhooksFlowError::OutgoingWebhookResponseEncodingFailed,
                        )
                        .map(Secret::new)?,
                ),
                Identifier::Merchant(merchant_key_store.merchant_id.clone()),
                merchant_key_store.key.get_inner().peek(),
            )
            .await
            .and_then(|val| val.try_into_operation())
            .change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)
            .attach_printable("Failed to encrypt outgoing webhook response content")?,
        ),
    };

    state
        .store
        .update_event_by_merchant_id_event_id(
            key_manager_state,
            merchant_id,
            event_id,
            event_update,
            &merchant_key_store,
        )
        .await
        .change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)
}

async fn api_client_error_handler(
    state: SessionState,
    merchant_key_store: domain::MerchantKeyStore,
    merchant_id: &common_utils::id_type::MerchantId,
    event_id: &str,
    client_error: error_stack::Report<errors::ApiClientError>,
    delivery_attempt: enums::WebhookDeliveryAttempt,
    schedule_webhook_retry: ScheduleWebhookRetry,
) -> CustomResult<(), errors::WebhooksFlowError> {
    // Not including detailed error message in response information since it contains too
    // much of diagnostic information to be exposed to the merchant.
    update_event_if_client_error(
        state.clone(),
        merchant_key_store,
        merchant_id,
        event_id,
        "Unable to send request to merchant server".to_string(),
    )
    .await?;

    let error = client_error.change_context(errors::WebhooksFlowError::CallToMerchantFailed);
    logger::error!(
        ?error,
        ?delivery_attempt,
        "An error occurred when sending webhook to merchant"
    );

    if let ScheduleWebhookRetry::WithProcessTracker(process_tracker) = schedule_webhook_retry {
        // Schedule a retry attempt for webhook delivery
        outgoing_webhook_retry::retry_webhook_delivery_task(
            &*state.store,
            merchant_id,
            *process_tracker,
        )
        .await
        .change_context(errors::WebhooksFlowError::OutgoingWebhookRetrySchedulingFailed)?;
    }

    Err(error)
}

async fn update_event_in_storage(
    state: SessionState,
    merchant_key_store: domain::MerchantKeyStore,
    merchant_id: &common_utils::id_type::MerchantId,
    event_id: &str,
    response: reqwest::Response,
) -> CustomResult<domain::Event, errors::WebhooksFlowError> {
    let status_code = response.status();
    let is_webhook_notified = status_code.is_success();
    let key_manager_state = &(&state).into();
    let response_headers = response
        .headers()
        .iter()
        .map(|(name, value)| {
            (
                name.as_str().to_owned(),
                value
                    .to_str()
                    .map(|s| Secret::from(String::from(s)))
                    .unwrap_or_else(|error| {
                        logger::warn!(
                            "Response header {} contains non-UTF-8 characters: {error:?}",
                            name.as_str()
                        );
                        Secret::from(String::from("Non-UTF-8 header value"))
                    }),
            )
        })
        .collect::<Vec<_>>();
    let response_body = response
        .text()
        .await
        .map(Secret::from)
        .unwrap_or_else(|error| {
            logger::warn!("Response contains non-UTF-8 characters: {error:?}");
            Secret::from(String::from("Non-UTF-8 response body"))
        });
    let response_to_store = OutgoingWebhookResponseContent {
        body: Some(response_body),
        headers: Some(response_headers),
        status_code: Some(status_code.as_u16()),
        error_message: None,
    };

    let event_update = domain::EventUpdate::UpdateResponse {
        is_webhook_notified,
        response: Some(
            crypto_operation(
                key_manager_state,
                type_name!(domain::Event),
                CryptoOperation::Encrypt(
                    response_to_store
                        .encode_to_string_of_json()
                        .change_context(
                            errors::WebhooksFlowError::OutgoingWebhookResponseEncodingFailed,
                        )
                        .map(Secret::new)?,
                ),
                Identifier::Merchant(merchant_key_store.merchant_id.clone()),
                merchant_key_store.key.get_inner().peek(),
            )
            .await
            .and_then(|val| val.try_into_operation())
            .change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)
            .attach_printable("Failed to encrypt outgoing webhook response content")?,
        ),
    };
    state
        .store
        .update_event_by_merchant_id_event_id(
            key_manager_state,
            merchant_id,
            event_id,
            event_update,
            &merchant_key_store,
        )
        .await
        .change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)
}

async fn update_overall_delivery_status_in_storage(
    state: SessionState,
    merchant_key_store: domain::MerchantKeyStore,
    merchant_id: &common_utils::id_type::MerchantId,
    updated_event: domain::Event,
) -> CustomResult<(), errors::WebhooksFlowError> {
    let key_manager_state = &(&state).into();

    let update_overall_delivery_status = domain::EventUpdate::OverallDeliveryStatusUpdate {
        is_overall_delivery_successful: true,
    };

    let initial_attempt_id = updated_event.initial_attempt_id.as_ref();
    let delivery_attempt = updated_event.delivery_attempt;

    if let Some((
        initial_attempt_id,
        enums::WebhookDeliveryAttempt::InitialAttempt
        | enums::WebhookDeliveryAttempt::AutomaticRetry,
    )) = initial_attempt_id.zip(delivery_attempt)
    {
        state
            .store
            .update_event_by_merchant_id_event_id(
                key_manager_state,
                merchant_id,
                initial_attempt_id.as_str(),
                update_overall_delivery_status,
                &merchant_key_store,
            )
            .await
            .change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)
            .attach_printable("Failed to update initial delivery attempt")?;
    }

    Ok(())
}

fn increment_webhook_outgoing_received_count(merchant_id: &common_utils::id_type::MerchantId) {
    metrics::WEBHOOK_OUTGOING_RECEIVED_COUNT.add(
        1,
        router_env::metric_attributes!((MERCHANT_ID, merchant_id.clone())),
    )
}

async fn success_response_handler(
    state: SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    process_tracker: Option<storage::ProcessTracker>,
    business_status: &'static str,
) -> CustomResult<(), errors::WebhooksFlowError> {
    increment_webhook_outgoing_received_count(merchant_id);

    match process_tracker {
        Some(process_tracker) => state
            .store
            .as_scheduler()
            .finish_process_with_business_status(process_tracker, business_status)
            .await
            .change_context(
                errors::WebhooksFlowError::OutgoingWebhookProcessTrackerTaskUpdateFailed,
            ),
        None => Ok(()),
    }
}

async fn error_response_handler(
    state: SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    delivery_attempt: enums::WebhookDeliveryAttempt,
    status_code: u16,
    log_message: &'static str,
    schedule_webhook_retry: ScheduleWebhookRetry,
) -> CustomResult<(), errors::WebhooksFlowError> {
    metrics::WEBHOOK_OUTGOING_NOT_RECEIVED_COUNT.add(
        1,
        router_env::metric_attributes!((MERCHANT_ID, merchant_id.clone())),
    );

    let error = report!(errors::WebhooksFlowError::NotReceivedByMerchant);
    logger::warn!(?error, ?delivery_attempt, status_code, %log_message);

    if let ScheduleWebhookRetry::WithProcessTracker(process_tracker) = schedule_webhook_retry {
        // Schedule a retry attempt for webhook delivery
        outgoing_webhook_retry::retry_webhook_delivery_task(
            &*state.store,
            merchant_id,
            *process_tracker,
        )
        .await
        .change_context(errors::WebhooksFlowError::OutgoingWebhookRetrySchedulingFailed)?;
    }

    Err(error)
}

impl ForeignFrom<&api::OutgoingWebhookContent> for storage::EventMetadata {
    fn foreign_from(content: &api::OutgoingWebhookContent) -> Self {
        match content {
            webhooks::OutgoingWebhookContent::PaymentDetails(payments_response) => Self::Payment {
                payment_id: payments_response.payment_id.clone(),
            },
            webhooks::OutgoingWebhookContent::RefundDetails(refund_response) => Self::Refund {
                payment_id: refund_response.payment_id.clone(),
                refund_id: refund_response.refund_id.clone(),
            },
            webhooks::OutgoingWebhookContent::DisputeDetails(dispute_response) => Self::Dispute {
                payment_id: dispute_response.payment_id.clone(),
                attempt_id: dispute_response.attempt_id.clone(),
                dispute_id: dispute_response.dispute_id.clone(),
            },
            webhooks::OutgoingWebhookContent::MandateDetails(mandate_response) => Self::Mandate {
                payment_method_id: mandate_response.payment_method_id.clone(),
                mandate_id: mandate_response.mandate_id.clone(),
            },
            #[cfg(feature = "payouts")]
            webhooks::OutgoingWebhookContent::PayoutDetails(payout_response) => Self::Payout {
                payout_id: payout_response.payout_id.clone(),
            },
        }
    }
}

fn get_outgoing_webhook_event_content_from_event_metadata(
    event_metadata: Option<storage::EventMetadata>,
) -> Option<OutgoingWebhookEventContent> {
    event_metadata.map(|metadata| match metadata {
        diesel_models::EventMetadata::Payment { payment_id } => {
            OutgoingWebhookEventContent::Payment {
                payment_id,
                content: serde_json::Value::Null,
            }
        }
        diesel_models::EventMetadata::Payout { payout_id } => OutgoingWebhookEventContent::Payout {
            payout_id,
            content: serde_json::Value::Null,
        },
        diesel_models::EventMetadata::Refund {
            payment_id,
            refund_id,
        } => OutgoingWebhookEventContent::Refund {
            payment_id,
            refund_id,
            content: serde_json::Value::Null,
        },
        diesel_models::EventMetadata::Dispute {
            payment_id,
            attempt_id,
            dispute_id,
        } => OutgoingWebhookEventContent::Dispute {
            payment_id,
            attempt_id,
            dispute_id,
            content: serde_json::Value::Null,
        },
        diesel_models::EventMetadata::Mandate {
            payment_method_id,
            mandate_id,
        } => OutgoingWebhookEventContent::Mandate {
            payment_method_id,
            mandate_id,
            content: serde_json::Value::Null,
        },
    })
}


impl api::PaymentSession for Nomupay {}

impl api::PaymentSession for for Nomupay


/// Construct of `FormattingLayer with implicit default entries.
    pub fn new_with_implicit_entries(
        service: &str,
        dst_writer: W,
        default_fields: HashMap<String, Value>,
        formatter: F,
    ) -> error_stack::Result<Self, ConfigError> {
        let pid = std::process::id();
        let hostname = gethostname::gethostname().to_string_lossy().into_owned();
        let service = service.to_string();
        #[cfg(feature = "vergen")]
        let version = crate::version!().to_string();
        #[cfg(feature = "vergen")]
        let build = crate::build!().to_string();
        let env = crate::env::which().to_string();
        for key in default_fields.keys() {
            if IMPLICIT_KEYS.contains(key.as_str()) {
                return Err(ConfigError::Message(format!(
                    "A reserved key `{key}` was included in `default_fields` in the log formatting layer"
                ))
                .into());
            }
        }

        Ok(Self {
            dst_writer,
            pid,
            hostname,
            env,
            service,
            #[cfg(feature = "vergen")]
            version,
            #[cfg(feature = "vergen")]
            build,
            default_fields,
            formatter,
        })
    }


pub struct AuthenticationSyncPostUpdateRequest {
    /// Authentication ID for the authentication
    #[serde(skip_deserializing)]
    pub authentication_id: id_type::AuthenticationId,
}


pub struct InespayRSyncResponseData {
    cod_status: InespayRSyncStatus,
    status_desc: String,
    refund_id: String,
}


impl Dlocal {
    pub fn new() -> &'static Self {
        &Self {
            amount_convertor: &MinorUnitForConnector,
        }
    }
}


pub fn get_payment_method_billing(&self) -> Option<&Address> {
        self.unified_payment_method_billing.as_ref()
    }


- **api_models:** Follow naming convention for wallets & paylater payment method data enums ([#1338](https://github.com/juspay/hyperswitch/pull/1338)) ([`6c0d136`](https://github.com/juspay/hyperswitch/commit/6c0d136cee106fc25fbcf63e4bbc01b28baa1519))
- **auth_type:** Updated auth type in `update tracker` and also changed the default flow to `non-3ds` from `3ds` ([#1424](https://github.com/juspay/hyperswitch/pull/1424)) ([`1616051`](https://github.com/juspay/hyperswitch/commit/1616051145c1e276fdd7d0f85cda76baaeaa0023))
- **compatibility:** Map connector to routing in payments request for backward compatibility ([#1339](https://github.com/juspay/hyperswitch/pull/1339)) ([`166688a`](https://github.com/juspay/hyperswitch/commit/166688a5906a2fcbb034c40a113452f6dc2e7160))
- **compatibility, connector:** Add holder name and change trust pay merchant_ref id to payment_id ([`d091549`](https://github.com/juspay/hyperswitch/commit/d091549576676c87f855e06678544704339d82e4))
- **configs:** Make kms module and KmsDecrypt pub ([#1274](https://github.com/juspay/hyperswitch/pull/1274)) ([`f0db993`](https://github.com/juspay/hyperswitch/commit/f0db9937c7b33858a1ff3e17eaecba094ca4c18c))
- **connector:**
  - Update error handling for Nexinets, Cybersource ([#1151](https://github.com/juspay/hyperswitch/pull/1151)) ([`2ede8ad`](https://github.com/juspay/hyperswitch/commit/2ede8ade8cff56443d8712518c64de7d952f4a0c))
  - [Zen] refactor connector_meta_data for zen connector applepay session data ([#1390](https://github.com/juspay/hyperswitch/pull/1390)) ([`0575b26`](https://github.com/juspay/hyperswitch/commit/0575b26b4fc229e92aef179146dfd561a9ee7f27))
- **connector_customer:** Incorrect mapping of connector customer ([#1275](https://github.com/juspay/hyperswitch/pull/1275)) ([`ebdfde7`](https://github.com/juspay/hyperswitch/commit/ebdfde75ecc1c39720396ad7c18062f5c108b8d3))
- **core:**
  - Generate response hash key if not specified in create merchant account request ([#1232](https://github.com/juspay/hyperswitch/pull/1232)) ([`7b74cab`](https://github.com/juspay/hyperswitch/commit/7b74cab385db68e510d2d513083a725a4f945ae3))
  - Add 'redirect_response' field to CompleteAuthorizeData ([#1222](https://github.com/juspay/hyperswitch/pull/1222)) ([`77e60c8`](https://github.com/juspay/hyperswitch/commit/77e60c82fa123ef780485a8507ce779f2f41e166))
  - Use HMAC-SHA512 to calculate payments response hash ([#1302](https://github.com/juspay/hyperswitch/pull/1302)) ([`7032ea8`](https://github.com/juspay/hyperswitch/commit/7032ea849416cb740c892360d21e436d2675fbe4))
  - Accept customer data in customer object ([#1447](https://github.com/juspay/hyperswitch/pull/1447)) ([`cff1ce6`](https://github.com/juspay/hyperswitch/commit/cff1ce61f0347665d18040486cfbbcd93139950b))
  - Move update trackers after build request ([#1472](https://github.com/juspay/hyperswitch/pull/1472)) ([`6114fb6`](https://github.com/juspay/hyperswitch/commit/6114fb634063a9a6d732af38e2a9e343d940a15e))
  - Update trackers for preprocessing steps ([#1481](https://github.com/juspay/hyperswitch/pull/1481)) ([`8fffc16`](https://github.com/juspay/hyperswitch/commit/8fffc161ea909fb29a81090f97ee9f811431d539))
- **disputes:** Resolve incorrect 5xx error mappings for disputes ([#1360](https://github.com/juspay/hyperswitch/pull/1360)) ([`c9b400e`](https://github.com/juspay/hyperswitch/commit/c9b400e186731b7de6073fece662fd0fcbbfc953))
- **errors:**
  - Remove RedisErrorExt ([#1389](https://github.com/juspay/hyperswitch/pull/1389)) ([`5d51505`](https://github.com/juspay/hyperswitch/commit/5d515050cf77705e3bf8c4b83f81ee51a8bff052))
  - Refactor `actix_web::ResponseError` for `ApiErrorResponse` ([#1362](https://github.com/juspay/hyperswitch/pull/1362)) ([`02a3ce7`](https://github.com/juspay/hyperswitch/commit/02a3ce74b84e86b0e17f8809c9b7651998a1c864))
- **fix:**
  - [Stripe] Fix bug in Stripe ([#1412](https://github.com/juspay/hyperswitch/pull/1412)) ([`e48202e`](https://github.com/juspay/hyperswitch/commit/e48202e0a06fa4d61a2637f57830ffa4aae1335d))
  - [Adyen] Fix bug in Adyen ([#1375](https://github.com/juspay/hyperswitch/pull/1375)) ([`d3a6906`](https://github.com/juspay/hyperswitch/commit/d3a69060b4db24fbbfc5c03934684dd8bfd45711))
- **mca:** Use separate struct for connector metadata ([#1465](https://github.com/juspay/hyperswitch/pull/1465)) ([`8d20578`](https://github.com/juspay/hyperswitch/commit/8d2057844ef4a29474d266d814c8ee01cc557961))
- **payments:**
  - Attempt to address unintended 5xx and 4xx in payments ([#1376](https://github.com/juspay/hyperswitch/pull/1376)) ([`cf64862`](https://github.com/juspay/hyperswitch/commit/cf64862daca0ad05b7af27646430d12bac71a5ee))
  - Add udf field and remove refactor metadata ([#1466](https://github.com/juspay/hyperswitch/pull/1466)) ([`6419953`](https://github.com/juspay/hyperswitch/commit/641995371db4deba13dc246179d726ed390b6b3e))
- **process_tracker:** Attempt to identify unintended 5xx in process_tracker ([#1359](https://github.com/juspay/hyperswitch/pull/1359)) ([`d8adf4c`](https://github.com/juspay/hyperswitch/commit/d8adf4c2b542a5cdd7888b956b085a69bd900920))
- **router:**
  - Router_parameters field inclusion ([#1251](https://github.com/juspay/hyperswitch/pull/1251)) ([`16cd325`](https://github.com/juspay/hyperswitch/commit/16cd32513bc6528e064058907a8c3c848fdba132))
  - Remove `pii-encryption-script` feature and use of timestamps for decryption ([#1350](https://github.com/juspay/hyperswitch/pull/1350)) ([`9f2832f`](https://github.com/juspay/hyperswitch/commit/9f2832f60078b98e6faae34b05b63d2dab6b7969))
  - Infer ip address for online mandates from request headers if absent ([#1419](https://github.com/juspay/hyperswitch/pull/1419)) ([`a1a009d`](https://github.com/juspay/hyperswitch/commit/a1a009d7966d2354d12bba86fbc59c1b853e14a1))
  - Send 200 response for 5xx status codes received from connector ([#1440](https://github.com/juspay/hyperswitch/pull/1440)) ([`1e5d2a2`](https://github.com/juspay/hyperswitch/commit/1e5d2a28f6592106a5924044fc8d6fc49ab20acf))
- **webhook:** Added the unknown field to the webhook_event_status of every connector ([#1343](https://github.com/juspay/hyperswitch/pull/1343)) ([`65d4a95`](https://github.com/juspay/hyperswitch/commit/65d4a95b59ee950ba67ce5b38688a650c5131149))
- Make NextAction as enum ([#1234](https://github.com/juspay/hyperswitch/pull/1234)) ([`a359b76`](https://github.com/juspay/hyperswitch/commit/a359b76d09ffc581d5808e3750dac7326c389876))
- Make bank names optional in payment method data ([#1483](https://github.com/juspay/hyperswitch/pull/1483)) ([`8198559`](https://github.com/juspay/hyperswitch/commit/8198559966313ab147161eb72c07a230ecebb70c))

### Testing

- **connector:**
  - [Stripe] Fix redirection UI tests ([#1215](https://github.com/juspay/hyperswitch/pull/1215)) ([`ea6bce6`](https://github.com/juspay/hyperswitch/commit/ea6bce663dcb084b5990834cb922eec5c626e897))
  - [Globalpay] Fix unit tests ([#1217](https://github.com/juspay/hyperswitch/pull/1217)) ([`71c0d4c`](https://github.com/juspay/hyperswitch/commit/71c0d4c500f7daca7b00f737d714f2d98cc91513))
- **postman-collection:** Add Github action to run postman collection ([#1272](https://github.com/juspay/hyperswitch/pull/1272)) ([`92c7767`](https://github.com/juspay/hyperswitch/commit/92c776714f63d02055fc46b5b750cee71328f5eb))
- **selenium:** Read config from `CONNECTOR_AUTH_FILE_PATH` environment variable and fix bugs in UI tests ([#1225](https://github.com/juspay/hyperswitch/pull/1225)) ([`d9a16ed`](https://github.com/juspay/hyperswitch/commit/d9a16ed5abdafa6d48bf30a6ba8c3783bed3dff5))

### Documentation

- **CONTRIBUTING:** Update commit guidelines ([#1351](https://github.com/juspay/hyperswitch/pull/1351)) ([`5d8895c`](https://github.com/juspay/hyperswitch/commit/5d8895c06412ff05d5abbfefaaa4933db853eb13))
- Add changelog to 0.5.15 ([#1216](https://github.com/juspay/hyperswitch/pull/1216)) ([`0be900d`](https://github.com/juspay/hyperswitch/commit/0be900d2388ad732a40b788223bd48aee9b3aa95))
- Add `ApplePayRedirectionData` to OpenAPI schema ([#1386](https://github.com/juspay/hyperswitch/pull/1386)) ([`d0d3254`](https://github.com/juspay/hyperswitch/commit/d0d32544c23481a1acd91182055a7a0afb78d723))

### Miscellaneous Tasks

- **common_utils:** Apply the new type pattern for phone numbers ([#1286](https://github.com/juspay/hyperswitch/pull/1286)) ([`98e73e2`](https://github.com/juspay/hyperswitch/commit/98e73e2e90b4c79d0cc6cf8682693c1e5aad50a3))
- **config:**
  - Add bank config for online_banking_poland, online_banking_slovakia ([#1220](https://github.com/juspay/hyperswitch/pull/1220)) ([`ee5466a`](https://github.com/juspay/hyperswitch/commit/ee5466a3b04f69c92dc5d04faca80d1f04275a9c))
  - Add bank config for przelewy24 ([#1460](https://github.com/juspay/hyperswitch/pull/1460)) ([`3ee97cd`](https://github.com/juspay/hyperswitch/commit/3ee97cda552e0745b8d75daad2e300288673a4d7))
- **migrations:** Shrink `merchant_id` column of `merchant_key_store` to 64 characters ([#1476](https://github.com/juspay/hyperswitch/pull/1476)) ([`0fdd6ec`](https://github.com/juspay/hyperswitch/commit/0fdd6ecd4ac4bc5e1fc11e5cf79292c99eae71c1))
- Address Rust 1.70 clippy lints ([#1334](https://github.com/juspay/hyperswitch/pull/1334)) ([`b681f78`](https://github.com/juspay/hyperswitch/commit/b681f78d964d02f80249751cc6fd12e3c85bc4d7))

### Build System / Dependencies

- **deps:**
  - Bump `diesel` from `2.0.3` to `2.1.0` ([#1287](https://github.com/juspay/hyperswitch/pull/1287)) ([`b9ec38a`](https://github.com/juspay/hyperswitch/commit/b9ec38a1b54abbaa90bbc967aa8cdd450f149947))
  - Update dependencies ([#1342](https://github.com/juspay/hyperswitch/pull/1342)) ([`bce01ce`](https://github.com/juspay/hyperswitch/commit/bce01ced11e3869699d454827dc659fc82941951))
- **docker:** Use `debian:bookworm-slim` as base image for builder and runner stages ([#1473](https://github.com/juspay/hyperswitch/pull/1473)) ([`5eb0333`](https://github.com/juspay/hyperswitch/commit/5eb033336321b5deb197f4416c8409abf99a8421))
- Unify `sandbox` and `production` cargo features as `release` ([#1356](https://github.com/juspay/hyperswitch/pull/1356)) ([`695d3cd`](https://github.com/juspay/hyperswitch/commit/695d3cdac27448806fcde8cbb9cdc6ba4e7cbe7e))

**Full Changelog:** [`v0.5.15...v0.6.0`](https://github.com/juspay/hyperswitch/compare/v0.5.15...v0.6.0)

- - -

## 0.5.15 (2023-05-19)

### Features

- **connector:**
  - [Bluesnap] Add support for ApplePay ([#1178](https://github.com/juspay/hyperswitch/pull/1178)) ([`919c03e`](https://github.com/juspay/hyperswitch/commit/919c03e679c4ebbb138509da52a18bface7ba319))
  - Add Interac as Payment Method Type ([#1205](https://github.com/juspay/hyperswitch/pull/1205)) ([`afceda5`](https://github.com/juspay/hyperswitch/commit/afceda55ad9741909e21a3c3956d78b5ba858746))
  - [Authorizedotnet] implement Capture flow and webhooks for Authorizedotnet ([#1171](https://github.com/juspay/hyperswitch/pull/1171)) ([`2d49ce5`](https://github.com/juspay/hyperswitch/commit/2d49ce56de5ed314aa099f3ce4aa569b3e22b561))
- **db:** Implement `AddressInterface` for `MockDb` ([#968](https://github.com/juspay/hyperswitch/pull/968)) ([`39405bb`](https://github.com/juspay/hyperswitch/commit/39405bb4788bf88d6c8c166281fffc238a589aaa))
- **documentation:** Add polymorphic `generate_schema` macro ([#1183](https://github.com/juspay/hyperswitch/pull/1183)) ([`53aa5ac`](https://github.com/juspay/hyperswitch/commit/53aa5ac92d0692b753624a4254040f8452def1d2))
- **email:** Integrate email service using AWS SES ([#1158](https://github.com/juspay/hyperswitch/pull/1158)) ([`07e0fcb`](https://github.com/juspay/hyperswitch/commit/07e0fcbe06107e8be532b4e9a1e1a1ef6efba68e))
- **frm_routing_algorithm:** Added frm_routing_algorithm to merchant_account table, to be consumed for frm selection ([#1161](https://github.com/juspay/hyperswitch/pull/1161)) ([`ea98145`](https://github.com/juspay/hyperswitch/commit/ea9814531880584435c122b3e32e9883e4518fd2))
- **payments:** Add support for manual retries in payments confirm call ([#1170](https://github.com/juspay/hyperswitch/pull/1170)) ([`1f52a66`](https://github.com/juspay/hyperswitch/commit/1f52a66452042deb0e3959e839a726f261cce880))
- **redis_interface:** Implement `MGET` command ([#1206](https://github.com/juspay/hyperswitch/pull/1206)) ([`93dcd98`](https://github.com/juspay/hyperswitch/commit/93dcd98640a31e41f0d66d2ece2396e536adefae))
- **router:**
  - Implement `ApiKeyInterface` for `MockDb` ([#1101](https://github.com/juspay/hyperswitch/pull/1101)) ([`95c7ca9`](https://github.com/juspay/hyperswitch/commit/95c7ca99d1b5009f4cc8664825c5e63a165006c7))
  - Add mandates list api ([#1143](https://github.com/juspay/hyperswitch/pull/1143)) ([`commit`](https://github.com/juspay/hyperswitch/commit/75ba3ff09f71d1dd295f9dad0060d2620d7b3764))
- **traces:** Add support for aws xray ([#1194](https://github.com/juspay/hyperswitch/pull/1194)) ([`8947e1c`](https://github.com/juspay/hyperswitch/commit/8947e1c9dba3585c3d998110b53747cbc1007bc2))
- ACH transfers ([#905](https://github.com/juspay/hyperswitch/pull/905)) ([`23bca66`](https://github.com/juspay/hyperswitch/commit/23bca66b810993895e4054cc4bf3fdcac6b2ed4c))
- SEPA and BACS bank transfers through stripe ([#930](https://github.com/juspay/hyperswitch/pull/930)) ([`cf00059`](https://github.com/juspay/hyperswitch/commit/cf000599ddaca2646efce0493a013c06fcdf34b8))

### Bug Fixes

- **connector:** [Checkout] Fix incoming webhook event mapping ([#1197](https://github.com/juspay/hyperswitch/pull/1197)) ([`912a108`](https://github.com/juspay/hyperswitch/commit/912a1084846b6dc8e843e852a3b664a4faaf9f00))
- **core:** Add ephemeral key to payment_create response when customer_id is mentioned ([#1133](https://github.com/juspay/hyperswitch/pull/1133)) ([`f394c4a`](https://github.com/juspay/hyperswitch/commit/f394c4abc071b314798943024ba22d653a6a056e))
- **mandate:** Throw DuplicateMandate Error if mandate insert fails ([#1201](https://github.com/juspay/hyperswitch/pull/1201)) ([`186bd72`](https://github.com/juspay/hyperswitch/commit/186bd729d672290e0f49eac0cebb3dcb8948f992))
- **merchant_connector_account:** Add validation for the `disabled` flag ([#1141](https://github.com/juspay/hyperswitch/pull/1141)) ([`600dc33`](https://github.com/juspay/hyperswitch/commit/600dc338673c593c3cbd3ad8dfebe17d4f5c0326))
- **router:**
  - Aggregate critical hotfixes for v0.5.10 ([#1162](https://github.com/juspay/hyperswitch/pull/1162)) ([`ed22b2a`](https://github.com/juspay/hyperswitch/commit/ed22b2af763425d4e71cccd8da158e5e95722fed))
  - Outgoing webhook api call ([#1193](https://github.com/juspay/hyperswitch/pull/1193)) ([`31a52d8`](https://github.com/juspay/hyperswitch/commit/31a52d8058dbee38cd77de20b7cae7c5d6fb23bf))
  - Add dummy connector url to proxy bypass ([#1186](https://github.com/juspay/hyperswitch/pull/1186)) ([`bc5497f`](https://github.com/juspay/hyperswitch/commit/bc5497f03ab7fde585e7c57815f55cf7b4b8d475))
  - Aggregate hotfixes for v0.5.10 ([#1204](https://github.com/juspay/hyperswitch/pull/1204)) ([`9cc1cee`](https://github.com/juspay/hyperswitch/commit/9cc1ceec6986f5696030d95e6899730807637cd9))
- **utils:** Fix bug in email validation ([#1180](https://github.com/juspay/hyperswitch/pull/1180)) ([`5e51b6b`](https://github.com/juspay/hyperswitch/commit/5e51b6b16db6830dd5051b43fbd7d43532d9f195))
- Fix(connector) : Added signifyd to routableconnectors for frm ([#1182](https://github.com/juspay/hyperswitch/pull/1182)) ([`2ce5d5f`](https://github.com/juspay/hyperswitch/commit/2ce5d5ffe4e37de29749fc97b13d2faaec8fcee0))
- Handle unique constraint violation error gracefully ([#1202](https://github.com/juspay/hyperswitch/pull/1202)) ([`b3fd174`](https://github.com/juspay/hyperswitch/commit/b3fd174d04cdd4b26328d36c4f886e6ef4df830d))

### Refactors

- **mandate:** Allow merchant to pass the mandate details and customer acceptance separately ([#1188](https://github.com/juspay/hyperswitch/pull/1188)) ([`6c41cdb`](https://github.com/juspay/hyperswitch/commit/6c41cdb1c942d3152c73a44b62dd9a02587f6bd8))
- Use `strum::EnumString` implementation for connector name conversions ([#1052](https://github.com/juspay/hyperswitch/pull/1052)) ([`2809425`](https://github.com/juspay/hyperswitch/commit/28094251546b6067a44df8ae906d9cd04f85e84e))

### Documentation

- Add changelog for v0.5.14 ([#1177](https://github.com/juspay/hyperswitch/pull/1177)) ([`236124d`](https://github.com/juspay/hyperswitch/commit/236124d1993c2a7d52e30441761a3558ad02c973))

### Miscellaneous Tasks

- **CODEOWNERS:** Add hyperswitch-maintainers as default owners for all files ([#1210](https://github.com/juspay/hyperswitch/pull/1210)) ([`985670d`](https://github.com/juspay/hyperswitch/commit/985670da9c90cbc904162d7863c9c508f5cf5e19))
- **git-cliff:** Simplify `git-cliff` config files ([#1213](https://github.com/juspay/hyperswitch/pull/1213)) ([`bd0069e`](https://github.com/juspay/hyperswitch/commit/bd0069e2a8bd3c3389c92590c688ce945cd7ebec))

### Revert

- **connector:** Fix stripe status to attempt status map ([#1179](https://github.com/juspay/hyperswitch/pull/1179)) ([`bd8868e`](https://github.com/juspay/hyperswitch/commit/bd8868efd00748cf64c46519c4ed7ba04ad06d5e))
- Fix(connector): Added signifyd to routableconnectors for frm ([#1203](https://github.com/juspay/hyperswitch/pull/1203)) ([`dbc5bc5`](https://github.com/juspay/hyperswitch/commit/dbc5bc538a218ae287e96c44de0223c26c1583f0))

- - -

## 0.5.14 (2023-05-16)

### Features

- **connector:**
  - [Stripe] implement Bancontact Bank Redirect for stripe ([#1169](https://github.com/juspay/hyperswitch/pull/1169)) ([`5b22e96`](https://github.com/juspay/hyperswitch/commit/5b22e967981b604be6070f5b373555756a5c62f7))
  - [Noon] Add script generated template code ([#1164](https://github.com/juspay/hyperswitch/pull/1164)) ([`bfaf75f`](https://github.com/juspay/hyperswitch/commit/bfaf75fca38e535ceb3ea4327e252d807fb61892))
  - [Adyen] implement BACS Direct Debits for Adyen ([#1159](https://github.com/juspay/hyperswitch/pull/1159)) ([`9f47f20`](https://github.com/juspay/hyperswitch/commit/9f47f2070216eb8c64db14eae555073a507cc634))


impl api::ConnectorAccessToken for Powertranz {}

impl api::ConnectorAccessToken for for Powertranz


pub fn get_name(&self) -> Secret<String> {
        self.0.name.clone()
    }


impl ConnectorValidation for Fiuu {
    fn validate_mandate_payment(
        &self,
        pm_type: Option<PaymentMethodType>,
        pm_data: PaymentMethodData,
    ) -> CustomResult<(), errors::ConnectorError> {
        let mandate_supported_pmd: HashSet<PaymentMethodDataType> =
            HashSet::from([PaymentMethodDataType::Card]);
        utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id())
    }
}


pub fn new() -> &'static Self {
        &Self {
            amount_converter: &StringMajorUnitForConnector,
        }
    }


pub struct PayLaterRequest {
    flow: Flow,
    payment_method: PaymentMethod,
}


pub async fn find_by_algorithm_id_merchant_id(
        conn: &PgPooledConn,
        algorithm_id: &common_utils::id_type::RoutingId,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::algorithm_id
                .eq(algorithm_id.to_owned())
                .and(dsl::merchant_id.eq(merchant_id.to_owned())),
        )
        .await
    }


pub async fn get_individual_surcharge_detail_from_redis(
        state: &SessionState,
        surcharge_key: SurchargeKey,
        payment_attempt_id: &str,
    ) -> CustomResult<SurchargeDetails, RedisError> {
        let redis_conn = state
            .store
            .get_redis_conn()
            .attach_printable("Failed to get redis connection")?;
        let redis_key = Self::get_surcharge_metadata_redis_key(payment_attempt_id);
        let value_key = Self::get_surcharge_details_redis_hashset_key(&surcharge_key);
        let result = redis_conn
            .get_hash_field_and_deserialize(
                &redis_key.as_str().into(),
                &value_key,
                "SurchargeDetails",
            )
            .await;
        logger::debug!(
            "Surcharge result fetched from redis with key = {} and {}",
            redis_key,
            value_key
        );
        result
    }


pub struct NovalnetRefundSyncResponse {
    result: ResultData,
    transaction: Option<NovalnetSyncResponseTransactionData>,
}


pub struct MifinityConnectorMetadataObject {
    pub brand_id: Secret<String>,
    pub destination_account_number: Secret<String>,
}


impl api::PaymentSession for Vgs {}


pub async fn sync_refund_with_gateway_workflow(
    state: &SessionState,
    refund_tracker: &storage::ProcessTracker,
) -> Result<(), errors::ProcessTrackerError> {
    let key_manager_state = &state.into();
    let refund_core = serde_json::from_value::<diesel_refund::RefundCoreWorkflow>(
        refund_tracker.tracking_data.clone(),
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable_lazy(|| {
        format!(
            "unable to convert into refund_core {:?}",
            refund_tracker.tracking_data
        )
    })?;

    let key_store = state
        .store
        .get_merchant_key_store_by_merchant_id(
            key_manager_state,
            &refund_core.merchant_id,
            &state.store.get_master_key().to_vec().into(),
        )
        .await?;

    let merchant_account = state
        .store
        .find_merchant_account_by_merchant_id(
            key_manager_state,
            &refund_core.merchant_id,
            &key_store,
        )
        .await?;
    let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(domain::Context(
        merchant_account.clone(),
        key_store.clone(),
    )));
    let response = Box::pin(refund_retrieve_core_with_internal_reference_id(
        state.clone(),
        merchant_context,
        None,
        refund_core.refund_internal_reference_id,
        Some(true),
    ))
    .await?;
    let terminal_status = [
        enums::RefundStatus::Success,
        enums::RefundStatus::Failure,
        enums::RefundStatus::TransactionFailure,
    ];
    match response.refund_status {
        status if terminal_status.contains(&status) => {
            state
                .store
                .as_scheduler()
                .finish_process_with_business_status(
                    refund_tracker.clone(),
                    business_status::COMPLETED_BY_PT,
                )
                .await?
        }
        _ => {
            _ = retry_refund_sync_task(
                &*state.store,
                response.connector,
                response.merchant_id,
                refund_tracker.to_owned(),
            )
            .await?;
        }
    }

    Ok(())
}


pub fn update_storage_scheme(&mut self, storage_scheme: MerchantStorageScheme) {
        self.updated_by = Some(storage_scheme.to_string());
    }


impl UserRoleNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<UserRole> {
        generics::generic_insert(conn, self).await
    }
}


impl Capturable for PaymentsSessionData {}

impl Capturable for for PaymentsSessionData


pub async fn delete_theme(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<String>,
) -> HttpResponse {
    let flow = Flow::DeleteTheme;
    let theme_id = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        theme_id,
        |state, _, theme_id, _| theme_core::delete_theme(state, theme_id),
        &auth::AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub struct StripeBillingDetails {
    pub address: Option<payments::AddressDetails>,
    pub email: Option<pii::Email>,
    pub name: Option<String>,
    pub phone: Option<pii::Secret<String>>,
}


pub struct PaymeRedirectResponseData {
    meta_data: String,
}


pub async fn customers_retrieve() {}


pub fn update_algorithm_id(&mut self, new_id: common_utils::id_type::RoutingId) {
        self.algorithm_id = Some(new_id);
        self.timestamp = common_utils::date_time::now_unix_timestamp();
    }