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

use std::marker::PhantomData;

use api_models::{enums::FrmSuggestion, payments::PaymentsIncrementalAuthorizationRequest};
use async_trait::async_trait;
use common_utils::errors::CustomResult;
use diesel_models::authorization::AuthorizationNew;
use error_stack::{report, ResultExt};
use router_env::{instrument, tracing};

use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
    core::{
        errors::{self, RouterResult, StorageErrorExt},
        payments::{
            self, helpers, operations, CustomerDetails, IncrementalAuthorizationDetails,
            PaymentAddress,
        },
    },
    routes::{app::ReqState, SessionState},
    services,
    types::{
        api::{self, PaymentIdTypeExt},
        domain,
        storage::{self, enums},
    },
    utils::OptionExt,
};

#[derive(Debug, Clone, Copy, router_derive::PaymentOperation)]
#[operation(operations = "all", flow = "incremental_authorization")]
pub struct PaymentIncrementalAuthorization;

type PaymentIncrementalAuthorizationOperation<'b, F> =
    BoxedOperation<'b, F, PaymentsIncrementalAuthorizationRequest, payments::PaymentData<F>>;

#[async_trait]
impl<F: Send + Clone + Sync>
    GetTracker<F, payments::PaymentData<F>, PaymentsIncrementalAuthorizationRequest>
    for PaymentIncrementalAuthorization
{
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_id: &api::PaymentIdType,
        request: &PaymentsIncrementalAuthorizationRequest,
        merchant_context: &domain::MerchantContext,
        _auth_flow: services::AuthFlow,
        _header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<
        operations::GetTrackerResponse<
            'a,
            F,
            PaymentsIncrementalAuthorizationRequest,
            payments::PaymentData<F>,
        >,
    > {
        let db = &*state.store;
        let key_manager_state = &state.into();

        let merchant_id = merchant_context.get_merchant_account().get_id();
        let storage_scheme = merchant_context.get_merchant_account().storage_scheme;
        let payment_id = payment_id
            .get_payment_intent_id()
            .change_context(errors::ApiErrorResponse::PaymentNotFound)?;

        let payment_intent = db
            .find_payment_intent_by_payment_id_merchant_id(
                &state.into(),
                &payment_id,
                merchant_id,
                merchant_context.get_merchant_key_store(),
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

        helpers::validate_payment_status_against_allowed_statuses(
            payment_intent.status,
            &[enums::IntentStatus::RequiresCapture],
            "increment authorization",
        )?;

        if payment_intent.incremental_authorization_allowed != Some(true) {
            Err(errors::ApiErrorResponse::PreconditionFailed {
                message:
                    "You cannot increment authorization this payment because it is not allowed for incremental_authorization".to_owned(),
            })?
        }

        let attempt_id = payment_intent.active_attempt.get_id().clone();
        let payment_attempt = db
            .find_payment_attempt_by_payment_id_merchant_id_attempt_id(
                &payment_intent.payment_id,
                merchant_id,
                attempt_id.clone().as_str(),
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

        // Incremental authorization should be performed on an amount greater than the original authorized amount (in this case, greater than the net_amount which is sent for authorization)
        // request.amount is the total amount that should be authorized in incremental authorization which should be greater than the original authorized amount
        if payment_attempt.get_total_amount() >= request.amount {
            Err(errors::ApiErrorResponse::PreconditionFailed {
                message: "Amount should be greater than original authorized amount".to_owned(),
            })?
        }

        let currency = payment_attempt.currency.get_required_value("currency")?;
        let amount = payment_attempt.get_total_amount();

        let profile_id = payment_intent
            .profile_id
            .as_ref()
            .get_required_value("profile_id")
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("'profile_id' not set in payment intent")?;

        let business_profile = state
            .store
            .find_business_profile_by_profile_id(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
                id: profile_id.get_string_repr().to_owned(),
            })?;

        let payment_data = payments::PaymentData {
            flow: PhantomData,
            payment_intent,
            payment_attempt,
            currency,
            amount: amount.into(),
            email: None,
            mandate_id: None,
            mandate_connector: None,
            setup_mandate: None,
            customer_acceptance: None,
            token: None,
            token_data: None,
            address: PaymentAddress::new(None, None, None, None),
            confirm: None,
            payment_method_data: None,
            payment_method_token: None,
            payment_method_info: None,
            force_sync: None,
            all_keys_required: None,
            refunds: vec![],
            disputes: vec![],
            attempts: None,
            sessions_token: vec![],
            card_cvc: None,
            creds_identifier: None,
            pm_token: None,
            connector_customer_id: None,
            recurring_mandate_payment_data: None,
            ephemeral_key: None,
            multiple_capture_data: None,
            redirect_response: None,
            surcharge_details: None,
            frm_message: None,
            payment_link_data: None,
            incremental_authorization_details: Some(IncrementalAuthorizationDetails {
                additional_amount: request.amount - amount,
                total_amount: request.amount,
                reason: request.reason.clone(),
                authorization_id: None,
            }),
            authorizations: vec![],
            authentication: None,
            recurring_details: None,
            poll_config: None,
            tax_data: None,
            session_id: None,
            service_details: None,
            card_testing_guard_data: None,
            vault_operation: None,
            threeds_method_comp_ind: None,
            whole_connector_response: None,
        };

        let get_trackers_response = operations::GetTrackerResponse {
            operation: Box::new(self),
            customer_details: None,
            payment_data,
            business_profile,
            mandate_type: None,
        };

        Ok(get_trackers_response)
    }
}

#[async_trait]
impl<F: Clone + Sync>
    UpdateTracker<F, payments::PaymentData<F>, PaymentsIncrementalAuthorizationRequest>
    for PaymentIncrementalAuthorization
{
    #[instrument(skip_all)]
    async fn update_trackers<'b>(
        &'b self,
        state: &'b SessionState,
        _req_state: ReqState,
        mut payment_data: payments::PaymentData<F>,
        _customer: Option<domain::Customer>,
        storage_scheme: enums::MerchantStorageScheme,
        _updated_customer: Option<storage::CustomerUpdate>,
        key_store: &domain::MerchantKeyStore,
        _frm_suggestion: Option<FrmSuggestion>,
        _header_payload: hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<(
        PaymentIncrementalAuthorizationOperation<'b, F>,
        payments::PaymentData<F>,
    )>
    where
        F: 'b + Send,
    {
        let new_authorization_count = payment_data
            .payment_intent
            .authorization_count
            .map(|count| count + 1)
            .unwrap_or(1);
        // Create new authorization record
        let authorization_new = AuthorizationNew {
            authorization_id: format!(
                "{}_{}",
                common_utils::generate_id_with_default_len("auth"),
                new_authorization_count
            ),
            merchant_id: payment_data.payment_intent.merchant_id.clone(),
            payment_id: payment_data.payment_intent.payment_id.clone(),
            amount: payment_data
                .incremental_authorization_details
                .clone()
                .map(|details| details.total_amount)
                .ok_or(
                    report!(errors::ApiErrorResponse::InternalServerError).attach_printable(
                        "missing incremental_authorization_details in payment_data",
                    ),
                )?,
            status: common_enums::AuthorizationStatus::Processing,
            error_code: None,
            error_message: None,
            connector_authorization_id: None,
            previously_authorized_amount: payment_data.payment_attempt.get_total_amount(),
        };
        let authorization = state
            .store
            .insert_authorization(authorization_new.clone())
            .await
            .to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError {
                message: format!(
                    "Authorization with authorization_id {} already exists",
                    authorization_new.authorization_id
                ),
            })
            .attach_printable("failed while inserting new authorization")?;
        // Update authorization_count in payment_intent
        payment_data.payment_intent = state
            .store
            .update_payment_intent(
                &state.into(),
                payment_data.payment_intent.clone(),
                storage::PaymentIntentUpdate::AuthorizationCountUpdate {
                    authorization_count: new_authorization_count,
                },
                key_store,
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
            .attach_printable("Failed to update authorization_count in Payment Intent")?;
        match &payment_data.incremental_authorization_details {
            Some(details) => {
                payment_data.incremental_authorization_details =
                    Some(IncrementalAuthorizationDetails {
                        authorization_id: Some(authorization.authorization_id),
                        ..details.clone()
                    });
            }
            None => Err(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("missing incremental_authorization_details in payment_data")?,
        }
        Ok((Box::new(self), payment_data))
    }
}

impl<F: Send + Clone + Sync>
    ValidateRequest<F, PaymentsIncrementalAuthorizationRequest, payments::PaymentData<F>>
    for PaymentIncrementalAuthorization
{
    #[instrument(skip_all)]
    fn validate_request<'a, 'b>(
        &'b self,
        request: &PaymentsIncrementalAuthorizationRequest,
        merchant_context: &'a domain::MerchantContext,
    ) -> RouterResult<(
        PaymentIncrementalAuthorizationOperation<'b, F>,
        operations::ValidateResult,
    )> {
        Ok((
            Box::new(self),
            operations::ValidateResult {
                merchant_id: merchant_context.get_merchant_account().get_id().to_owned(),
                payment_id: api::PaymentIdType::PaymentIntentId(request.payment_id.to_owned()),
                storage_scheme: merchant_context.get_merchant_account().storage_scheme,
                requeue: false,
            },
        ))
    }
}

#[async_trait]
impl<F: Clone + Send + Sync>
    Domain<F, PaymentsIncrementalAuthorizationRequest, payments::PaymentData<F>>
    for PaymentIncrementalAuthorization
{
    #[instrument(skip_all)]
    async fn get_or_create_customer_details<'a>(
        &'a self,
        _state: &SessionState,
        _payment_data: &mut payments::PaymentData<F>,
        _request: Option<CustomerDetails>,
        _merchant_key_store: &domain::MerchantKeyStore,
        _storage_scheme: enums::MerchantStorageScheme,
    ) -> CustomResult<
        (
            BoxedOperation<
                'a,
                F,
                PaymentsIncrementalAuthorizationRequest,
                payments::PaymentData<F>,
            >,
            Option<domain::Customer>,
        ),
        errors::StorageError,
    > {
        Ok((Box::new(self), None))
    }

    #[instrument(skip_all)]
    async fn make_pm_data<'a>(
        &'a self,
        _state: &'a SessionState,
        _payment_data: &mut payments::PaymentData<F>,
        _storage_scheme: enums::MerchantStorageScheme,
        _merchant_key_store: &domain::MerchantKeyStore,
        _customer: &Option<domain::Customer>,
        _business_profile: &domain::Profile,
        _should_retry_with_pan: bool,
    ) -> RouterResult<(
        PaymentIncrementalAuthorizationOperation<'a, F>,
        Option<domain::PaymentMethodData>,
        Option<String>,
    )> {
        Ok((Box::new(self), None, None))
    }

    async fn get_connector<'a>(
        &'a self,
        _merchant_context: &domain::MerchantContext,
        state: &SessionState,
        _request: &PaymentsIncrementalAuthorizationRequest,
        _payment_intent: &storage::PaymentIntent,
    ) -> CustomResult<api::ConnectorChoice, errors::ApiErrorResponse> {
        helpers::get_connector_default(state, None).await
    }

    #[instrument(skip_all)]
    async fn guard_payment_against_blocklist<'a>(
        &'a self,
        _state: &SessionState,
        _merchant_context: &domain::MerchantContext,
        _payment_data: &mut payments::PaymentData<F>,
    ) -> CustomResult<bool, errors::ApiErrorResponse> {
        Ok(false)
    }
}


