        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;

        let auth = AuthenticationData {
            merchant_account: merchant,
            platform_merchant_account: None,
            key_store,
            profile_id: Some(payload.profile_id),
        };
        Ok((
            auth.clone(),
            AuthenticationType::MerchantJwt {
                merchant_id: auth.merchant_account.get_id().clone(),
                user_id: Some(payload.user_id),
            },
        ))
    }
}

#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for JWTAuthMerchantFromRoute
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        let profile_id =
            get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)?
                .get_required_value(headers::X_PROFILE_ID)?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }

        if payload.merchant_id != self.merchant_id {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;
        let profile = state
            .store()
            .find_business_profile_by_merchant_id_profile_id(
                key_manager_state,
                &key_store,
                &payload.merchant_id,
                &profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;

        let auth = AuthenticationData {
            merchant_account: merchant,
            key_store,
            profile,
            platform_merchant_account: None,
        };
        Ok((
            auth.clone(),
            AuthenticationType::MerchantJwt {
                merchant_id: auth.merchant_account.get_id().clone(),
                user_id: Some(payload.user_id),
            },
        ))
    }
}

#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationDataWithoutProfile, A> for JWTAuthMerchantFromRoute
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationDataWithoutProfile, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;

        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }

        if payload.merchant_id != self.merchant_id {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;

        let auth = AuthenticationDataWithoutProfile {
            merchant_account: merchant,
            key_store,
        };
        Ok((
            auth.clone(),
            AuthenticationType::MerchantJwt {
                merchant_id: auth.merchant_account.get_id().clone(),
                user_id: Some(payload.user_id),
            },
        ))
    }
}

pub struct JWTAuthMerchantAndProfileFromRoute {
    pub merchant_id: id_type::MerchantId,
    pub profile_id: id_type::ProfileId,
    pub required_permission: Permission,
}

#[cfg(feature = "v1")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for JWTAuthMerchantAndProfileFromRoute
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        if payload.merchant_id != self.merchant_id {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }

        if payload.profile_id != self.profile_id {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;

        let auth = AuthenticationData {
            merchant_account: merchant,
            platform_merchant_account: None,
            key_store,
            profile_id: Some(payload.profile_id),
        };
        Ok((
            auth.clone(),
            AuthenticationType::MerchantJwtWithProfileId {
                merchant_id: auth.merchant_account.get_id().clone(),
                profile_id: auth.profile_id.clone(),
                user_id: payload.user_id,
            },
        ))
    }
}

pub struct JWTAuthProfileFromRoute {
    pub profile_id: id_type::ProfileId,
    pub required_permission: Permission,
}

#[cfg(feature = "v1")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for JWTAuthProfileFromRoute
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;

        if payload.profile_id != self.profile_id {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        } else {
            // if both of them are same then proceed with the profile id present in the request
            let auth = AuthenticationData {
                merchant_account: merchant,
                platform_merchant_account: None,
                key_store,
                profile_id: Some(self.profile_id.clone()),
            };
            Ok((
                auth.clone(),
                AuthenticationType::MerchantJwt {
                    merchant_id: auth.merchant_account.get_id().clone(),
                    user_id: Some(payload.user_id),
                },
            ))
        }
    }
}

#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for JWTAuthProfileFromRoute
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        let profile_id =
            get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)?
                .get_required_value(headers::X_PROFILE_ID)?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let profile = state
            .store()
            .find_business_profile_by_merchant_id_profile_id(
                key_manager_state,
                &key_store,
                &payload.merchant_id,
                &profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;

        let auth = AuthenticationData {
            merchant_account: merchant,
            key_store,
            profile,
            platform_merchant_account: None,
        };
        Ok((
            auth.clone(),
            AuthenticationType::MerchantJwt {
                merchant_id: auth.merchant_account.get_id().clone(),
                user_id: Some(payload.user_id),
            },
        ))
    }
}

pub async fn parse_jwt_payload<A, T>(headers: &HeaderMap, state: &A) -> RouterResult<T>
where
    T: serde::de::DeserializeOwned,
    A: SessionStateInfo + Sync,
{
    let cookie_token_result =
        get_cookie_from_header(headers).and_then(cookies::get_jwt_from_cookies);
    let auth_header_token_result = get_jwt_from_authorization_header(headers);
    let force_cookie = state.conf().user.force_cookies;

    logger::info!(
        user_agent = ?headers.get(headers::USER_AGENT),
        header_names = ?headers.keys().collect::<Vec<_>>(),
        is_token_equal =
            auth_header_token_result.as_deref().ok() == cookie_token_result.as_deref().ok(),
        cookie_error = ?cookie_token_result.as_ref().err(),
        token_error = ?auth_header_token_result.as_ref().err(),
        force_cookie,
    );

    let final_token = if force_cookie {
        cookie_token_result?
    } else {
        auth_header_token_result?.to_owned()
    };

    decode_jwt(&final_token, state).await
}

#[cfg(feature = "v1")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for JWTAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.permission, &role_info)?;

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;
        let merchant_id = merchant.get_id().clone();
        let auth = AuthenticationData {
            merchant_account: merchant,
            platform_merchant_account: None,
            key_store,
            profile_id: Some(payload.profile_id),
        };
        Ok((
            auth,
            AuthenticationType::MerchantJwt {
                merchant_id,
                user_id: Some(payload.user_id),
            },
        ))
    }
}

#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for JWTAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        let profile_id = HeaderMapStruct::new(request_headers)
            .get_id_type_from_header::<id_type::ProfileId>(headers::X_PROFILE_ID)?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.permission, &role_info)?;

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let profile = state
            .store()
            .find_business_profile_by_merchant_id_profile_id(
                key_manager_state,
                &key_store,
                &payload.merchant_id,
                &profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;
        let merchant_id = merchant.get_id().clone();
        let auth = AuthenticationData {
            merchant_account: merchant,
            key_store,
            profile,
            platform_merchant_account: None,
        };
        Ok((
            auth,
            AuthenticationType::MerchantJwt {
                merchant_id,
                user_id: Some(payload.user_id),
            },
        ))
    }
}

pub type AuthenticationDataWithUserId = (AuthenticationData, String);

#[cfg(feature = "v1")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationDataWithUserId, A> for JWTAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationDataWithUserId, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.permission, &role_info)?;

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .change_context(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .change_context(errors::ApiErrorResponse::InvalidJwtToken)?;

        let auth = AuthenticationData {
            merchant_account: merchant,
            platform_merchant_account: None,
            key_store,
            profile_id: Some(payload.profile_id),
        };
        Ok((
            (auth.clone(), payload.user_id.clone()),
            AuthenticationType::MerchantJwt {
                merchant_id: auth.merchant_account.get_id().clone(),
                user_id: None,
            },
        ))
    }
}

pub struct DashboardNoPermissionAuth;

#[cfg(feature = "olap")]
#[async_trait]
impl<A> AuthenticateAndFetch<UserFromToken, A> for DashboardNoPermissionAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(UserFromToken, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }

        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        Ok((
            UserFromToken {
                user_id: payload.user_id.clone(),
                merchant_id: payload.merchant_id.clone(),
                org_id: payload.org_id,
                role_id: payload.role_id,
                profile_id: payload.profile_id,
                tenant_id: payload.tenant_id,
            },
            AuthenticationType::MerchantJwt {
                merchant_id: payload.merchant_id,
                user_id: Some(payload.user_id),
            },
        ))
    }
}

#[cfg(feature = "olap")]
#[async_trait]
impl<A> AuthenticateAndFetch<(), A> for DashboardNoPermissionAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<((), AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        Ok(((), AuthenticationType::NoAuth))
    }
}

#[cfg(feature = "v1")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for DashboardNoPermissionAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .change_context(errors::ApiErrorResponse::Unauthorized)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;

        let auth = AuthenticationData {
            merchant_account: merchant,
            platform_merchant_account: None,
            key_store,
            profile_id: Some(payload.profile_id),
        };
        Ok((
            auth.clone(),
            AuthenticationType::MerchantJwt {
                merchant_id: auth.merchant_account.get_id().clone(),
                user_id: Some(payload.user_id),
            },
        ))
    }
}

pub trait ClientSecretFetch {
    fn get_client_secret(&self) -> Option<&String>;
}
#[cfg(feature = "payouts")]
impl ClientSecretFetch for payouts::PayoutCreateRequest {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret.as_ref()
    }
}

#[cfg(feature = "v1")]
impl ClientSecretFetch for payments::PaymentsRequest {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret.as_ref()
    }
}

#[cfg(feature = "v1")]
impl ClientSecretFetch for payments::PaymentsRetrieveRequest {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret.as_ref()
    }
}

#[cfg(feature = "v1")]
impl ClientSecretFetch for PaymentMethodListRequest {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret.as_ref()
    }
}

impl ClientSecretFetch for payments::PaymentsPostSessionTokensRequest {
    fn get_client_secret(&self) -> Option<&String> {
        Some(self.client_secret.peek())
    }
}

#[cfg(feature = "v1")]
impl ClientSecretFetch for PaymentMethodCreate {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret.as_ref()
    }
}

impl ClientSecretFetch for api_models::cards_info::CardsInfoRequest {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret.as_ref()
    }
}

impl ClientSecretFetch for payments::RetrievePaymentLinkRequest {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret.as_ref()
    }
}

impl ClientSecretFetch for api_models::pm_auth::LinkTokenCreateRequest {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret.as_ref()
    }
}

impl ClientSecretFetch for api_models::pm_auth::ExchangeTokenCreateRequest {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret.as_ref()
    }
}

#[cfg(feature = "v1")]
impl ClientSecretFetch for api_models::payment_methods::PaymentMethodUpdate {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret.as_ref()
    }
}

#[cfg(feature = "v1")]
impl ClientSecretFetch for api_models::authentication::AuthenticationEligibilityRequest {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret
            .as_ref()
            .map(|client_secret| client_secret.peek())
    }
}

impl ClientSecretFetch for api_models::authentication::AuthenticationAuthenticateRequest {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret
            .as_ref()
            .map(|client_secret| client_secret.peek())
    }
}

impl ClientSecretFetch for api_models::authentication::AuthenticationSyncRequest {
    fn get_client_secret(&self) -> Option<&String> {
        self.client_secret
            .as_ref()
            .map(|client_secret| client_secret.peek())
    }
}

pub fn get_auth_type_and_flow<A: SessionStateInfo + Sync + Send>(
    headers: &HeaderMap,
    api_auth: ApiKeyAuth,
) -> RouterResult<(
    Box<dyn AuthenticateAndFetch<AuthenticationData, A>>,
    api::AuthFlow,
)> {
    let api_key = get_api_key(headers)?;

    if api_key.starts_with("pk_") {
        return Ok((
            Box::new(HeaderAuth(PublishableKeyAuth)),
            api::AuthFlow::Client,
        ));
    }
    Ok((Box::new(HeaderAuth(api_auth)), api::AuthFlow::Merchant))
}

pub fn check_client_secret_and_get_auth<T>(
    headers: &HeaderMap,
    payload: &impl ClientSecretFetch,
    api_auth: ApiKeyAuth,
) -> RouterResult<(
    Box<dyn AuthenticateAndFetch<AuthenticationData, T>>,
    api::AuthFlow,
)>
where
    T: SessionStateInfo + Sync + Send,
    ApiKeyAuth: AuthenticateAndFetch<AuthenticationData, T>,
    PublishableKeyAuth: AuthenticateAndFetch<AuthenticationData, T>,
{
    let api_key = get_api_key(headers)?;
    if api_key.starts_with("pk_") {
        payload
            .get_client_secret()
            .check_value_present("client_secret")
            .map_err(|_| errors::ApiErrorResponse::MissingRequiredField {
                field_name: "client_secret",
            })?;
        return Ok((
            Box::new(HeaderAuth(PublishableKeyAuth)),
            api::AuthFlow::Client,
        ));
    }

    if payload.get_client_secret().is_some() {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "client_secret is not a valid parameter".to_owned(),
        }
        .into());
    }
    Ok((Box::new(HeaderAuth(api_auth)), api::AuthFlow::Merchant))
}

pub async fn get_ephemeral_or_other_auth<T>(
    headers: &HeaderMap,
    is_merchant_flow: bool,
    payload: Option<&impl ClientSecretFetch>,
    api_auth: ApiKeyAuth,
) -> RouterResult<(
    Box<dyn AuthenticateAndFetch<AuthenticationData, T>>,
    api::AuthFlow,
    bool,
)>
where
    T: SessionStateInfo + Sync + Send,
    ApiKeyAuth: AuthenticateAndFetch<AuthenticationData, T>,
    PublishableKeyAuth: AuthenticateAndFetch<AuthenticationData, T>,
    EphemeralKeyAuth: AuthenticateAndFetch<AuthenticationData, T>,
{
    let api_key = get_api_key(headers)?;

    if api_key.starts_with("epk") {
        Ok((Box::new(EphemeralKeyAuth), api::AuthFlow::Client, true))
    } else if is_merchant_flow {
        Ok((
            Box::new(HeaderAuth(api_auth)),
            api::AuthFlow::Merchant,
            false,
        ))
    } else {
        let payload = payload.get_required_value("ClientSecretFetch")?;
        let (auth, auth_flow) = check_client_secret_and_get_auth(headers, payload, api_auth)?;
        Ok((auth, auth_flow, false))
    }
}

#[cfg(feature = "v1")]
pub fn is_ephemeral_auth<A: SessionStateInfo + Sync + Send>(
    headers: &HeaderMap,
    api_auth: ApiKeyAuth,
) -> RouterResult<Box<dyn AuthenticateAndFetch<AuthenticationData, A>>> {
    let api_key = get_api_key(headers)?;

    if !api_key.starts_with("epk") {
        Ok(Box::new(HeaderAuth(api_auth)))
    } else {
        Ok(Box::new(EphemeralKeyAuth))
    }
}

pub fn is_jwt_auth(headers: &HeaderMap) -> bool {
    let header_map_struct = HeaderMapStruct::new(headers);
    match header_map_struct.get_auth_string_from_header() {
        Ok(auth_str) => auth_str.starts_with("Bearer"),
        Err(_) => get_cookie_from_header(headers)
            .and_then(cookies::get_jwt_from_cookies)
            .is_ok(),
    }
}

pub async fn decode_jwt<T>(token: &str, state: &impl SessionStateInfo) -> RouterResult<T>
where
    T: serde::de::DeserializeOwned,
{
    let conf = state.conf();
    let secret = conf.secrets.get_inner().jwt_secret.peek().as_bytes();

    let key = DecodingKey::from_secret(secret);
    decode::<T>(token, &key, &Validation::new(Algorithm::HS256))
        .map(|decoded| decoded.claims)
        .change_context(errors::ApiErrorResponse::InvalidJwtToken)
}