pub fn table<'a>(&self) -> &'a str {
        match self {
            Self::Insert { insertable } => match **insertable {
                Insertable::PaymentIntent(_) => "payment_intent",
                Insertable::PaymentAttempt(_) => "payment_attempt",
                Insertable::Refund(_) => "refund",
                Insertable::Address(_) => "address",
                Insertable::Payouts(_) => "payouts",
                Insertable::PayoutAttempt(_) => "payout_attempt",
                Insertable::Customer(_) => "customer",
                Insertable::ReverseLookUp(_) => "reverse_lookup",
                Insertable::PaymentMethod(_) => "payment_method",
                Insertable::Mandate(_) => "mandate",
            },
            Self::Update { updatable } => match **updatable {
                Updateable::PaymentIntentUpdate(_) => "payment_intent",
                Updateable::PaymentAttemptUpdate(_) => "payment_attempt",
                Updateable::RefundUpdate(_) => "refund",
                Updateable::CustomerUpdate(_) => "customer",
                Updateable::AddressUpdate(_) => "address",
                Updateable::PayoutsUpdate(_) => "payouts",
                Updateable::PayoutAttemptUpdate(_) => "payout_attempt",
                Updateable::PaymentMethodUpdate(_) => "payment_method",
                Updateable::MandateUpdate(_) => " mandate",
            },
        }
    }


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


pub struct PaymentMethodUpdateInternal {
    payment_method_data: Option<Encryption>,
    last_used_at: Option<PrimitiveDateTime>,
    network_transaction_id: Option<String>,
    status: Option<storage_enums::PaymentMethodStatus>,
    locker_id: Option<String>,
    payment_method_type_v2: Option<storage_enums::PaymentMethod>,
    connector_mandate_details: Option<CommonMandateReference>,
    updated_by: Option<String>,
    payment_method_subtype: Option<storage_enums::PaymentMethodType>,
    last_modified: PrimitiveDateTime,
    network_token_requestor_reference_id: Option<String>,
    network_token_locker_id: Option<String>,
    network_token_payment_method_data: Option<Encryption>,
    locker_fingerprint_id: Option<String>,
    external_vault_source: Option<common_utils::id_type::MerchantConnectorAccountId>,
}


OpenAPI Block Path: components.schemas.SupportedPaymentMethod
{
  "allOf": [
    {
      "allOf": [
        {
          "$ref": "#/components/schemas/PaymentMethodSpecificFeatures"
        }
      ],
      "nullable": true
    },
    {
      "type": "object",
      "required": [
        "payment_method",
        "payment_method_type",
        "payment_method_type_display_name",
        "mandates",
        "refunds",
        "supported_capture_methods"
      ],
      "properties": {
        "payment_method": {
          "$ref": "#/components/schemas/PaymentMethod"
        },
        "payment_method_type": {
          "$ref": "#/components/schemas/PaymentMethodType"
        },
        "payment_method_type_display_name": {
          "type": "string",
          "description": "The display name of the payment method type"
        },
        "mandates": {
          "$ref": "#/components/schemas/FeatureStatus"
        },
        "refunds": {
          "$ref": "#/components/schemas/FeatureStatus"
        },
        "supported_capture_methods": {
          "type": "array",
          "items": {
            "$ref": "#/components/schemas/CaptureMethod"
          },
          "description": "List of supported capture methods supported by the payment method type"
        },
        "supported_countries": {
          "type": "array",
          "items": {
            "$ref": "#/components/schemas/CountryAlpha3"
          },
          "description": "List of countries supported by the payment method type via the connector",
          "uniqueItems": true,
          "nullable": true
        },
        "supported_currencies": {
          "type": "array",
          "items": {
            "$ref": "#/components/schemas/Currency"
          },
          "description": "List of currencies supported by the payment method type via the connector",
          "uniqueItems": true,
          "nullable": true
        }
      }
    }
  ]
}


File: crates/router/src/types/storage/fraud_check.rs

pub use diesel_models::fraud_check::{
    FraudCheck, FraudCheckNew, FraudCheckUpdate, FraudCheckUpdateInternal,
};


pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite(
    state: &SessionState,
    user_id: &str,
    tenant_id: &id_type::TenantId,
    entity_id: String,
    entity_type: EntityType,
) -> UserResult<
    Option<(
        id_type::OrganizationId,
        Option<id_type::MerchantId>,
        Option<id_type::ProfileId>,
    )>,
> {
    match entity_type {
        EntityType::Tenant => Err(UserErrors::InvalidRoleOperationWithMessage(
            "Tenant roles are not allowed for this operation".to_string(),
        )
        .into()),
        EntityType::Organization => {
            let Ok(org_id) =
                id_type::OrganizationId::try_from(std::borrow::Cow::from(entity_id.clone()))
            else {
                return Ok(None);
            };

            let user_roles = state
                .global_store
                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
                    user_id,
                    tenant_id,
                    org_id: Some(&org_id),
                    merchant_id: None,
                    profile_id: None,
                    entity_id: None,
                    version: None,
                    status: Some(UserStatus::InvitationSent),
                    limit: None,
                })
                .await
                .change_context(UserErrors::InternalServerError)?
                .into_iter()
                .collect::<HashSet<_>>();

            if user_roles.len() > 1 {
                return Ok(None);
            }

            if let Some(user_role) = user_roles.into_iter().next() {
                let (_entity_id, entity_type) = user_role
                    .get_entity_id_and_type()
                    .ok_or(UserErrors::InternalServerError)?;

                if entity_type != EntityType::Organization {
                    return Ok(None);
                }

                return Ok(Some((
                    user_role.org_id.ok_or(UserErrors::InternalServerError)?,
                    None,
                    None,
                )));
            }

            Ok(None)
        }
        EntityType::Merchant => {
            let Ok(merchant_id) = id_type::MerchantId::wrap(entity_id) else {
                return Ok(None);
            };

            let user_roles = state
                .global_store
                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
                    user_id,
                    tenant_id,
                    org_id: None,
                    merchant_id: Some(&merchant_id),
                    profile_id: None,
                    entity_id: None,
                    version: None,
                    status: Some(UserStatus::InvitationSent),
                    limit: None,
                })
                .await
                .change_context(UserErrors::InternalServerError)?
                .into_iter()
                .collect::<HashSet<_>>();

            if user_roles.len() > 1 {
                return Ok(None);
            }

            if let Some(user_role) = user_roles.into_iter().next() {
                let (_entity_id, entity_type) = user_role
                    .get_entity_id_and_type()
                    .ok_or(UserErrors::InternalServerError)?;

                if entity_type != EntityType::Merchant {
                    return Ok(None);
                }

                return Ok(Some((
                    user_role.org_id.ok_or(UserErrors::InternalServerError)?,
                    Some(merchant_id),
                    None,
                )));
            }

            Ok(None)
        }
        EntityType::Profile => {
            let Ok(profile_id) = id_type::ProfileId::try_from(std::borrow::Cow::from(entity_id))
            else {
                return Ok(None);
            };

            let user_roles = state
                .global_store
                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
                    user_id,
                    tenant_id: &state.tenant.tenant_id,
                    org_id: None,
                    merchant_id: None,
                    profile_id: Some(&profile_id),
                    entity_id: None,
                    version: None,
                    status: Some(UserStatus::InvitationSent),
                    limit: None,
                })
                .await
                .change_context(UserErrors::InternalServerError)?
                .into_iter()
                .collect::<HashSet<_>>();

            if user_roles.len() > 1 {
                return Ok(None);
            }

            if let Some(user_role) = user_roles.into_iter().next() {
                let (_entity_id, entity_type) = user_role
                    .get_entity_id_and_type()
                    .ok_or(UserErrors::InternalServerError)?;

                if entity_type != EntityType::Profile {
                    return Ok(None);
                }

                return Ok(Some((
                    user_role.org_id.ok_or(UserErrors::InternalServerError)?,
                    Some(
                        user_role
                            .merchant_id
                            .ok_or(UserErrors::InternalServerError)?,
                    ),
                    Some(profile_id),
                )));
            }

            Ok(None)
        }
    }
}


OpenAPI Block Path: components.schemas.XenditSplitRequest
{
  "oneOf": [
    {
      "type": "object",
      "required": [
        "multiple_splits"
      ],
      "properties": {
        "multiple_splits": {
          "$ref": "#/components/schemas/XenditMultipleSplitRequest"
        }
      }
    },
    {
      "type": "object",
      "required": [
        "single_split"
      ],
      "properties": {
        "single_split": {
          "$ref": "#/components/schemas/XenditSplitSubMerchantData"
        }
      }
    }
  ],
  "description": "Xendit Charge Request"
}


/// returns amount_captured and minor_amount_capturable
    pub fn get_amount_captured(
        &self,
    ) -> Result<(Option<i64>, Option<MinorUnit>), error_stack::Report<errors::ConnectorError>> {
        match &self.partial_approval {
            Some(partial_approval) => {
                let amount = utils::convert_back_amount_to_minor_units(
                    NUVEI_AMOUNT_CONVERTOR,
                    partial_approval.processed_amount.clone(),
                    partial_approval.processed_currency,
                )?;
                match self.transaction_type {
                    None => Ok((None, None)),
                    Some(NuveiTransactionType::Sale) => {
                        Ok((Some(MinorUnit::get_amount_as_i64(amount)), None))
                    }
                    Some(NuveiTransactionType::Auth) => Ok((None, Some(amount))),
                    Some(NuveiTransactionType::Auth3D) => {
                        Ok((Some(MinorUnit::get_amount_as_i64(amount)), None))
                    }
                    Some(NuveiTransactionType::InitAuth3D) => Ok((None, Some(amount))),
                    Some(NuveiTransactionType::Credit) => Ok((None, None)),
                    Some(NuveiTransactionType::Void) => Ok((None, None)),
                    Some(NuveiTransactionType::Settle) => Ok((None, None)),
                }
            }
            None => Ok((None, None)),
        }
    }


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


pub struct ReturnUrl {
    pub success_url: Option<String>,
    pub failure_url: Option<String>,
}


/// The latest git tag. If tags are not present in the repository, the short commit hash is used
/// instead. Refer to the [`git describe`](https://git-scm.com/docs/git-describe) documentation for
/// more details.
#[macro_export]
macro_rules! git_tag {
    () => {
        env!("VERGEN_GIT_DESCRIBE")
    };
}


pub(super) struct SerializeMap<T: Serializer> {
        inner: <serde_json::value::Serializer as Serializer>::SerializeMap,
        ser: T,
    }


impl NuveiPaymentsGenericResponse for Void {}

impl NuveiPaymentsGenericResponse for for Void


pub struct BluesnapCreateWalletToken {
    wallet_type: String,
    validation_url: Secret<String>,
    domain_name: String,
    display_name: Option<String>,
}


OpenAPI Block Path: components.schemas.DeRoutableConnectorChoice
{
  "type": "object",
  "description": "Routable Connector chosen for a payment",
  "required": [
    "gateway_name",
    "gateway_id"
  ],
  "properties": {
    "gateway_name": {
      "$ref": "#/components/schemas/RoutableConnectors"
    },
    "gateway_id": {
      "type": "string"
    }
  }
}


        is_latency_header_enabled: Option<bool>,
    ) -> RouterResponse<Self> {
        let captures = payment_data
            .get_multiple_capture_data()
            .and_then(|multiple_capture_data| {
                multiple_capture_data
                    .expand_captures
                    .and_then(|should_expand| {
                        should_expand.then_some(
                            multiple_capture_data
                                .get_all_captures()
                                .into_iter()
                                .cloned()
                                .collect(),
                        )
                    })
            });

        payments_to_payments_response(
            payment_data,
            captures,
            customer,
            auth_flow,
            base_url,
            &operation,
            connector_request_reference_id_config,
            connector_http_status_code,
            external_latency,
            is_latency_header_enabled,
        )
    }
}

#[cfg(feature = "v1")]
impl<F, Op, D> ToResponse<F, D, Op> for api::PaymentsSessionResponse
where
    F: Clone,
    Op: Debug,
    D: OperationSessionGetters<F>,
{
    #[allow(clippy::too_many_arguments)]
    fn generate_response(
        payment_data: D,
        _customer: Option<domain::Customer>,
        _auth_flow: services::AuthFlow,
        _base_url: &str,
        _operation: Op,
        _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
        _connector_http_status_code: Option<u16>,
        _external_latency: Option<u128>,
        _is_latency_header_enabled: Option<bool>,
    ) -> RouterResponse<Self> {
        Ok(services::ApplicationResponse::JsonWithHeaders((
            Self {
                session_token: payment_data.get_sessions_token(),
                payment_id: payment_data.get_payment_attempt().payment_id.clone(),
                client_secret: payment_data
                    .get_payment_intent()
                    .client_secret
                    .clone()
                    .get_required_value("client_secret")?
                    .into(),
            },
            vec![],
        )))
    }
}