pub fn get_api_key(headers: &HeaderMap) -> RouterResult<&str> {
    get_header_value_by_key("api-key".into(), headers)?.get_required_value("api_key")
}

pub fn get_header_value_by_key(key: String, headers: &HeaderMap) -> RouterResult<Option<&str>> {
    headers
        .get(&key)
        .map(|source_str| {
            source_str
                .to_str()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(format!(
                    "Failed to convert header value to string for header key: {key}",
                ))
        })
        .transpose()
}
pub fn get_id_type_by_key_from_headers<T: FromStr>(
    key: String,
    headers: &HeaderMap,
) -> RouterResult<Option<T>> {
    get_header_value_by_key(key.clone(), headers)?
        .map(|str_value| T::from_str(str_value))
        .transpose()
        .map_err(|_err| {
            error_stack::report!(errors::ApiErrorResponse::InvalidDataFormat {
                field_name: key,
                expected_format: "Valid Id String".to_string(),
            })
        })
}

pub fn get_jwt_from_authorization_header(headers: &HeaderMap) -> RouterResult<&str> {
    headers
        .get(headers::AUTHORIZATION)
        .get_required_value(headers::AUTHORIZATION)?
        .to_str()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to convert JWT token to string")?
        .strip_prefix("Bearer ")
        .ok_or(errors::ApiErrorResponse::InvalidJwtToken.into())
}

pub fn get_cookie_from_header(headers: &HeaderMap) -> RouterResult<&str> {
    let cookie = headers
        .get(cookies::get_cookie_header())
        .ok_or(report!(errors::ApiErrorResponse::CookieNotFound))?;

    cookie
        .to_str()
        .change_context(errors::ApiErrorResponse::InvalidCookie)
}

pub fn strip_jwt_token(token: &str) -> RouterResult<&str> {
    token
        .strip_prefix("Bearer ")
        .ok_or_else(|| errors::ApiErrorResponse::InvalidJwtToken.into())
}

pub fn auth_type<'a, T, A>(
    default_auth: &'a dyn AuthenticateAndFetch<T, A>,
    jwt_auth_type: &'a dyn AuthenticateAndFetch<T, A>,
    headers: &HeaderMap,
) -> &'a dyn AuthenticateAndFetch<T, A>
where
{
    if is_jwt_auth(headers) {
        return jwt_auth_type;
    }
    default_auth
}

#[cfg(feature = "recon")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationDataWithUser, A> for JWTAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationDataWithUser, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;
        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.permission, &role_info)?;

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;

        let user_id = payload.user_id;

        let user = state
            .session_state()
            .global_store
            .find_user_by_id(&user_id)
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch user for the user id")?;

        let auth = AuthenticationDataWithUser {
            merchant_account: merchant,
            key_store,
            profile_id: payload.profile_id.clone(),
            user,
        };

        let auth_type = AuthenticationType::MerchantJwt {
            merchant_id: auth.merchant_account.get_id().clone(),
            user_id: Some(user_id),
        };

        Ok((auth, auth_type))
    }
}

async fn get_connected_merchant_account<A>(
    state: &A,
    connected_merchant_id: id_type::MerchantId,
    platform_org_id: id_type::OrganizationId,
) -> RouterResult<domain::MerchantAccount>
where
    A: SessionStateInfo + Sync,
{
    let key_manager_state = &(&state.session_state()).into();
    let key_store = state
        .store()
        .get_merchant_key_store_by_merchant_id(
            key_manager_state,
            &connected_merchant_id,
            &state.store().get_master_key().to_vec().into(),
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::InvalidPlatformOperation)
        .attach_printable("Failed to fetch merchant key store for the merchant id")?;

    let connected_merchant_account = state
        .store()
        .find_merchant_account_by_merchant_id(key_manager_state, &connected_merchant_id, &key_store)
        .await
        .to_not_found_response(errors::ApiErrorResponse::InvalidPlatformOperation)
        .attach_printable("Failed to fetch merchant account for the merchant id")?;

    if platform_org_id != connected_merchant_account.organization_id {
        return Err(errors::ApiErrorResponse::InvalidPlatformOperation)
            .attach_printable("Access for merchant id Unauthorized");
    }

    Ok(connected_merchant_account)
}

async fn get_platform_merchant_account<A>(
    state: &A,
    request_headers: &HeaderMap,
    merchant_account: domain::MerchantAccount,


File: crates/hyperswitch_connectors/src/connectors/zsl/transformers.rs
 Public functions: 1
 Public structs: 7

use std::collections::HashMap;

use base64::Engine;
use common_enums::enums;
use common_utils::{crypto::GenerateDigest, date_time, pii::Email, request::Method};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{BankTransferData, PaymentMethodData},
    router_data::{ConnectorAuthType, ErrorResponse, RouterData},
    router_request_types::{PaymentsSyncData, ResponseId},
    router_response_types::{PaymentsResponseData, RedirectForm},
    types,
};
use hyperswitch_interfaces::{api, consts::NO_ERROR_CODE, errors};
use masking::{ExposeInterface, Secret};
use ring::digest;
use serde::{Deserialize, Serialize};

use crate::{
    types::ResponseRouterData,
    utils::{
        get_amount_as_string, get_unimplemented_payment_method_error_message,
        PaymentsAuthorizeRequestData, RouterData as _,
    },
};

mod auth_error {
    pub const INVALID_SIGNATURE: &str = "INVALID_SIGNATURE";
}
mod zsl_version {
    pub const VERSION_1: &str = "1";
}

pub struct ZslRouterData<T> {
    pub amount: String,
    pub router_data: T,
}

impl<T> TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for ZslRouterData<T> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (currency_unit, currency, txn_amount, item): (&api::CurrencyUnit, enums::Currency, i64, T),
    ) -> Result<Self, Self::Error> {
        let amount = get_amount_as_string(currency_unit, txn_amount, currency)?;
        Ok(Self {
            amount,
            router_data: item,
        })
    }
}