#[cfg(feature = "v2")]
impl<F, Op, D> ToResponse<F, D, Op> for api::PaymentsSessionResponse
where
    F: Clone,
    Op: Debug,
    D: OperationSessionGetters<F>,
{
    #[allow(clippy::too_many_arguments)]
    fn generate_response(
        payment_data: D,
        _customer: Option<domain::Customer>,
        _base_url: &str,
        _operation: Op,
        _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
        _connector_http_status_code: Option<u16>,
        _external_latency: Option<u128>,
        _is_latency_header_enabled: Option<bool>,
        _merchant_context: &domain::MerchantContext,
    ) -> RouterResponse<Self> {
        Ok(services::ApplicationResponse::JsonWithHeaders((
            Self {
                session_token: payment_data.get_sessions_token(),
                payment_id: payment_data.get_payment_intent().id.clone(),
                vault_details: payment_data.get_optional_external_vault_session_details(),
            },
            vec![],
        )))
    }
}

#[cfg(feature = "v1")]
impl<F, Op, D> ToResponse<F, D, Op> for api::PaymentsDynamicTaxCalculationResponse
where
    F: Clone,
    Op: Debug,
    D: OperationSessionGetters<F>,
{
    #[allow(clippy::too_many_arguments)]
    fn generate_response(
        payment_data: D,
        _customer: Option<domain::Customer>,
        _auth_flow: services::AuthFlow,
        _base_url: &str,
        _operation: Op,
        _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
        _connector_http_status_code: Option<u16>,
        _external_latency: Option<u128>,
        _is_latency_header_enabled: Option<bool>,
    ) -> RouterResponse<Self> {
        let mut amount = payment_data.get_payment_intent().amount;
        let shipping_cost = payment_data.get_payment_intent().shipping_cost;
        if let Some(shipping_cost) = shipping_cost {
            amount = amount + shipping_cost;
        }
        let order_tax_amount = payment_data
            .get_payment_intent()
            .tax_details
            .clone()
            .and_then(|tax| {
                tax.payment_method_type
                    .map(|a| a.order_tax_amount)
                    .or_else(|| tax.default.map(|a| a.order_tax_amount))
            });
        if let Some(tax_amount) = order_tax_amount {
            amount = amount + tax_amount;
        }

        let currency = payment_data
            .get_payment_attempt()
            .currency
            .get_required_value("currency")?;

        Ok(services::ApplicationResponse::JsonWithHeaders((
            Self {
                net_amount: amount,
                payment_id: payment_data.get_payment_attempt().payment_id.clone(),
                order_tax_amount,
                shipping_cost,
                display_amount: api_models::payments::DisplayAmountOnSdk::foreign_try_from((
                    amount,
                    shipping_cost,
                    order_tax_amount,
                    currency,
                ))?,
            },
            vec![],
        )))
    }
}

#[cfg(feature = "v2")]
impl<F, Op, D> ToResponse<F, D, Op> for api::PaymentsIntentResponse
where
    F: Clone,
    Op: Debug,
    D: OperationSessionGetters<F>,
{
    #[allow(clippy::too_many_arguments)]
    fn generate_response(
        payment_data: D,
        _customer: Option<domain::Customer>,
        _base_url: &str,
        operation: Op,
        _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
        _connector_http_status_code: Option<u16>,
        _external_latency: Option<u128>,
        _is_latency_header_enabled: Option<bool>,
        _merchant_context: &domain::MerchantContext,
    ) -> RouterResponse<Self> {
        let payment_intent = payment_data.get_payment_intent();
        let client_secret = payment_data.get_client_secret();

        let is_cit_transaction = payment_intent.setup_future_usage.is_off_session();

        let mandate_type = if payment_intent.customer_present
            == common_enums::PresenceOfCustomerDuringPayment::Absent
        {
            Some(api::MandateTransactionType::RecurringMandateTransaction)
        } else if is_cit_transaction {
            Some(api::MandateTransactionType::NewMandateTransaction)
        } else {
            None
        };

        let payment_type = helpers::infer_payment_type(
            payment_intent.amount_details.order_amount.into(),
            mandate_type.as_ref(),
        );

        Ok(services::ApplicationResponse::JsonWithHeaders((
            Self {
                id: payment_intent.id.clone(),
                profile_id: payment_intent.profile_id.clone(),
                status: payment_intent.status,
                amount_details: api_models::payments::AmountDetailsResponse::foreign_from(
                    payment_intent.amount_details.clone(),
                ),
                client_secret: client_secret.clone(),
                merchant_reference_id: payment_intent.merchant_reference_id.clone(),
                routing_algorithm_id: payment_intent.routing_algorithm_id.clone(),
                capture_method: payment_intent.capture_method,
                authentication_type: payment_intent.authentication_type,
                billing: payment_intent
                    .billing_address
                    .clone()
                    .map(|billing| billing.into_inner())
                    .map(From::from),
                shipping: payment_intent
                    .shipping_address
                    .clone()
                    .map(|shipping| shipping.into_inner())
                    .map(From::from),
                customer_id: payment_intent.customer_id.clone(),
                customer_present: payment_intent.customer_present,
                description: payment_intent.description.clone(),
                return_url: payment_intent.return_url.clone(),
                setup_future_usage: payment_intent.setup_future_usage,
                apply_mit_exemption: payment_intent.apply_mit_exemption,
                statement_descriptor: payment_intent.statement_descriptor.clone(),
                order_details: payment_intent.order_details.clone().map(|order_details| {
                    order_details
                        .into_iter()
                        .map(|order_detail| order_detail.expose().convert_back())
                        .collect()
                }),
                allowed_payment_method_types: payment_intent.allowed_payment_method_types.clone(),
                metadata: payment_intent.metadata.clone(),
                connector_metadata: payment_intent.connector_metadata.clone(),
                feature_metadata: payment_intent
                    .feature_metadata
                    .clone()
                    .map(|feature_metadata| feature_metadata.convert_back()),
                payment_link_enabled: payment_intent.enable_payment_link,
                payment_link_config: payment_intent
                    .payment_link_config
                    .clone()
                    .map(ForeignFrom::foreign_from),
                request_incremental_authorization: payment_intent.request_incremental_authorization,
                split_txns_enabled: payment_intent.split_txns_enabled,
                expires_on: payment_intent.session_expiry,
                frm_metadata: payment_intent.frm_metadata.clone(),
                request_external_three_ds_authentication: payment_intent
                    .request_external_three_ds_authentication,
                payment_type,
            },
            vec![],
        )))
    }
}

#[cfg(feature = "v2")]
impl<F, Op, D> ToResponse<F, D, Op> for api::PaymentAttemptListResponse
where
    F: Clone,
    Op: Debug,
    D: OperationSessionGetters<F>,
{
    #[allow(clippy::too_many_arguments)]
    fn generate_response(
        payment_data: D,
        _customer: Option<domain::Customer>,
        _base_url: &str,
        _operation: Op,
        _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
        _connector_http_status_code: Option<u16>,
        _external_latency: Option<u128>,
        _is_latency_header_enabled: Option<bool>,
        _merchant_context: &domain::MerchantContext,
    ) -> RouterResponse<Self> {
        Ok(services::ApplicationResponse::JsonWithHeaders((
            Self {
                payment_attempt_list: payment_data
                    .list_payments_attempts()
                    .iter()
                    .map(api_models::payments::PaymentAttemptResponse::foreign_from)
                    .collect(),
            },
            vec![],
        )))
    }
}

#[cfg(feature = "v2")]
impl<F> GenerateResponse<api_models::payments::PaymentsResponse>
    for hyperswitch_domain_models::payments::PaymentConfirmData<F>
where
    F: Clone,
{
    fn generate_response(
        self,
        state: &SessionState,
        connector_http_status_code: Option<u16>,
        external_latency: Option<u128>,
        is_latency_header_enabled: Option<bool>,
        merchant_context: &domain::MerchantContext,
        profile: &domain::Profile,
        connector_response_data: Option<common_types::domain::ConnectorResponseData>,
    ) -> RouterResponse<api_models::payments::PaymentsResponse> {
        let payment_intent = self.payment_intent;
        let payment_attempt = self.payment_attempt;

        let amount = api_models::payments::PaymentAmountDetailsResponse::foreign_from((
            &payment_intent.amount_details,
            &payment_attempt.amount_details,
        ));

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

        let merchant_connector_id = payment_attempt.merchant_connector_id.clone();

        let error = payment_attempt
            .error
            .as_ref()
            .map(api_models::payments::ErrorDetails::foreign_from);

        let payment_address = self.payment_address;

        let raw_connector_response =
            connector_response_data.and_then(|data| data.raw_connector_response);

        let payment_method_data =
            Some(api_models::payments::PaymentMethodDataResponseWithBilling {
                payment_method_data: None,
                billing: payment_address
                    .get_request_payment_method_billing()
                    .cloned()
                    .map(From::from),
            });

        // TODO: Add support for other next actions, currently only supporting redirect to url
        let redirect_to_url = payment_intent.create_start_redirection_url(
            &state.base_url,
            merchant_context
                .get_merchant_account()
                .publishable_key
                .clone(),
        )?;

        let next_action_containing_wait_screen =
            wait_screen_next_steps_check(payment_attempt.clone())?;

        let next_action = if payment_intent.status.is_in_terminal_state() {
            None
        } else {
            payment_attempt
                .redirection_data
                .as_ref()
                .map(|_| api_models::payments::NextActionData::RedirectToUrl { redirect_to_url })
                .or(next_action_containing_wait_screen.map(|wait_screen_data| {
                    api_models::payments::NextActionData::WaitScreenInformation {
                        display_from_timestamp: wait_screen_data.display_from_timestamp,
                        display_to_timestamp: wait_screen_data.display_to_timestamp,
                        poll_config: wait_screen_data.poll_config,
                    }
                }))
        };

        let connector_token_details = payment_attempt
            .connector_token_details
            .and_then(Option::<api_models::payments::ConnectorTokenDetails>::foreign_from);

        let return_url = payment_intent
            .return_url
            .clone()
            .or(profile.return_url.clone());

        let headers = connector_http_status_code
            .map(|status_code| {
                vec![(
                    X_CONNECTOR_HTTP_STATUS_CODE.to_string(),
                    Maskable::new_normal(status_code.to_string()),
                )]
            })
            .unwrap_or_default();

        let response = api_models::payments::PaymentsResponse {
            id: payment_intent.id.clone(),
            status: payment_intent.status,
            amount,
            customer_id: payment_intent.customer_id.clone(),
            connector: Some(connector),
            created: payment_intent.created_at,
            payment_method_data,
            payment_method_type: Some(payment_attempt.payment_method_type),
            payment_method_subtype: Some(payment_attempt.payment_method_subtype),
            next_action,
            connector_transaction_id: payment_attempt.connector_payment_id.clone(),
            connector_reference_id: payment_attempt.connector_response_reference_id.clone(),
            connector_token_details,
            merchant_connector_id,
            browser_info: None,
            error,
            return_url,
            authentication_type: payment_intent.authentication_type,
            authentication_type_applied: Some(payment_attempt.authentication_type),
            payment_method_id: payment_attempt.payment_method_id,
            attempts: None,
            billing: None,  //TODO: add this
            shipping: None, //TODO: add this
            is_iframe_redirection_enabled: None,
            merchant_reference_id: payment_intent.merchant_reference_id.clone(),
            raw_connector_response,
            feature_metadata: None,
        };

        Ok(services::ApplicationResponse::JsonWithHeaders((
            response, headers,
        )))
    }
}

#[cfg(feature = "v2")]
impl<F> GenerateResponse<api_models::payments::PaymentsResponse>
    for hyperswitch_domain_models::payments::PaymentStatusData<F>
where
    F: Clone,
{
    fn generate_response(
        self,
        state: &SessionState,
        connector_http_status_code: Option<u16>,
        external_latency: Option<u128>,
        is_latency_header_enabled: Option<bool>,
        merchant_context: &domain::MerchantContext,
        profile: &domain::Profile,
        connector_response_data: Option<common_types::domain::ConnectorResponseData>,
    ) -> RouterResponse<api_models::payments::PaymentsResponse> {
        let payment_intent = self.payment_intent;
        let payment_attempt = &self.payment_attempt;

        let amount = api_models::payments::PaymentAmountDetailsResponse::foreign_from((
            &payment_intent.amount_details,
            &payment_attempt.amount_details,
        ));

        let connector = payment_attempt.connector.clone();

        let merchant_connector_id = payment_attempt.merchant_connector_id.clone();

        let error = payment_attempt
            .error
            .as_ref()
            .map(api_models::payments::ErrorDetails::foreign_from);
        let attempts = self.attempts.as_ref().map(|attempts| {
            attempts
                .iter()
                .map(api_models::payments::PaymentAttemptResponse::foreign_from)
                .collect()
        });

        let payment_method_data =
            Some(api_models::payments::PaymentMethodDataResponseWithBilling {
                payment_method_data: None,
                billing: self
                    .payment_address
                    .get_request_payment_method_billing()
                    .cloned()
                    .map(From::from),
            });

        let raw_connector_response =
            connector_response_data.and_then(|data| data.raw_connector_response);

        let connector_token_details = self
            .payment_attempt
            .connector_token_details
            .clone()
            .and_then(Option::<api_models::payments::ConnectorTokenDetails>::foreign_from);

        let return_url = payment_intent.return_url.or(profile.return_url.clone());

        let headers = connector_http_status_code
            .map(|status_code| {
                vec![(
                    X_CONNECTOR_HTTP_STATUS_CODE.to_string(),
                    Maskable::new_normal(status_code.to_string()),
                )]
            })
            .unwrap_or_default();

        let response = api_models::payments::PaymentsResponse {
            id: payment_intent.id.clone(),
            status: payment_intent.status,
            amount,
            customer_id: payment_intent.customer_id.clone(),
            connector,
            billing: self
                .payment_address
                .get_payment_billing()
                .cloned()
                .map(From::from),
            shipping: self.payment_address.get_shipping().cloned().map(From::from),
            created: payment_intent.created_at,
            payment_method_data,
            payment_method_type: Some(payment_attempt.payment_method_type),
            payment_method_subtype: Some(payment_attempt.payment_method_subtype),
            connector_transaction_id: payment_attempt.connector_payment_id.clone(),
            connector_reference_id: payment_attempt.connector_response_reference_id.clone(),
            merchant_connector_id,
            browser_info: None,
            connector_token_details,
            payment_method_id: payment_attempt.payment_method_id.clone(),
            error,
            authentication_type_applied: payment_attempt.authentication_applied,
            authentication_type: payment_intent.authentication_type,
            next_action: None,
            attempts,
            return_url,
            is_iframe_redirection_enabled: payment_intent.is_iframe_redirection_enabled,
            merchant_reference_id: payment_intent.merchant_reference_id.clone(),
            raw_connector_response,
            feature_metadata: None,
        };

        Ok(services::ApplicationResponse::JsonWithHeaders((
            response, headers,
        )))
    }
}

#[cfg(feature = "v2")]
impl<F> GenerateResponse<api_models::payments::PaymentAttemptResponse>
    for hyperswitch_domain_models::payments::PaymentAttemptRecordData<F>
where
    F: Clone,
{
    fn generate_response(
        self,
        _state: &SessionState,
        _connector_http_status_code: Option<u16>,
        _external_latency: Option<u128>,
        _is_latency_header_enabled: Option<bool>,
        _merchant_context: &domain::MerchantContext,
        _profile: &domain::Profile,
        _connector_response_data: Option<common_types::domain::ConnectorResponseData>,
    ) -> RouterResponse<api_models::payments::PaymentAttemptResponse> {
        let payment_attempt = self.payment_attempt;
        let response = api_models::payments::PaymentAttemptResponse::foreign_from(&payment_attempt);
        Ok(services::ApplicationResponse::JsonWithHeaders((
            response,
            vec![],
        )))
    }
}