pub struct ZslAuthType {
    pub(super) api_key: Secret<String>,
    pub(super) merchant_id: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for ZslAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
                api_key: api_key.to_owned(),
                merchant_id: key1.clone(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZslPaymentsRequest {
    process_type: ProcessType,
    process_code: ProcessCode,
    txn_amt: String,
    ccy: api_models::enums::Currency,
    mer_ref: String,
    mer_txn_date: String,
    mer_id: Secret<String>,
    lang: String,
    success_url: String,
    failure_url: String,
    success_s2s_url: String,
    failure_s2s_url: String,
    enctype: EncodingType,
    signature: Secret<String>,
    country: api_models::enums::CountryAlpha2,
    verno: String,
    service_code: ServiceCode,
    cust_tag: String,
    #[serde(flatten)]
    payment_method: ZslPaymentMethods,
    name: Option<Secret<String>>,
    family_name: Option<Secret<String>>,
    tel_phone: Option<Secret<String>>,
    email: Option<Email>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ZslPaymentMethods {
    LocalBankTransfer(LocalBankTransaferRequest),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocalBankTransaferRequest {
    bank_code: Option<String>,
    pay_method: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ProcessType {
    #[serde(rename = "0200")]
    PaymentRequest,
    #[serde(rename = "0208")]
    PaymentResponse,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ProcessCode {
    #[serde(rename = "200002")]
    API,
    #[serde(rename = "200003")]
    CallBack,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EncodingType {
    #[serde(rename = "1")]
    MD5,
    #[serde(rename = "2")]
    Sha1,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum ServiceCode {
    MPG,
}

impl TryFrom<&ZslRouterData<&types::PaymentsAuthorizeRouterData>> for ZslPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &ZslRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let payment_method = match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::BankTransfer(bank_transfer_data) => match *bank_transfer_data {
                BankTransferData::LocalBankTransfer { bank_code } => Ok(
                    ZslPaymentMethods::LocalBankTransfer(LocalBankTransaferRequest {
                        bank_code,
                        pay_method: None,
                    }),
                ),
                BankTransferData::AchBankTransfer { .. }
                | BankTransferData::SepaBankTransfer { .. }
                | BankTransferData::BacsBankTransfer { .. }
                | BankTransferData::MultibancoBankTransfer { .. }
                | BankTransferData::PermataBankTransfer { .. }
                | BankTransferData::BcaBankTransfer { .. }
                | BankTransferData::BniVaBankTransfer { .. }
                | BankTransferData::BriVaBankTransfer { .. }
                | BankTransferData::CimbVaBankTransfer { .. }
                | BankTransferData::DanamonVaBankTransfer { .. }
                | BankTransferData::MandiriVaBankTransfer { .. }
                | BankTransferData::Pix { .. }
                | BankTransferData::Pse {}
                | BankTransferData::InstantBankTransferFinland {}
                | BankTransferData::InstantBankTransferPoland {}
                | BankTransferData::IndonesianBankTransfer { .. }
                | BankTransferData::InstantBankTransfer {} => {
                    Err(errors::ConnectorError::NotImplemented(
                        get_unimplemented_payment_method_error_message(
                            item.router_data.connector.as_str(),
                        ),
                    ))
                }
            },
            PaymentMethodData::Card(_)
            | PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::Wallet(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::BankDebit(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_)
            | PaymentMethodData::OpenBanking(_) => Err(errors::ConnectorError::NotImplemented(
                get_unimplemented_payment_method_error_message(item.router_data.connector.as_str()),
            )),
        }?;
        let auth_type = ZslAuthType::try_from(&item.router_data.connector_auth_type)?;
        let key: Secret<String> = auth_type.api_key;
        let mer_id = auth_type.merchant_id;
        let mer_txn_date =
            date_time::format_date(date_time::now(), date_time::DateFormat::YYYYMMDDHHmmss)
                .change_context(errors::ConnectorError::RequestEncodingFailed)?;
        let txn_amt = item.amount.clone();
        let ccy = item.router_data.request.currency;
        let mer_ref = item.router_data.connector_request_reference_id.clone();
        let signature = calculate_signature(
            EncodingType::MD5,
            ZslSignatureType::RequestSignature {
                txn_amt: txn_amt.clone(),
                ccy: ccy.to_string(),
                mer_ref: mer_ref.clone(),
                mer_id: mer_id.clone().expose(),
                mer_txn_date: mer_txn_date.clone(),
                key: key.expose(),
            },
        )?;
        let tel_phone = item.router_data.get_optional_billing_phone_number();
        let email = item.router_data.get_optional_billing_email();
        let name = item.router_data.get_optional_billing_first_name();
        let family_name = item.router_data.get_optional_billing_last_name();
        let router_url = item.router_data.request.get_router_return_url()?;
        let webhook_url = item.router_data.request.get_webhook_url()?;
        let billing_country = item.router_data.get_billing_country()?;

        let lang = item
            .router_data
            .request
            .browser_info
            .as_ref()
            .and_then(|browser_data| {
                browser_data.language.as_ref().map(|language| {
                    language
                        .split_once('-')
                        .map_or(language.to_uppercase(), |(lang, _)| lang.to_uppercase())
                })
            })
            .ok_or(errors::ConnectorError::MissingRequiredField {
                field_name: "browser_info.language",
            })?;

        let cust_tag = item
            .router_data
            .customer_id
            .clone()
            .and_then(|customer_id| {
                let cust_id = customer_id.get_string_repr().replace(['_', '-'], "");
                let id_len = cust_id.len();
                if id_len > 10 {
                    cust_id.get(id_len - 10..id_len).map(|id| id.to_string())
                } else {
                    Some(cust_id)
                }
            })
            .ok_or(errors::ConnectorError::MissingRequiredField {
                field_name: "customer_id",
            })?;

        Ok(Self {
            process_type: ProcessType::PaymentRequest,
            process_code: ProcessCode::API,
            txn_amt,
            ccy,
            mer_ref,
            mer_txn_date,
            mer_id,
            lang,
            success_url: router_url.clone(),
            failure_url: router_url.clone(),
            success_s2s_url: webhook_url.clone(),
            failure_s2s_url: webhook_url.clone(),
            enctype: EncodingType::MD5,
            signature,
            verno: zsl_version::VERSION_1.to_owned(),
            service_code: ServiceCode::MPG,
            country: billing_country,
            payment_method,
            name,
            family_name,
            tel_phone,
            email,
            cust_tag,
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZslPaymentsResponse {
    process_type: ProcessType,
    process_code: ProcessCode,
    status: String,
    mer_ref: String,
    mer_id: String,
    enctype: EncodingType,
    txn_url: String,
    signature: Secret<String>,
}

impl<F, T> TryFrom<ResponseRouterData<F, ZslPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, ZslPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        if item.response.status.eq("0") && !item.response.txn_url.is_empty() {
            let auth_type = ZslAuthType::try_from(&item.data.connector_auth_type)?;
            let key: Secret<String> = auth_type.api_key;
            let mer_id = auth_type.merchant_id;
            let calculated_signature = calculate_signature(
                item.response.enctype,
                ZslSignatureType::ResponseSignature {
                    status: item.response.status.clone(),
                    txn_url: item.response.txn_url.clone(),
                    mer_ref: item.response.mer_ref.clone(),
                    mer_id: mer_id.clone().expose(),
                    key: key.expose(),
                },
            )?;

            if calculated_signature.clone().eq(&item.response.signature) {
                let decoded_redirect_url_bytes: Vec<u8> = base64::engine::general_purpose::STANDARD
                    .decode(item.response.txn_url.clone())
                    .change_context(errors::ConnectorError::RequestEncodingFailed)?;

                let redirect_url = String::from_utf8(decoded_redirect_url_bytes)
                    .change_context(errors::ConnectorError::RequestEncodingFailed)?;

                Ok(Self {
                    status: enums::AttemptStatus::AuthenticationPending, // Redirect is always expected after success response
                    response: Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: ResponseId::NoResponseId,
                        redirection_data: Box::new(Some(RedirectForm::Form {
                            endpoint: redirect_url,
                            method: Method::Get,
                            form_fields: HashMap::new(),
                        })),
                        mandate_reference: Box::new(None),
                        connector_metadata: None,
                        network_txn_id: None,
                        connector_response_reference_id: Some(item.response.mer_ref.clone()),
                        incremental_authorization_allowed: None,
                        charges: None,
                    }),
                    ..item.data
                })
            } else {
                // When the signature check fails
                Ok(Self {
                    status: enums::AttemptStatus::Failure,
                    response: Err(ErrorResponse {
                        code: NO_ERROR_CODE.to_string(),
                        message: auth_error::INVALID_SIGNATURE.to_string(),
                        reason: Some(auth_error::INVALID_SIGNATURE.to_string()),
                        status_code: item.http_code,
                        attempt_status: Some(enums::AttemptStatus::Failure),
                        connector_transaction_id: Some(item.response.mer_ref.clone()),
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    }),
                    ..item.data
                })
            }
        } else {
            let error_reason =
                ZslResponseStatus::try_from(item.response.status.clone())?.to_string();
            Ok(Self {
                status: enums::AttemptStatus::Failure,
                response: Err(ErrorResponse {
                    code: item.response.status.clone(),
                    message: error_reason.clone(),
                    reason: Some(error_reason.clone()),
                    status_code: item.http_code,
                    attempt_status: Some(enums::AttemptStatus::Failure),
                    connector_transaction_id: Some(item.response.mer_ref.clone()),
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                }),
                ..item.data
            })
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZslWebhookResponse {
    pub process_type: ProcessType,
    pub process_code: ProcessCode,
    pub status: String,
    pub txn_id: String,
    pub txn_date: String,
    pub paid_ccy: api_models::enums::Currency,
    pub paid_amt: String,
    pub consr_paid_ccy: Option<api_models::enums::Currency>,
    pub consr_paid_amt: String,
    pub service_fee_ccy: Option<api_models::enums::Currency>,
    pub service_fee: Option<String>,
    pub txn_amt: String,
    pub ccy: String,
    pub mer_ref: String,
    pub mer_txn_date: String,
    pub mer_id: String,
    pub enctype: EncodingType,
    pub signature: Secret<String>,
}

pub(crate) fn get_status(status: String) -> api_models::webhooks::IncomingWebhookEvent {
    match status.as_str() {
        //any response with status != 0 are a failed deposit transaction
        "0" => api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess,
        _ => api_models::webhooks::IncomingWebhookEvent::PaymentIntentFailure,
    }
}

impl<F> TryFrom<ResponseRouterData<F, ZslWebhookResponse, PaymentsSyncData, PaymentsResponseData>>
    for RouterData<F, PaymentsSyncData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, ZslWebhookResponse, PaymentsSyncData, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let paid_amount = item
            .response
            .consr_paid_amt
            .parse::<i64>()
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        if item.response.status == "0" {
            Ok(Self {
                status: enums::AttemptStatus::Charged,
                amount_captured: Some(paid_amount),
                response: Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(item.response.txn_id.clone()),
                    redirection_data: Box::new(None),
                    mandate_reference: Box::new(None),
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: Some(item.response.mer_ref.clone()),
                    incremental_authorization_allowed: None,
                    charges: None,
                }),
                ..item.data
            })
        } else {
            let error_reason =
                ZslResponseStatus::try_from(item.response.status.clone())?.to_string();
            Ok(Self {
                status: enums::AttemptStatus::Failure,
                response: Err(ErrorResponse {
                    code: item.response.status.clone(),
                    message: error_reason.clone(),
                    reason: Some(error_reason.clone()),
                    status_code: item.http_code,
                    attempt_status: Some(enums::AttemptStatus::Failure),
                    connector_transaction_id: Some(item.response.mer_ref.clone()),
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                }),
                ..item.data
            })
        }
    }
}

impl TryFrom<String> for ZslResponseStatus {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(status: String) -> Result<Self, Self::Error> {
        match status.as_str() {
            "0" => Ok(Self::Normal),
            "1000" => Ok(Self::InternalError),
            "1001" => Ok(Self::BreakDownMessageError),
            "1002" => Ok(Self::FormatError),
            "1004" => Ok(Self::InvalidTransaction),
            "1005" => Ok(Self::TransactionCountryNotFound),
            "1006" => Ok(Self::MerchantIdNotFound),
            "1007" => Ok(Self::AccountDisabled),
            "1008" => Ok(Self::DuplicateMerchantReference),
            "1009" => Ok(Self::InvalidPayAmount),
            "1010" => Ok(Self::PayAmountNotFound),
            "1011" => Ok(Self::InvalidCurrencyCode),
            "1012" => Ok(Self::CurrencyCodeNotFound),
            "1013" => Ok(Self::ReferenceNotFound),
            "1014" => Ok(Self::TransmissionTimeNotFound),
            "1015" => Ok(Self::PayMethodNotFound),
            "1016" => Ok(Self::BankCodeNotFound),
            "1017" => Ok(Self::InvalidShowPayPage),
            "1018" => Ok(Self::ShowPayPageNotFound),
            "1019" => Ok(Self::SuccessUrlNotFound),
            "1020" => Ok(Self::SuccessCallbackUrlNotFound),
            "1021" => Ok(Self::FailUrlNotFound),
            "1022" => Ok(Self::FailCallbackUrlNotFound),
            "1023" => Ok(Self::MacNotFound),
            "1025" => Ok(Self::OriginalTransactionNotFound),
            "1026" => Ok(Self::DeblockDataError),
            "1028" => Ok(Self::PspAckNotYetReturn),
            "1029" => Ok(Self::BankBranchNameNotFound),
            "1030" => Ok(Self::BankAccountIDNotFound),
            "1031" => Ok(Self::BankAccountNameNotFound),
            "1032" => Ok(Self::IdentityIDNotFound),
            "1033" => Ok(Self::ErrorConnectingToPsp),
            "1034" => Ok(Self::CountryPspNotAvailable),
            "1035" => Ok(Self::UnsupportedPayAmount),
            "1036" => Ok(Self::RecordMismatch),
            "1037" => Ok(Self::NoRecord),
            "1038" => Ok(Self::PspError),
            "1039" => Ok(Self::UnsupportedEncryptionType),
            "1040" => Ok(Self::ExceedTransactionLimitCount),
            "1041" => Ok(Self::ExceedTransactionLimitAmount),
            "1042" => Ok(Self::ExceedTransactionAccountLimitCount),
            "1043" => Ok(Self::ExceedTransactionAccountLimitAmount),
            "1044" => Ok(Self::ExchangeRateError),
            "1045" => Ok(Self::InvalidEncoding),
            "1046" => Ok(Self::CustomerNameNotFound),
            "1047" => Ok(Self::CustomerFamilyNameNotFound),
            "1048" => Ok(Self::CustomerTelPhoneNotFound),
            "1049" => Ok(Self::InsufficientFund),
            "1050" => Ok(Self::ServiceCodeIsMissing),
            "1051" => Ok(Self::CurrencyIdNotMatch),
            "1052" => Ok(Self::NoPendingRecord),
            "1053" => Ok(Self::NoLoadBalancerRuleDefineForTransaction),
            "1054" => Ok(Self::NoPaymentProviderAvailable),
            "1055" => Ok(Self::UnsupportedPayMethod),
            "1056" => Ok(Self::PendingTransaction),
            "1057" => Ok(Self::OtherError1059),
            "1058" => Ok(Self::OtherError1058),
            "1059" => Ok(Self::OtherError1059),
            "1084" => Ok(Self::InvalidRequestId),
            "5043" => Ok(Self::BeneficiaryBankAccountIsNotAvailable),
            "5053" => Ok(Self::BaidNotFound),
            "5057" => Ok(Self::InvalidBaid),
            "5059" => Ok(Self::InvalidBaidStatus),
            "5107" => Ok(Self::AutoUploadBankDisabled),
            "5108" => Ok(Self::InvalidNature),
            "5109" => Ok(Self::SmsCreateDateNotFound),
            "5110" => Ok(Self::InvalidSmsCreateDate),
            "5111" => Ok(Self::RecordNotFound),
            "5112" => Ok(Self::InsufficientBaidAvailableBalance),
            "5113" => Ok(Self::ExceedTxnAmountLimit),
            "5114" => Ok(Self::BaidBalanceNotFound),
            "5115" => Ok(Self::AutoUploadIndicatorNotFound),
            "5116" => Ok(Self::InvalidBankAcctStatus),
            "5117" => Ok(Self::InvalidAutoUploadIndicator),
            "5118" => Ok(Self::InvalidPidStatus),
            "5119" => Ok(Self::InvalidProviderStatus),
            "5120" => Ok(Self::InvalidBankAccountSystemSwitchEnabled),
            "5121" => Ok(Self::AutoUploadProviderDisabled),
            "5122" => Ok(Self::AutoUploadBankNotFound),
            "5123" => Ok(Self::AutoUploadBankAcctNotFound),
            "5124" => Ok(Self::AutoUploadProviderNotFound),
            "5125" => Ok(Self::UnsupportedBankCode),
            "5126" => Ok(Self::BalanceOverrideIndicatorNotFound),
            "5127" => Ok(Self::InvalidBalanceOverrideIndicator),
            "10000" => Ok(Self::VernoInvalid),
            "10001" => Ok(Self::ServiceCodeInvalid),
            "10002" => Ok(Self::PspResponseSignatureIsNotValid),
            "10003" => Ok(Self::ProcessTypeNotFound),
            "10004" => Ok(Self::ProcessCodeNotFound),
            "10005" => Ok(Self::EnctypeNotFound),
            "10006" => Ok(Self::VernoNotFound),
            "10007" => Ok(Self::DepositBankNotFound),
            "10008" => Ok(Self::DepositFlowNotFound),
            "10009" => Ok(Self::CustDepositDateNotFound),
            "10010" => Ok(Self::CustTagNotFound),
            "10011" => Ok(Self::CountryValueInvalid),
            "10012" => Ok(Self::CurrencyCodeValueInvalid),
            "10013" => Ok(Self::MerTxnDateInvalid),
            "10014" => Ok(Self::CustDepositDateInvalid),
            "10015" => Ok(Self::TxnAmtInvalid),
            "10016" => Ok(Self::SuccessCallbackUrlInvalid),
            "10017" => Ok(Self::DepositFlowInvalid),
            "10018" => Ok(Self::ProcessTypeInvalid),
            "10019" => Ok(Self::ProcessCodeInvalid),
            "10020" => Ok(Self::UnsupportedMerRefLength),
            "10021" => Ok(Self::DepositBankLengthOverLimit),
            "10022" => Ok(Self::CustTagLengthOverLimit),
            "10023" => Ok(Self::SignatureLengthOverLimit),
            "10024" => Ok(Self::RequestContainInvalidTag),
            "10025" => Ok(Self::RequestSignatureNotMatch),
            "10026" => Ok(Self::InvalidCustomer),
            "10027" => Ok(Self::SchemeNotFound),
            "10028" => Ok(Self::PspResponseFieldsMissing),
            "10029" => Ok(Self::PspResponseMerRefNotMatchWithRequestMerRef),
            "10030" => Ok(Self::PspResponseMerIdNotMatchWithRequestMerId),
            "10031" => Ok(Self::UpdateDepositFailAfterResponse),
            "10032" => Ok(Self::UpdateUsedLimitTransactionCountFailAfterSuccessResponse),
            "10033" => Ok(Self::UpdateCustomerLastDepositRecordAfterSuccessResponse),
            "10034" => Ok(Self::CreateDepositFail),
            "10035" => Ok(Self::CreateDepositMsgFail),
            "10036" => Ok(Self::UpdateStatusSubStatusFail),
            "10037" => Ok(Self::AddDepositRecordToSchemeAccount),
            "10038" => Ok(Self::EmptyResponse),
            "10039" => Ok(Self::AubConfirmErrorFromPh),
            "10040" => Ok(Self::ProviderEmailAddressNotFound),
            "10041" => Ok(Self::AubConnectionTimeout),
            "10042" => Ok(Self::AubConnectionIssue),
            "10043" => Ok(Self::AubMsgTypeMissing),
            "10044" => Ok(Self::AubMsgCodeMissing),
            "10045" => Ok(Self::AubVersionMissing),
            "10046" => Ok(Self::AubEncTypeMissing),
            "10047" => Ok(Self::AubSignMissing),
            "10048" => Ok(Self::AubInfoMissing),
            "10049" => Ok(Self::AubErrorCodeMissing),
            "10050" => Ok(Self::AubMsgTypeInvalid),
            "10051" => Ok(Self::AubMsgCodeInvalid),
            "10052" => Ok(Self::AubBaidMissing),
            "10053" => Ok(Self::AubResponseSignNotMatch),
            "10054" => Ok(Self::SmsConnectionTimeout),
            "10055" => Ok(Self::SmsConnectionIssue),
            "10056" => Ok(Self::SmsConfirmErrorFromPh),
            "10057" => Ok(Self::SmsMsgTypeMissing),
            "10058" => Ok(Self::SmsMsgCodeMissing),
            "10059" => Ok(Self::SmsVersionMissing),
            "10060" => Ok(Self::SmsEncTypeMissing),
            "10061" => Ok(Self::SmsSignMissing),
            "10062" => Ok(Self::SmsInfoMissing),
            "10063" => Ok(Self::SmsErrorCodeMissing),
            "10064" => Ok(Self::SmsMsgTypeInvalid),
            "10065" => Ok(Self::SmsMsgCodeInvalid),
            "10066" => Ok(Self::SmsResponseSignNotMatch),
            "10067" => Ok(Self::SmsRequestReachMaximumLimit),
            "10068" => Ok(Self::SyncConnectionTimeout),
            "10069" => Ok(Self::SyncConnectionIssue),
            "10070" => Ok(Self::SyncConfirmErrorFromPh),
            "10071" => Ok(Self::SyncMsgTypeMissing),
            "10072" => Ok(Self::SyncMsgCodeMissing),
            "10073" => Ok(Self::SyncVersionMissing),
            "10074" => Ok(Self::SyncEncTypeMissing),
            "10075" => Ok(Self::SyncSignMissing),
            "10076" => Ok(Self::SyncInfoMissing),
            "10077" => Ok(Self::SyncErrorCodeMissing),
            "10078" => Ok(Self::SyncMsgTypeInvalid),
            "10079" => Ok(Self::SyncMsgCodeInvalid),
            "10080" => Ok(Self::SyncResponseSignNotMatch),
            "10081" => Ok(Self::AccountExpired),
            "10082" => Ok(Self::ExceedMaxMinAmount),
            "10083" => Ok(Self::WholeNumberAmountLessThanOne),
            "10084" => Ok(Self::AddDepositRecordToSchemeChannel),
            "10085" => Ok(Self::UpdateUtilizedAmountFailAfterSuccessResponse),
            "10086" => Ok(Self::PidResponseInvalidFormat),
            "10087" => Ok(Self::PspNameNotFound),
            "10088" => Ok(Self::LangIsMissing),
            "10089" => Ok(Self::FailureCallbackUrlInvalid),
            "10090" => Ok(Self::SuccessRedirectUrlInvalid),
            "10091" => Ok(Self::FailureRedirectUrlInvalid),
            "10092" => Ok(Self::LangValueInvalid),
            "10093" => Ok(Self::OnlineDepositSessionTimeout),
            "10094" => Ok(Self::AccessPaymentPageRouteFieldMissing),
            "10095" => Ok(Self::AmountNotMatch),
            "10096" => Ok(Self::PidCallbackFieldsMissing),
            "10097" => Ok(Self::TokenNotMatch),
            "10098" => Ok(Self::OperationDuplicated),
            "10099" => Ok(Self::PayPageDomainNotAvailable),
            "10100" => Ok(Self::PayPageConfirmSignatureNotMatch),
            "10101" => Ok(Self::PaymentPageConfirmationFieldMissing),
            "10102" => Ok(Self::MultipleCallbackFromPsp),
            "10103" => Ok(Self::PidNotAvailable),
            "10104" => Ok(Self::PidDepositUrlNotValidOrEmp),
            "10105" => Ok(Self::PspSelfRedirectTagNotValid),
            "20000" => Ok(Self::InternalError20000),
            "20001" => Ok(Self::DepositTimeout),
            _ => Err(errors::ConnectorError::ResponseHandlingFailed.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, strum::Display)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ZslResponseStatus {
    Normal,
    InternalError,
    BreakDownMessageError,
    FormatError,
    InvalidTransaction,
    TransactionCountryNotFound,
    MerchantIdNotFound,
    AccountDisabled,
    DuplicateMerchantReference,
    InvalidPayAmount,
    PayAmountNotFound,
    InvalidCurrencyCode,
    CurrencyCodeNotFound,
    ReferenceNotFound,
    TransmissionTimeNotFound,
    PayMethodNotFound,
    BankCodeNotFound,
    InvalidShowPayPage,
    ShowPayPageNotFound,
    SuccessUrlNotFound,
    SuccessCallbackUrlNotFound,
    FailUrlNotFound,
    FailCallbackUrlNotFound,
    MacNotFound,
    OriginalTransactionNotFound,
    DeblockDataError,
    PspAckNotYetReturn,
    BankBranchNameNotFound,
    BankAccountIDNotFound,
    BankAccountNameNotFound,
    IdentityIDNotFound,
    ErrorConnectingToPsp,
    CountryPspNotAvailable,
    UnsupportedPayAmount,
    RecordMismatch,
    NoRecord,
    PspError,
    UnsupportedEncryptionType,
    ExceedTransactionLimitCount,
    ExceedTransactionLimitAmount,
    ExceedTransactionAccountLimitCount,
    ExceedTransactionAccountLimitAmount,
    ExchangeRateError,
    InvalidEncoding,
    CustomerNameNotFound,
    CustomerFamilyNameNotFound,
    CustomerTelPhoneNotFound,
    InsufficientFund,
    ServiceCodeIsMissing,
    CurrencyIdNotMatch,
    NoPendingRecord,
    NoLoadBalancerRuleDefineForTransaction,
    NoPaymentProviderAvailable,
    UnsupportedPayMethod,
    PendingTransaction,
    OtherError1059,
    OtherError1058,
    InvalidRequestId,
    BeneficiaryBankAccountIsNotAvailable,
    BaidNotFound,
    InvalidBaid,
    InvalidBaidStatus,
    AutoUploadBankDisabled,
    InvalidNature,
    SmsCreateDateNotFound,
    InvalidSmsCreateDate,
    RecordNotFound,
    InsufficientBaidAvailableBalance,
    ExceedTxnAmountLimit,
    BaidBalanceNotFound,
    AutoUploadIndicatorNotFound,
    InvalidBankAcctStatus,
    InvalidAutoUploadIndicator,
    InvalidPidStatus,
    InvalidProviderStatus,
    InvalidBankAccountSystemSwitchEnabled,
    AutoUploadProviderDisabled,
    AutoUploadBankNotFound,
    AutoUploadBankAcctNotFound,
    AutoUploadProviderNotFound,
    UnsupportedBankCode,
    BalanceOverrideIndicatorNotFound,
    InvalidBalanceOverrideIndicator,
    VernoInvalid,
    ServiceCodeInvalid,
    PspResponseSignatureIsNotValid,
    ProcessTypeNotFound,
    ProcessCodeNotFound,
    EnctypeNotFound,
    VernoNotFound,
    DepositBankNotFound,
    DepositFlowNotFound,
    CustDepositDateNotFound,
    CustTagNotFound,
    CountryValueInvalid,
    CurrencyCodeValueInvalid,
    MerTxnDateInvalid,
    CustDepositDateInvalid,
    TxnAmtInvalid,
    SuccessCallbackUrlInvalid,
    DepositFlowInvalid,
    ProcessTypeInvalid,
    ProcessCodeInvalid,
    UnsupportedMerRefLength,
    DepositBankLengthOverLimit,
    CustTagLengthOverLimit,
    SignatureLengthOverLimit,
    RequestContainInvalidTag,
    RequestSignatureNotMatch,
    InvalidCustomer,
    SchemeNotFound,
    PspResponseFieldsMissing,
    PspResponseMerRefNotMatchWithRequestMerRef,
    PspResponseMerIdNotMatchWithRequestMerId,
    UpdateDepositFailAfterResponse,
    UpdateUsedLimitTransactionCountFailAfterSuccessResponse,
    UpdateCustomerLastDepositRecordAfterSuccessResponse,
    CreateDepositFail,
    CreateDepositMsgFail,
    UpdateStatusSubStatusFail,
    AddDepositRecordToSchemeAccount,
    EmptyResponse,
    AubConfirmErrorFromPh,
    ProviderEmailAddressNotFound,
    AubConnectionTimeout,
    AubConnectionIssue,
    AubMsgTypeMissing,
    AubMsgCodeMissing,
    AubVersionMissing,
    AubEncTypeMissing,
    AubSignMissing,
    AubInfoMissing,
    AubErrorCodeMissing,
    AubMsgTypeInvalid,
    AubMsgCodeInvalid,
    AubBaidMissing,
    AubResponseSignNotMatch,
    SmsConnectionTimeout,
    SmsConnectionIssue,
    SmsConfirmErrorFromPh,
    SmsMsgTypeMissing,
    SmsMsgCodeMissing,
    SmsVersionMissing,
    SmsEncTypeMissing,
    SmsSignMissing,
    SmsInfoMissing,
    SmsErrorCodeMissing,
    SmsMsgTypeInvalid,
    SmsMsgCodeInvalid,
    SmsResponseSignNotMatch,
    SmsRequestReachMaximumLimit,
    SyncConnectionTimeout,
    SyncConnectionIssue,
    SyncConfirmErrorFromPh,
    SyncMsgTypeMissing,
    SyncMsgCodeMissing,
    SyncVersionMissing,
    SyncEncTypeMissing,
    SyncSignMissing,
    SyncInfoMissing,
    SyncErrorCodeMissing,
    SyncMsgTypeInvalid,
    SyncMsgCodeInvalid,
    SyncResponseSignNotMatch,
    AccountExpired,
    ExceedMaxMinAmount,
    WholeNumberAmountLessThanOne,
    AddDepositRecordToSchemeChannel,
    UpdateUtilizedAmountFailAfterSuccessResponse,
    PidResponseInvalidFormat,
    PspNameNotFound,
    LangIsMissing,
    FailureCallbackUrlInvalid,
    SuccessRedirectUrlInvalid,
    FailureRedirectUrlInvalid,
    LangValueInvalid,
    OnlineDepositSessionTimeout,
    AccessPaymentPageRouteFieldMissing,
    AmountNotMatch,
    PidCallbackFieldsMissing,
    TokenNotMatch,
    OperationDuplicated,
    PayPageDomainNotAvailable,
    PayPageConfirmSignatureNotMatch,
    PaymentPageConfirmationFieldMissing,
    MultipleCallbackFromPsp,
    PidNotAvailable,
    PidDepositUrlNotValidOrEmp,
    PspSelfRedirectTagNotValid,


pub struct WellsfargoRouterData<T> {
    pub amount: StringMajorUnit,
    pub router_data: T,
}


pub async fn cancel_payout(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    connector_data: &api::ConnectorData,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    // 1. Form Router data
    let router_data = core_utils::construct_payout_router_data(
        state,
        connector_data,
        merchant_context,
        payout_data,
    )
    .await?;

    // 2. Fetch connector integration details
    let connector_integration: services::BoxedPayoutConnectorIntegrationInterface<
        api::PoCancel,
        types::PayoutsData,
        types::PayoutsResponseData,
    > = connector_data.connector.get_connector_integration();

    // 3. Call connector service
    let router_data_resp = services::execute_connector_processing_step(
        state,
        connector_integration,
        &router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_payout_failed_response()?;

    // 4. Process data returned by the connector
    let db = &*state.store;
    match router_data_resp.response {
        Ok(payout_response_data) => {
            let status = payout_response_data
                .status
                .unwrap_or(payout_data.payout_attempt.status.to_owned());
            let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                connector_payout_id: payout_response_data.connector_payout_id,
                status,
                error_code: None,
                error_message: None,
                is_eligible: payout_response_data.payout_eligible,
                unified_code: None,
                unified_message: None,
            };
            payout_data.payout_attempt = db
                .update_payout_attempt(
                    &payout_data.payout_attempt,
                    updated_payout_attempt,
                    &payout_data.payouts,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payout_attempt in db")?;
            payout_data.payouts = db
                .update_payout(
                    &payout_data.payouts,
                    storage::PayoutsUpdate::StatusUpdate { status },
                    &payout_data.payout_attempt,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payouts in db")?;
        }
        Err(err) => {
            let status = storage_enums::PayoutStatus::Failed;
            let (error_code, error_message) = (Some(err.code), Some(err.message));
            let (unified_code, unified_message) = helpers::get_gsm_record(
                state,
                error_code.clone(),
                error_message.clone(),
                payout_data.payout_attempt.connector.clone(),
                consts::PAYOUT_FLOW_STR,
            )
            .await
            .map_or(
                (
                    Some(crate::consts::DEFAULT_UNIFIED_ERROR_CODE.to_string()),
                    Some(crate::consts::DEFAULT_UNIFIED_ERROR_MESSAGE.to_string()),
                ),
                |gsm| (gsm.unified_code, gsm.unified_message),
            );
            let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                connector_payout_id: payout_data.payout_attempt.connector_payout_id.to_owned(),
                status,
                error_code,
                error_message,
                is_eligible: None,
                unified_code: unified_code
                    .map(UnifiedCode::try_from)
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "unified_code",
                    })?,
                unified_message: unified_message
                    .map(UnifiedMessage::try_from)
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "unified_message",
                    })?,
            };
            payout_data.payout_attempt = db
                .update_payout_attempt(
                    &payout_data.payout_attempt,
                    updated_payout_attempt,
                    &payout_data.payouts,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payout_attempt in db")?;
            payout_data.payouts = db
                .update_payout(
                    &payout_data.payouts,
                    storage::PayoutsUpdate::StatusUpdate { status },
                    &payout_data.payout_attempt,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payouts in db")?;
        }
    };

    Ok(())
}


pub struct PaymentsCollectionItem {
    amount: OrderAmount,
    expiration_time: Option<String>,
    id: String,
    final_capture: Option<bool>,
    status: PaypalPaymentStatus,
}


Documentation: api-reference/v1/payments/setup--instructions.mdx
# Type: Doc File

---
tags: [Payments]
sidebarTitle: "Setup Instructions"
icon: "flag"
iconType: "solid"
---
The **Hyperswitch Payments API** enables businesses to **accept, process, and manage payments seamlessly**. It supports the entire **payment lifecycle**—from creation to capture, refunds, and disputes—allowing smooth integration of **various payment methods** into any application with minimal complexity.

### How to try your first payment through hyperswitch?
You have two options to use the Payments API:

1. **Sandbox API Key (Dashboard)** – Quick testing without setup.
2. **Self-Deploy** – Create merchants and API keys through Rest API.

Each option has specific nuances, we have mentioned the differences in the below step by step guide
<Tip>We recommend using our [Dashboard](https://app.hyperswitch.io/dashboard/login) to generate API Key and setting up Connectors (Step 4) for faster trial and simple setup.</Tip>
<Steps>
  <Step title="Create a Merchant Account">
    This account is representative of you or your organization that would like to accept payments from different <Tooltip tip="Can be a payment method or payment service provider">payment connectors</Tooltip>
    <Steps>
  <Step title="Hyperswitch Dashboard flow" icon= "box-open">
    Access the <Tooltip tip="Control Center is a frontend interface for the API to track and test payments">[Dashboard](https://app.hyperswitch.io/dashboard/login)</Tooltip> and sign up -> **Sign up here is equivalent to creating a Merchant Account**
  </Step>
  <Step title="Self-Deploy flow" icon="map-pin">
   Use the admin api key and the [Merchant Account - Create](api-reference/merchant-account/merchant-account--create) endpoint to create your Merchant Account
  </Step>
</Steps>
  </Step>
  <Step title="Create API Key">
   You can now generate an API key that will be the secret key used to authenticate your payment request
 <Steps>
  <Step title="Hyperswitch Dashboard flow" icon= "box-open">
    In Dashboard go to Developer-> API Keys -> +Create New Api Key. This key can be used in your API requests for authentication.
  </Step>
  <Step title="Self-Deploy flow" icon="map-pin">
   Use the admin api key and the [Merchant Account - Create](api-reference/merchant-account/merchant-account--create) endpoint to create your Merchant Account
  </Step>
</Steps>
  </Step>
  <Step title="Set up Connectors">
    Connect the payment [connectors](api-reference/merchant-connector-account/merchant-connector--create) and payment methods that your organization will accept. Connectors could be a payment processor/facilitator/acquirer or a provider of specialized services like Fraud/Accounting
  <Steps>
  <Step title="Hyperswitch Dashboard flow" icon= "box-open">
    In Dashboard go to Connectors -> <Tooltip tip="Adding other connectors (eg. Adyen) will require you to go through their respective documentation">+Connect a Dummy Processor</Tooltip>. Choose the payments methods to enable and complete setup.
  </Step>
  <Step title="Self-Deploy flow" icon="map-pin">
   Use the admin api key and the [Connector Account](api-reference/merchant-connector-account/merchant-connector--create) endpoints to set up a connector
  </Step>
</Steps>
  </Step>
  <Step title="Try your first payment">
    You are all set! Go ahead and try the [Payments API](api-reference/payments/payments--create), be sure to try the different use cases provided
  </Step>
  <Step title="We are here to help" icon="circle-info">
   Test the use cases you are interested in and in case of difficulty, feel free to contact us on our [slack channel](https://join.slack.com/t/hyperswitch-io/shared_invite/zt-2jqxmpsbm-WXUENx022HjNEy~Ark7Orw)
  </Step>
</Steps>