#[cfg(feature = "v2")]
impl<F> GenerateResponse<api_models::payments::PaymentAttemptRecordResponse>
    for hyperswitch_domain_models::payments::PaymentAttemptRecordData<F>
where
    F: Clone,
{
    fn generate_response(
        self,
        _state: &SessionState,
        _connector_http_status_code: Option<u16>,
        _external_latency: Option<u128>,
        _is_latency_header_enabled: Option<bool>,
        _merchant_context: &domain::MerchantContext,
        _profile: &domain::Profile,
        _connector_response_data: Option<common_types::domain::ConnectorResponseData>,
    ) -> RouterResponse<api_models::payments::PaymentAttemptRecordResponse> {
        let payment_attempt = self.payment_attempt;
        let payment_intent = self.payment_intent;
        let response = api_models::payments::PaymentAttemptRecordResponse {
            id: payment_attempt.id.clone(),
            status: payment_attempt.status,
            amount: payment_attempt.amount_details.get_net_amount(),
            payment_intent_feature_metadata: payment_intent
                .feature_metadata
                .as_ref()
                .map(api_models::payments::FeatureMetadata::foreign_from),
            payment_attempt_feature_metadata: payment_attempt
                .feature_metadata
                .as_ref()
                .map(api_models::payments::PaymentAttemptFeatureMetadata::foreign_from),
            error_details: payment_attempt
                .error
                .map(api_models::payments::RecordAttemptErrorDetails::from),
            created_at: payment_attempt.created_at,
        };
        Ok(services::ApplicationResponse::JsonWithHeaders((
            response,
            vec![],
        )))
    }
}

#[cfg(feature = "v1")]
impl<F, Op, D> ToResponse<F, D, Op> for api::PaymentsPostSessionTokensResponse
where
    F: Clone,
    Op: Debug,
    D: OperationSessionGetters<F>,
{
    fn generate_response(
        payment_data: D,
        _customer: Option<domain::Customer>,
        _auth_flow: services::AuthFlow,
        _base_url: &str,
        _operation: Op,
        _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
        _connector_http_status_code: Option<u16>,
        _external_latency: Option<u128>,
        _is_latency_header_enabled: Option<bool>,
    ) -> RouterResponse<Self> {
        let papal_sdk_next_action =
            paypal_sdk_next_steps_check(payment_data.get_payment_attempt().clone())?;
        let next_action = papal_sdk_next_action.map(|paypal_next_action_data| {
            api_models::payments::NextActionData::InvokeSdkClient {
                next_action_data: paypal_next_action_data,
            }
        });
        Ok(services::ApplicationResponse::JsonWithHeaders((
            Self {
                payment_id: payment_data.get_payment_intent().payment_id.clone(),
                next_action,
                status: payment_data.get_payment_intent().status,
            },
            vec![],
        )))
    }
}

#[cfg(feature = "v1")]
impl<F, Op, D> ToResponse<F, D, Op> for api::PaymentsUpdateMetadataResponse
where
    F: Clone,
    Op: Debug,
    D: OperationSessionGetters<F>,
{
    fn generate_response(
        payment_data: D,
        _customer: Option<domain::Customer>,
        _auth_flow: services::AuthFlow,
        _base_url: &str,
        _operation: Op,
        _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
        _connector_http_status_code: Option<u16>,
        _external_latency: Option<u128>,
        _is_latency_header_enabled: Option<bool>,
    ) -> RouterResponse<Self> {
        Ok(services::ApplicationResponse::JsonWithHeaders((
            Self {
                payment_id: payment_data.get_payment_intent().payment_id.clone(),
                metadata: payment_data
                    .get_payment_intent()
                    .metadata
                    .clone()
                    .map(Secret::new),
            },
            vec![],
        )))
    }
}

impl ForeignTryFrom<(MinorUnit, Option<MinorUnit>, Option<MinorUnit>, Currency)>
    for api_models::payments::DisplayAmountOnSdk
{
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn foreign_try_from(
        (net_amount, shipping_cost, order_tax_amount, currency): (
            MinorUnit,
            Option<MinorUnit>,
            Option<MinorUnit>,
            Currency,
        ),
    ) -> Result<Self, Self::Error> {
        let major_unit_convertor = StringMajorUnitForConnector;

        let sdk_net_amount = major_unit_convertor
            .convert(net_amount, currency)
            .change_context(errors::ApiErrorResponse::PreconditionFailed {
                message: "Failed to convert net_amount to base unit".to_string(),
            })
            .attach_printable("Failed to convert net_amount to string major unit")?;

        let sdk_shipping_cost = shipping_cost
            .map(|cost| {
                major_unit_convertor
                    .convert(cost, currency)
                    .change_context(errors::ApiErrorResponse::PreconditionFailed {
                        message: "Failed to convert shipping_cost to base unit".to_string(),
                    })
                    .attach_printable("Failed to convert shipping_cost to string major unit")
            })
            .transpose()?;

        let sdk_order_tax_amount = order_tax_amount
            .map(|cost| {
                major_unit_convertor
                    .convert(cost, currency)
                    .change_context(errors::ApiErrorResponse::PreconditionFailed {
                        message: "Failed to convert order_tax_amount to base unit".to_string(),
                    })
                    .attach_printable("Failed to convert order_tax_amount to string major unit")
            })
            .transpose()?;
        Ok(Self {
            net_amount: sdk_net_amount,
            shipping_cost: sdk_shipping_cost,
            order_tax_amount: sdk_order_tax_amount,
        })
    }
}

#[cfg(feature = "v1")]
impl<F, Op, D> ToResponse<F, D, Op> for api::VerifyResponse
where
    F: Clone,
    Op: Debug,
    D: OperationSessionGetters<F>,
{
    #[cfg(feature = "v2")]
    #[allow(clippy::too_many_arguments)]
    fn generate_response(
        _data: D,
        _customer: Option<domain::Customer>,
        _auth_flow: services::AuthFlow,
        _base_url: &str,
        _operation: Op,
        _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
        _connector_http_status_code: Option<u16>,
        _external_latency: Option<u128>,
        _is_latency_header_enabled: Option<bool>,
    ) -> RouterResponse<Self> {
        todo!()
    }

    #[cfg(feature = "v1")]
    #[allow(clippy::too_many_arguments)]
    fn generate_response(
        payment_data: D,
        customer: Option<domain::Customer>,
        _auth_flow: services::AuthFlow,
        _base_url: &str,
        _operation: Op,
        _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
        _connector_http_status_code: Option<u16>,
        _external_latency: Option<u128>,
        _is_latency_header_enabled: Option<bool>,
    ) -> RouterResponse<Self> {
        let additional_payment_method_data: Option<api_models::payments::AdditionalPaymentData> =
            payment_data
                .get_payment_attempt()
                .payment_method_data
                .clone()
                .map(|data| data.parse_value("payment_method_data"))
                .transpose()
                .change_context(errors::ApiErrorResponse::InvalidDataValue {
                    field_name: "payment_method_data",
                })?;
        let payment_method_data_response =
            additional_payment_method_data.map(api::PaymentMethodDataResponse::from);
        Ok(services::ApplicationResponse::JsonWithHeaders((
            Self {
                verify_id: Some(payment_data.get_payment_intent().payment_id.clone()),
                merchant_id: Some(payment_data.get_payment_intent().merchant_id.clone()),
                client_secret: payment_data
                    .get_payment_intent()
                    .client_secret
                    .clone()
                    .map(Secret::new),
                customer_id: customer.as_ref().map(|x| x.customer_id.clone()),
                email: customer
                    .as_ref()
                    .and_then(|cus| cus.email.as_ref().map(|s| s.to_owned())),
                name: customer
                    .as_ref()
                    .and_then(|cus| cus.name.as_ref().map(|s| s.to_owned())),
                phone: customer
                    .as_ref()
                    .and_then(|cus| cus.phone.as_ref().map(|s| s.to_owned())),
                mandate_id: payment_data
                    .get_mandate_id()
                    .and_then(|mandate_ids| mandate_ids.mandate_id.clone()),
                payment_method: payment_data.get_payment_attempt().payment_method,
                payment_method_data: payment_method_data_response,
                payment_token: payment_data.get_token().map(ToString::to_string),
                error_code: payment_data.get_payment_attempt().clone().error_code,
                error_message: payment_data.get_payment_attempt().clone().error_message,
            },
            vec![],
        )))
    }
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
// try to use router data here so that already validated things , we don't want to repeat the validations.
// Add internal value not found and external value not found so that we can give 500 / Internal server error for internal value not found
#[allow(clippy::too_many_arguments)]
pub fn payments_to_payments_response<Op, F: Clone, D>(
    _payment_data: D,
    _captures: Option<Vec<storage::Capture>>,
    _customer: Option<domain::Customer>,
    _auth_flow: services::AuthFlow,
    _base_url: &str,
    _operation: &Op,
    _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
    _connector_http_status_code: Option<u16>,
    _external_latency: Option<u128>,
    _is_latency_header_enabled: Option<bool>,
) -> RouterResponse<api_models::payments::PaymentsResponse>
where
    Op: Debug,
    D: OperationSessionGetters<F>,
{
    todo!()
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
// try to use router data here so that already validated things , we don't want to repeat the validations.
// Add internal value not found and external value not found so that we can give 500 / Internal server error for internal value not found
#[allow(clippy::too_many_arguments)]
pub fn payments_to_payments_response<Op, F: Clone, D>(
    payment_data: D,
    captures: Option<Vec<storage::Capture>>,
    customer: Option<domain::Customer>,
    _auth_flow: services::AuthFlow,
    base_url: &str,
    operation: &Op,
    connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
    connector_http_status_code: Option<u16>,
    external_latency: Option<u128>,
    _is_latency_header_enabled: Option<bool>,
) -> RouterResponse<api::PaymentsResponse>
where
    Op: Debug,
    D: OperationSessionGetters<F>,
{
    use std::ops::Not;

    use hyperswitch_interfaces::consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE};

    let payment_attempt = payment_data.get_payment_attempt().clone();
    let payment_intent = payment_data.get_payment_intent().clone();
    let payment_link_data = payment_data.get_payment_link_data();

    let currency = payment_attempt
        .currency
        .as_ref()
        .get_required_value("currency")?;
    let amount = currency
        .to_currency_base_unit(
            payment_attempt
                .net_amount
                .get_total_amount()
                .get_amount_as_i64(),
        )
        .change_context(errors::ApiErrorResponse::InvalidDataValue {
            field_name: "amount",
        })?;
    let mandate_id = payment_attempt.mandate_id.clone();

    let refunds_response = payment_data.get_refunds().is_empty().not().then(|| {
        payment_data
            .get_refunds()
            .into_iter()
            .map(ForeignInto::foreign_into)
            .collect()
    });

    let disputes_response = payment_data.get_disputes().is_empty().not().then(|| {
        payment_data
            .get_disputes()
            .into_iter()
            .map(ForeignInto::foreign_into)
            .collect()
    });

    let incremental_authorizations_response =
        payment_data.get_authorizations().is_empty().not().then(|| {
            payment_data
                .get_authorizations()
                .into_iter()
                .map(ForeignInto::foreign_into)
                .collect()
        });

    let external_authentication_details = payment_data
        .get_authentication()
        .map(ForeignInto::foreign_into);

    let attempts_response = payment_data.get_attempts().map(|attempts| {
        attempts
            .into_iter()
            .map(ForeignInto::foreign_into)
            .collect()
    });

    let captures_response = captures.map(|captures| {
        captures
            .into_iter()
            .map(ForeignInto::foreign_into)
            .collect()
    });

    let merchant_id = payment_attempt.merchant_id.to_owned();
    let payment_method_type = payment_attempt
        .payment_method_type
        .as_ref()
        .map(ToString::to_string)
        .unwrap_or("".to_owned());
    let payment_method = payment_attempt
        .payment_method
        .as_ref()
        .map(ToString::to_string)
        .unwrap_or("".to_owned());
    let additional_payment_method_data: Option<api_models::payments::AdditionalPaymentData> =
        payment_attempt
            .payment_method_data
            .clone()
            .and_then(|data| match data {
                serde_json::Value::Null => None, // This is to handle the case when the payment_method_data is null
                _ => Some(data.parse_value("AdditionalPaymentData")),
            })
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse the AdditionalPaymentData from payment_attempt.payment_method_data")?;

    let surcharge_details =
        payment_attempt
            .net_amount
            .get_surcharge_amount()
            .map(|surcharge_amount| RequestSurchargeDetails {
                surcharge_amount,
                tax_amount: payment_attempt.net_amount.get_tax_on_surcharge(),
            });
    let merchant_decision = payment_intent.merchant_decision.to_owned();
    let frm_message = payment_data.get_frm_message().map(FrmMessage::foreign_from);

    let payment_method_data =
        additional_payment_method_data.map(api::PaymentMethodDataResponse::from);

    let payment_method_data_response = (payment_method_data.is_some()
        || payment_data
            .get_address()
            .get_request_payment_method_billing()
            .is_some())
    .then_some(api_models::payments::PaymentMethodDataResponseWithBilling {
        payment_method_data,
        billing: payment_data
            .get_address()
            .get_request_payment_method_billing()
            .cloned()
            .map(From::from),
    });

    let mut headers = connector_http_status_code
        .map(|status_code| {
            vec![(
                X_CONNECTOR_HTTP_STATUS_CODE.to_string(),
                Maskable::new_normal(status_code.to_string()),
            )]
        })
        .unwrap_or_default();
    if let Some(payment_confirm_source) = payment_intent.payment_confirm_source {
        headers.push((
            X_PAYMENT_CONFIRM_SOURCE.to_string(),
            Maskable::new_normal(payment_confirm_source.to_string()),
        ))
    }

    // For the case when we don't have Customer data directly stored in Payment intent
    let customer_table_response: Option<CustomerDetailsResponse> =
        customer.as_ref().map(ForeignInto::foreign_into);

    // If we have customer data in Payment Intent and if the customer is not deleted, We are populating the Retrieve response from the
    // same. If the customer is deleted then we use the customer table to populate customer details
    let customer_details_response =
        if let Some(customer_details_raw) = payment_intent.customer_details.clone() {
            let customer_details_encrypted =
                serde_json::from_value::<CustomerData>(customer_details_raw.into_inner().expose());
            if let Ok(customer_details_encrypted_data) = customer_details_encrypted {
                Some(CustomerDetailsResponse {
                    id: customer_table_response
                        .as_ref()
                        .and_then(|customer_data| customer_data.id.clone()),
                    name: customer_table_response
                        .as_ref()
                        .and_then(|customer_data| customer_data.name.clone())
                        .or(customer_details_encrypted_data
                            .name
                            .or(customer.as_ref().and_then(|customer| {
                                customer.name.as_ref().map(|name| name.clone().into_inner())
                            }))),
                    email: customer_table_response
                        .as_ref()
                        .and_then(|customer_data| customer_data.email.clone())
                        .or(customer_details_encrypted_data.email.or(customer
                            .as_ref()
                            .and_then(|customer| customer.email.clone().map(pii::Email::from)))),
                    phone: customer_table_response
                        .as_ref()
                        .and_then(|customer_data| customer_data.phone.clone())
                        .or(customer_details_encrypted_data
                            .phone
                            .or(customer.as_ref().and_then(|customer| {
                                customer
                                    .phone
                                    .as_ref()
                                    .map(|phone| phone.clone().into_inner())
                            }))),
                    phone_country_code: customer_table_response
                        .as_ref()
                        .and_then(|customer_data| customer_data.phone_country_code.clone())
                        .or(customer_details_encrypted_data
                            .phone_country_code
                            .or(customer
                                .as_ref()
                                .and_then(|customer| customer.phone_country_code.clone()))),
                })
            } else {
                customer_table_response
            }
        } else {
            customer_table_response
        };

    headers.extend(
        external_latency
            .map(|latency| {
                vec![(
                    X_HS_LATENCY.to_string(),
                    Maskable::new_normal(latency.to_string()),
                )]
            })
            .unwrap_or_default(),
    );
    let connector_name = payment_attempt.connector.as_deref().unwrap_or_default();
    let router_return_url = helpers::create_redirect_url(
        &base_url.to_string(),
        &payment_attempt,
        connector_name,
        payment_data.get_creds_identifier(),
    );

    let output = if payments::is_start_pay(&operation)
        && payment_attempt.authentication_data.is_some()
    {
        let redirection_data = payment_attempt
            .authentication_data
            .clone()
            .get_required_value("redirection_data")?;

        let form: RedirectForm = serde_json::from_value(redirection_data)
            .map_err(|_| errors::ApiErrorResponse::InternalServerError)?;

        services::ApplicationResponse::Form(Box::new(services::RedirectionFormData {
            redirect_form: form,
            payment_method_data: payment_data.get_payment_method_data().cloned(),
            amount,
            currency: currency.to_string(),
        }))
    } else {
        let mut next_action_response = None;

        // Early exit for terminal payment statuses - don't evaluate next_action at all
        if payment_intent.status.is_in_terminal_state() {
            next_action_response = None;
        } else {
            let bank_transfer_next_steps = bank_transfer_next_steps_check(payment_attempt.clone())?;

            let next_action_voucher = voucher_next_steps_check(payment_attempt.clone())?;

            let next_action_mobile_payment = mobile_payment_next_steps_check(&payment_attempt)?;

            let next_action_containing_qr_code_url =
                qr_code_next_steps_check(payment_attempt.clone())?;


impl api::PaymentAuthorizeSessionToken for Nuvei {}

impl api::PaymentAuthorizeSessionToken for for Nuvei


pub struct Payone {
    #[cfg(feature = "payouts")]
    amount_converter: &'static (dyn AmountConvertor<Output = MinorUnit> + Sync),
}


pub struct RelayWebhooks;


impl api::PaymentsPreProcessing for Nordea {}

impl api::PaymentsPreProcessing for for Nordea


OpenAPI Block Path: components.schemas.CardResponse
{
  "type": "object",
  "properties": {
    "last4": {
      "type": "string",
      "nullable": true
    },
    "card_type": {
      "type": "string",
      "nullable": true
    },
    "card_network": {
      "allOf": [
        {
          "$ref": "#/components/schemas/CardNetwork"
        }
      ],
      "nullable": true
    },
    "card_issuer": {
      "type": "string",
      "nullable": true
    },
    "card_issuing_country": {
      "type": "string",
      "nullable": true
    },
    "card_isin": {
      "type": "string",
      "nullable": true
    },
    "card_extended_bin": {
      "type": "string",
      "nullable": true
    },
    "card_exp_month": {
      "type": "string",
      "nullable": true
    },
    "card_exp_year": {
      "type": "string",
      "nullable": true
    },
    "card_holder_name": {
      "type": "string",
      "nullable": true
    },
    "payment_checks": {
      "nullable": true
    },
    "authentication_data": {
      "nullable": true
    }
  }
}


impl GetPaymentMethodType for CardRedirectData {
    fn get_payment_method_type(&self) -> api_enums::PaymentMethodType {
        match self {
            Self::Knet {} => api_enums::PaymentMethodType::Knet,
            Self::Benefit {} => api_enums::PaymentMethodType::Benefit,
            Self::MomoAtm {} => api_enums::PaymentMethodType::MomoAtm,
            Self::CardRedirect {} => api_enums::PaymentMethodType::CardRedirect,
        }
    }
}

impl GetPaymentMethodType for for CardRedirectData


impl ConnectorCommon for Opayo {
    fn id(&self) -> &'static str {
        "opayo"
    }

    fn common_get_content_type(&self) -> &'static str {
        "application/json"
    }
    fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
        connectors.opayo.base_url.as_ref()
    }

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let auth = opayo::OpayoAuthType::try_from(auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        Ok(vec![(
            headers::AUTHORIZATION.to_string(),
            auth.api_key.expose().into_masked(),
        )])
    }

    fn build_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        let response: opayo::OpayoErrorResponse =
            res.response
                .parse_struct("OpayoErrorResponse")
                .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_error_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: response.code,
            message: response.message,
            reason: response.reason,
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}


pub struct PaypalIncrementalAuthRequest {
    amount: OrderAmount,
}


pub struct PmFetched;


impl api::PaymentVoid for Rapyd {}


pub struct GetSearchRequest {
    pub offset: i64,
    pub count: i64,
    pub query: String,
    #[serde(default)]
    pub filters: Option<SearchFilters>,
    #[serde(default)]
    pub time_range: Option<TimeRange>,
}


pub async fn refund_payment(
    state: SessionState,
    req: types::DummyConnectorRefundRequest,
) -> types::DummyConnectorResponse<types::DummyConnectorRefundResponse> {
    utils::tokio_mock_sleep(
        state.conf.dummy_connector.refund_duration,
        state.conf.dummy_connector.refund_tolerance,
    )
    .await;

    let payment_id = req
        .payment_id
        .get_required_value("payment_id")
        .change_context(errors::DummyConnectorErrors::MissingRequiredField {
            field_name: "payment_id",
        })?;

    let mut payment_data =
        utils::get_payment_data_from_payment_id(&state, payment_id.get_string_repr().to_owned())
            .await?;

    payment_data.is_eligible_for_refund(req.amount)?;

    let refund_id = generate_id_with_default_len(consts::REFUND_ID_PREFIX);
    payment_data.eligible_amount -= req.amount;

    utils::store_data_in_redis(
        &state,
        payment_id.get_string_repr().to_owned(),
        payment_data.to_owned(),
        state.conf.dummy_connector.payment_ttl,
    )
    .await?;

    let refund_data = types::DummyConnectorRefundResponse::new(
        types::DummyConnectorStatus::Succeeded,
        refund_id.to_owned(),
        payment_data.currency,
        common_utils::date_time::now(),
        payment_data.amount,
        req.amount,
    );

    utils::store_data_in_redis(
        &state,
        refund_id,
        refund_data.to_owned(),
        state.conf.dummy_connector.refund_ttl,
    )
    .await?;
    Ok(api::ApplicationResponse::Json(refund_data))
}


pub async fn create(conf: &KafkaSettings) -> MQResult<Self> {
        Ok(Self {
            producer: Arc::new(RdKafkaProducer(
                ThreadedProducer::from_config(
                    rdkafka::ClientConfig::new().set("bootstrap.servers", conf.brokers.join(",")),
                )
                .change_context(KafkaError::InitializationError)?,
            )),

            fraud_check_analytics_topic: conf.fraud_check_analytics_topic.clone(),
            intent_analytics_topic: conf.intent_analytics_topic.clone(),
            attempt_analytics_topic: conf.attempt_analytics_topic.clone(),
            refund_analytics_topic: conf.refund_analytics_topic.clone(),
            api_logs_topic: conf.api_logs_topic.clone(),
            connector_logs_topic: conf.connector_logs_topic.clone(),
            outgoing_webhook_logs_topic: conf.outgoing_webhook_logs_topic.clone(),
            dispute_analytics_topic: conf.dispute_analytics_topic.clone(),
            audit_events_topic: conf.audit_events_topic.clone(),
            #[cfg(feature = "payouts")]
            payout_analytics_topic: conf.payout_analytics_topic.clone(),
            consolidated_events_topic: conf.consolidated_events_topic.clone(),
            authentication_analytics_topic: conf.authentication_analytics_topic.clone(),
            ckh_database_name: None,
            routing_logs_topic: conf.routing_logs_topic.clone(),
            revenue_recovery_topic: conf.revenue_recovery_topic.clone(),
        })
    }