    connector_data: &api::ConnectorData,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    let customer_details = payout_data.customer_details.to_owned();
    let connector_name = connector_data.connector_name.to_string();

    // Create the connector label using {profile_id}_{connector_name}
    let connector_label = format!(
        "{}_{}",
        payout_data.profile_id.get_string_repr(),
        connector_name
    );
    let (should_call_connector, _connector_customer_id) =
        helpers::should_call_payout_connector_create_customer(
            state,
            connector_data,
            &customer_details,
            &connector_label,
        );
    if should_call_connector {
        // 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::PoRecipient,
            types::PayoutsData,
            types::PayoutsResponseData,
        > = connector_data.connector.get_connector_integration();

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

        match router_resp.response {
            Ok(recipient_create_data) => {
                let db = &*state.store;
                if let Some(customer) = customer_details {
                    if let Some(updated_customer) =
                        customers::update_connector_customer_in_customers(
                            &connector_label,
                            Some(&customer),
                            recipient_create_data.connector_payout_id.clone(),
                        )
                        .await
                    {
                        #[cfg(feature = "v1")]
                        {
                            let customer_id = customer.customer_id.to_owned();
                            payout_data.customer_details = Some(
                                db.update_customer_by_customer_id_merchant_id(
                                    &state.into(),
                                    customer_id,
                                    merchant_context.get_merchant_account().get_id().to_owned(),
                                    customer,
                                    updated_customer,
                                    merchant_context.get_merchant_key_store(),
                                    merchant_context.get_merchant_account().storage_scheme,
                                )
                                .await
                                .change_context(errors::ApiErrorResponse::InternalServerError)
                                .attach_printable("Error updating customers in db")?,
                            );
                        }

                        #[cfg(feature = "v2")]
                        {
                            let customer_id = customer.get_id().clone();
                            payout_data.customer_details = Some(
                                db.update_customer_by_global_id(
                                    &state.into(),
                                    &customer_id,
                                    customer,
                                    updated_customer,
                                    key_store,
                                    merchant_account.storage_scheme,
                                )
                                .await
                                .change_context(errors::ApiErrorResponse::InternalServerError)
                                .attach_printable("Error updating customers in db")?,
                            );
                        }
                    }
                }

                // Add next step to ProcessTracker
                if recipient_create_data.should_add_next_step_to_process_tracker {
                    add_external_account_addition_task(
                        &*state.store,
                        payout_data,
                        common_utils::date_time::now().saturating_add(Duration::seconds(consts::STRIPE_ACCOUNT_ONBOARDING_DELAY_IN_SECONDS)),
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed while adding attach_payout_account_workflow workflow to process tracker")?;

                    // Update payout status in DB
                    let status = recipient_create_data
                        .status
                        .unwrap_or(api_enums::PayoutStatus::RequiresVendorAccountCreation);
                    let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                        connector_payout_id: payout_data
                            .payout_attempt
                            .connector_payout_id
                            .to_owned(),
                        status,
                        error_code: None,
                        error_message: None,
                        is_eligible: recipient_create_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")?;

                    // Helps callee functions skip the execution
                    payout_data.should_terminate = true;
                } else if let Some(status) = recipient_create_data.status {
                    let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                        connector_payout_id: payout_data
                            .payout_attempt
                            .connector_payout_id
                            .to_owned(),
                        status,
                        error_code: None,
                        error_message: None,
                        is_eligible: recipient_create_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",
                        })?,
                };
                let db = &*state.store;
                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(())
}

#[cfg(feature = "v2")]
pub async fn create_recipient(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    connector_data: &api::ConnectorData,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    todo!()
}

pub async fn complete_payout_eligibility(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    connector_data: &api::ConnectorData,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    let payout_attempt = &payout_data.payout_attempt.to_owned();

    if !payout_data.should_terminate
        && payout_attempt.is_eligible.is_none()
        && connector_data
            .connector_name
            .supports_payout_eligibility(payout_data.payouts.payout_type)
    {
        Box::pin(check_payout_eligibility(
            state,
            merchant_context,
            connector_data,
            payout_data,
        ))
        .await
        .attach_printable("Eligibility failed for given Payout request")?;
    }

    utils::when(
        !payout_attempt
            .is_eligible
            .unwrap_or(state.conf.payouts.payout_eligibility),
        || {
            Err(report!(errors::ApiErrorResponse::InvalidRequestData {
                message: "Payout method data is invalid".to_string(),
            })
            .attach_printable("Payout data provided is invalid"))
        },
    )?;

    Ok(())
}

pub async fn check_payout_eligibility(
    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::PoEligibility,
        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 payout_attempt = &payout_data.payout_attempt;
            let status = payout_response_data
                .status
                .unwrap_or(payout_attempt.status.to_owned());
            let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                connector_payout_id: payout_response_data.connector_payout_id,
                status,
                error_code: payout_response_data.error_code,
                error_message: payout_response_data.error_message,
                is_eligible: payout_response_data.payout_eligible,
                unified_code: None,
                unified_message: None,
            };
            payout_data.payout_attempt = db
                .update_payout_attempt(
                    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: Some(false),
                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 async fn complete_create_payout(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    connector_data: &api::ConnectorData,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    if !payout_data.should_terminate
        && matches!(
            payout_data.payout_attempt.status,
            storage_enums::PayoutStatus::RequiresCreation
                | storage_enums::PayoutStatus::RequiresConfirmation
                | storage_enums::PayoutStatus::RequiresPayoutMethodData
        )
    {
        if connector_data
            .connector_name
            .supports_instant_payout(payout_data.payouts.payout_type)
        {
            // create payout_object only in router
            let db = &*state.store;
            let payout_attempt = &payout_data.payout_attempt;
            let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                connector_payout_id: payout_data.payout_attempt.connector_payout_id.clone(),
                status: storage::enums::PayoutStatus::RequiresFulfillment,
                error_code: None,
                error_message: None,
                is_eligible: None,
                unified_code: None,
                unified_message: None,
            };
            payout_data.payout_attempt = db
                .update_payout_attempt(
                    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: storage::enums::PayoutStatus::RequiresFulfillment,
                    },
                    &payout_data.payout_attempt,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payouts in db")?;
        } else {
            // create payout_object in connector as well as router
            Box::pin(create_payout(
                state,
                merchant_context,
                connector_data,
                payout_data,
            ))
            .await
            .attach_printable("Payout creation failed for given Payout request")?;
        }
    }
    Ok(())
}

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

    // 2. Get/Create access token
    access_token::create_access_token(
        state,
        connector_data,
        merchant_context,
        &mut router_data,
        payout_data.payouts.payout_type.to_owned(),
    )
    .await?;

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

    // 4. Execute pretasks
    complete_payout_quote_steps_if_required(state, connector_data, &mut router_data).await?;

    // 5. 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()?;

    // 6. Process data returned by the connector
    let db = &*state.store;
    match router_data_resp.response {
        Ok(payout_response_data) => {
            let payout_attempt = &payout_data.payout_attempt;
            let status = payout_response_data
                .status
                .unwrap_or(payout_attempt.status.to_owned());
            let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                connector_payout_id: payout_response_data.connector_payout_id,
                status,
                error_code: payout_response_data.error_code,
                error_message: payout_response_data.error_message,
                is_eligible: payout_response_data.payout_eligible,
                unified_code: None,
                unified_message: None,
            };
            payout_data.payout_attempt = db
                .update_payout_attempt(
                    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(())
}

async fn complete_payout_quote_steps_if_required<F>(
    state: &SessionState,
    connector_data: &api::ConnectorData,
    router_data: &mut types::RouterData<F, types::PayoutsData, types::PayoutsResponseData>,
) -> RouterResult<()> {
    if connector_data
        .connector_name
        .is_payout_quote_call_required()
    {
        let quote_router_data =
            types::PayoutsRouterData::foreign_from((router_data, router_data.request.clone()));
        let connector_integration: services::BoxedPayoutConnectorIntegrationInterface<
            api::PoQuote,
            types::PayoutsData,
            types::PayoutsResponseData,
        > = connector_data.connector.get_connector_integration();
        let router_data_resp = services::execute_connector_processing_step(
            state,
            connector_integration,
            &quote_router_data,
            payments::CallConnectorAction::Trigger,
            None,
            None,
        )
        .await
        .to_payout_failed_response()?;

        match router_data_resp.response.to_owned() {
            Ok(resp) => {
                router_data.quote_id = resp.connector_payout_id;
            }
            Err(_err) => {
                router_data.response = router_data_resp.response;
            }
        };
    }
    Ok(())
}

#[cfg(feature = "v1")]
pub async fn complete_payout_retrieve(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    connector_call_type: api::ConnectorCallType,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    match connector_call_type {
        api::ConnectorCallType::PreDetermined(routing_data) => {
            create_payout_retrieve(
                state,
                merchant_context,
                &routing_data.connector_data,
                payout_data,
            )
            .await
            .attach_printable("Payout retrieval failed for given Payout request")?;
        }
        api::ConnectorCallType::Retryable(_) | api::ConnectorCallType::SessionMultiple(_) => {
            Err(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Payout retrieval not supported for given ConnectorCallType")?
        }
    }

    Ok(())
}

#[cfg(feature = "v2")]
pub async fn complete_payout_retrieve(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    connector_call_type: api::ConnectorCallType,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    todo!()
}

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

    // 2. Get/Create access token
    access_token::create_access_token(
        state,
        connector_data,
        merchant_context,
        &mut router_data,
        payout_data.payouts.payout_type.to_owned(),
    )
    .await?;

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

    // 4. 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()?;

    // 5. Process data returned by the connector
    update_retrieve_payout_tracker(state, merchant_context, payout_data, &router_data_resp).await?;

    Ok(())
}

pub async fn update_retrieve_payout_tracker<F, T>(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payout_data: &mut PayoutData,
    payout_router_data: &types::RouterData<F, T, types::PayoutsResponseData>,
) -> RouterResult<()> {
    let db = &*state.store;
    match payout_router_data.response.as_ref() {
        Ok(payout_response_data) => {
            let payout_attempt = &payout_data.payout_attempt;
            let status = payout_response_data
                .status
                .unwrap_or(payout_attempt.status.to_owned());

            let updated_payout_attempt = if helpers::is_payout_err_state(status) {
                let (error_code, error_message) = (
                    payout_response_data.error_code.clone(),
                    payout_response_data.error_message.clone(),
                );
                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),
                );
                storage::PayoutAttemptUpdate::StatusUpdate {
                    connector_payout_id: payout_response_data.connector_payout_id.clone(),
                    status,
                    error_code,
                    error_message,
                    is_eligible: payout_response_data.payout_eligible,
                    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",
                        })?,
                }
            } else {
                storage::PayoutAttemptUpdate::StatusUpdate {
                    connector_payout_id: payout_response_data.connector_payout_id.clone(),
                    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_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) => {
            // log in case of error in retrieval
            logger::error!("Error in payout retrieval");
            // show error in the response of sync
            payout_data.payout_attempt.error_code = Some(err.code.to_owned());
            payout_data.payout_attempt.error_message = Some(err.message.to_owned());
        }
    };
    Ok(())
}

pub async fn complete_create_recipient_disburse_account(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    connector_data: &api::ConnectorData,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    if !payout_data.should_terminate
        && matches!(
            payout_data.payout_attempt.status,
            storage_enums::PayoutStatus::RequiresVendorAccountCreation
                | storage_enums::PayoutStatus::RequiresCreation
        )
        && connector_data
            .connector_name
            .supports_vendor_disburse_account_create_for_payout()
        && helpers::should_create_connector_transfer_method(payout_data, connector_data)?.is_none()
    {
        Box::pin(create_recipient_disburse_account(
            state,
            merchant_context,
            connector_data,
            payout_data,
        ))
        .await
        .attach_printable("Creation of customer failed")?;
    }
    Ok(())
}

pub async fn create_recipient_disburse_account(
    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::PoRecipientAccount,
        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 payout_attempt = &payout_data.payout_attempt;
            let status = payout_response_data
                .status
                .unwrap_or(payout_attempt.status.to_owned());
            let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                connector_payout_id: payout_response_data.connector_payout_id.clone(),
                status,
                error_code: payout_response_data.error_code,
                error_message: payout_response_data.error_message,
                is_eligible: payout_response_data.payout_eligible,
                unified_code: None,
                unified_message: None,
            };
            payout_data.payout_attempt = db
                .update_payout_attempt(
                    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")?;

            if let (
                true,
                Some(ref payout_method_data),
                Some(connector_payout_id),
                Some(customer_details),
                Some(merchant_connector_id),
            ) = (
                payout_data.payouts.recurring,
                payout_data.payout_method_data.clone(),
                payout_response_data.connector_payout_id.clone(),
                payout_data.customer_details.clone(),
                connector_data.merchant_connector_id.clone(),
            ) {
                let connector_mandate_details = HashMap::from([(
                    merchant_connector_id.clone(),
                    PayoutsMandateReferenceRecord {
                        transfer_method_id: Some(connector_payout_id),
                    },
                )]);

                let common_connector_mandate = CommonMandateReference {
                    payments: None,
                    payouts: Some(PayoutsMandateReference(connector_mandate_details)),
                };

                let connector_mandate_details_value = common_connector_mandate
                    .get_mandate_details_value()
                    .map_err(|err| {
                        router_env::logger::error!(
                            "Failed to get get_mandate_details_value : {:?}",
                            err
                        );
                        errors::ApiErrorResponse::MandateUpdateFailed
                    })?;

                if let Some(pm_method) = payout_data.payment_method.clone() {
                    let pm_update =
                        diesel_models::PaymentMethodUpdate::ConnectorMandateDetailsUpdate {
                            #[cfg(feature = "v1")]
                            connector_mandate_details: Some(connector_mandate_details_value),

                            #[cfg(feature = "v2")]
                            connector_mandate_details: Some(common_connector_mandate),
                        };

                    payout_data.payment_method = Some(
                        db.update_payment_method(
                            &(state.into()),
                            merchant_context.get_merchant_key_store(),
                            pm_method,
                            pm_update,
                            merchant_context.get_merchant_account().storage_scheme,
                        )
                        .await
                        .change_context(errors::ApiErrorResponse::PaymentMethodNotFound)
                        .attach_printable("Unable to find payment method")?,
                    );
                } else {
                    #[cfg(feature = "v1")]
                    let customer_id = Some(customer_details.customer_id);

                    #[cfg(feature = "v2")]
                    let customer_id = customer_details.merchant_reference_id;

                    if let Some(customer_id) = customer_id {
                        helpers::save_payout_data_to_locker(
                            state,
                            payout_data,
                            &customer_id,
                            payout_method_data,
                            Some(connector_mandate_details_value),
                            merchant_context,
                        )
                        .await
                        .attach_printable("Failed to save payout data to locker")?;
                    }
                };
            }
        }
        Err(err) => {
            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: storage_enums::PayoutStatus::Failed,
                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")?;
        }
    };

    Ok(())
}

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;


// File: crates/hyperswitch_connectors/src/connectors/paypal.rs
// Module: hyperswitch_connectors
// Public functions: 2
// Public structs: 1

pub mod transformers;
use std::{fmt::Write, sync::LazyLock};

use base64::Engine;
use common_enums::{enums, CallConnectorAction, PaymentAction};
use common_utils::{
    consts,
    errors::CustomResult,
    ext_traits::ByteSliceExt,
    request::{Method, Request, RequestBuilder, RequestContent},
    types::{
        AmountConvertor, MinorUnit, StringMajorUnit, StringMajorUnitForConnector, StringMinorUnit,
        StringMinorUnitForConnector,
    },
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{PaymentMethodData, WalletData},
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{
        access_token_auth::AccessTokenAuth,
        payments::{
            Authorize, Capture, IncrementalAuthorization, PSync, PaymentMethodToken,
            PostSessionTokens, PreProcessing, SdkSessionUpdate, Session, SetupMandate, Void,
        },
        refunds::{Execute, RSync},
        CompleteAuthorize, VerifyWebhookSource,
    },
    router_request_types::{
        AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData,
        PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData,
        PaymentsIncrementalAuthorizationData, PaymentsPostSessionTokensData,
        PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, RefundsData, ResponseId,
        SdkPaymentsSessionUpdateData, SetupMandateRequestData, VerifyWebhookSourceRequestData,
    },
    router_response_types::{
        ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData,
        SupportedPaymentMethods, SupportedPaymentMethodsExt, VerifyWebhookSourceResponseData,
    },
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        PaymentsCompleteAuthorizeRouterData, PaymentsIncrementalAuthorizationRouterData,
        PaymentsPostSessionTokensRouterData, PaymentsPreProcessingRouterData,
        PaymentsSyncRouterData, RefreshTokenRouterData, RefundSyncRouterData, RefundsRouterData,
        SdkSessionUpdateRouterData, SetupMandateRouterData, VerifyWebhookSourceRouterData,
    },
};
#[cfg(feature = "payouts")]
use hyperswitch_domain_models::{
    router_flow_types::payouts::{PoCreate, PoFulfill, PoSync},
    router_response_types::PayoutsResponseData,
    types::{PayoutsData, PayoutsRouterData},
};
#[cfg(feature = "payouts")]
use hyperswitch_interfaces::types::{PayoutFulfillType, PayoutSyncType};
use hyperswitch_interfaces::{
    api::{
        self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse,
        ConnectorSpecifications, ConnectorValidation, PaymentIncrementalAuthorization,
    },
    configs::Connectors,
    consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE},
    disputes, errors,
    events::connector_api_logs::ConnectorEvent,
    types::{
        IncrementalAuthorizationType, PaymentsAuthorizeType, PaymentsCaptureType,
        PaymentsCompleteAuthorizeType, PaymentsPostSessionTokensType, PaymentsPreProcessingType,
        PaymentsSyncType, PaymentsVoidType, RefreshTokenType, RefundExecuteType, RefundSyncType,
        Response, SdkSessionUpdateType, SetupMandateType, VerifyWebhookSourceType,
    },
    webhooks::{IncomingWebhook, IncomingWebhookRequestDetails},
};
use masking::{ExposeInterface, Mask, Maskable, PeekInterface, Secret};
#[cfg(feature = "payouts")]
use router_env::{instrument, tracing};
use transformers::{
    self as paypal, auth_headers, PaypalAuthResponse, PaypalIncrementalAuthResponse, PaypalMeta,
    PaypalWebhookEventType,
};

use crate::{
    constants::{self, headers},
    types::ResponseRouterData,
    utils::{
        self as connector_utils, to_connector_meta, ConnectorErrorType, ConnectorErrorTypeMapping,
        ForeignTryFrom, PaymentMethodDataType, PaymentsAuthorizeRequestData,
        PaymentsCompleteAuthorizeRequestData, RefundsRequestData,
    },
};

#[derive(Clone)]
pub struct Paypal {
    amount_converter: &'static (dyn AmountConvertor<Output = StringMajorUnit> + Sync),
    amount_converter_webhooks: &'static (dyn AmountConvertor<Output = StringMinorUnit> + Sync),
}

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

impl api::Payment for Paypal {}
impl api::PaymentSession for Paypal {}
impl api::PaymentToken for Paypal {}
impl api::ConnectorAccessToken for Paypal {}
impl api::MandateSetup for Paypal {}
impl api::PaymentAuthorize for Paypal {}
impl api::PaymentsCompleteAuthorize for Paypal {}
impl api::PaymentSync for Paypal {}
impl api::PaymentCapture for Paypal {}
impl api::PaymentVoid for Paypal {}
impl api::Refund for Paypal {}
impl api::RefundExecute for Paypal {}
impl api::RefundSync for Paypal {}
impl api::ConnectorVerifyWebhookSource for Paypal {}
impl api::PaymentPostSessionTokens for Paypal {}
impl api::PaymentSessionUpdate for Paypal {}

impl api::Payouts for Paypal {}
#[cfg(feature = "payouts")]
impl api::PayoutCreate for Paypal {}
#[cfg(feature = "payouts")]
impl api::PayoutFulfill for Paypal {}
#[cfg(feature = "payouts")]
impl api::PayoutSync for Paypal {}

impl Paypal {
    pub fn get_order_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        //Handled error response separately for Orders as the end point is different for Orders - (Authorize) and Payments - (Capture, void, refund, rsync).
        //Error response have different fields for Orders and Payments.
        let response: paypal::PaypalOrderErrorResponse = res
            .response
            .parse_struct("Paypal ErrorResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

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

        let error_reason = response.details.clone().map(|order_errors| {
            order_errors
                .iter()
                .map(|error| {
                    let mut reason = format!("description - {}", error.description);
                    if let Some(value) = &error.value {
                        reason.push_str(&format!(", value - {value}"));
                    }
                    if let Some(field) = error
                        .field
                        .as_ref()
                        .and_then(|field| field.split('/').next_back())
                    {
                        reason.push_str(&format!(", field - {field}"));
                    }
                    reason.push(';');
                    reason
                })
                .collect::<String>()
        });
        let errors_list = response.details.unwrap_or_default();
        let option_error_code_message =
            connector_utils::get_error_code_error_message_based_on_priority(
                self.clone(),
                errors_list
                    .into_iter()
                    .map(|errors| errors.into())
                    .collect(),
            );
        Ok(ErrorResponse {
            status_code: res.status_code,
            code: option_error_code_message
                .clone()
                .map(|error_code_message| error_code_message.error_code)
                .unwrap_or(NO_ERROR_CODE.to_string()),
            message: option_error_code_message
                .map(|error_code_message| error_code_message.error_message)
                .unwrap_or(NO_ERROR_MESSAGE.to_string()),
            reason: error_reason.or(Some(response.message)),
            attempt_status: None,
            connector_transaction_id: response.debug_id,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Paypal
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    fn build_headers(
        &self,
        req: &RouterData<Flow, Request, Response>,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        let access_token = req
            .access_token
            .clone()
            .ok_or(errors::ConnectorError::FailedToObtainAuthType)?;
        let key = &req.connector_request_reference_id;
        let auth = paypal::PaypalAuthType::try_from(&req.connector_auth_type)?;
        let mut headers = vec![
            (
                headers::CONTENT_TYPE.to_string(),
                self.get_content_type().to_string().into(),
            ),
            (
                headers::AUTHORIZATION.to_string(),
                format!("Bearer {}", access_token.token.peek()).into_masked(),
            ),
            (
                auth_headers::PREFER.to_string(),
                "return=representation".to_string().into(),
            ),
            (
                auth_headers::PAYPAL_REQUEST_ID.to_string(),
                key.to_string().into_masked(),
            ),
        ];
        if let Ok(paypal::PaypalConnectorCredentials::PartnerIntegration(credentials)) =
            auth.get_credentials()
        {
            let auth_assertion_header =
                construct_auth_assertion_header(&credentials.payer_id, &credentials.client_id);
            headers.extend(vec![
                (
                    auth_headers::PAYPAL_AUTH_ASSERTION.to_string(),
                    auth_assertion_header.to_string().into_masked(),
                ),
                (
                    auth_headers::PAYPAL_PARTNER_ATTRIBUTION_ID.to_string(),
                    "HyperSwitchPPCP_SP".to_string().into(),
                ),
            ])
        } else {
            headers.extend(vec![(
                auth_headers::PAYPAL_PARTNER_ATTRIBUTION_ID.to_string(),
                "HyperSwitchlegacy_Ecom".to_string().into(),
            )])
        }
        Ok(headers)
    }
}

fn construct_auth_assertion_header(
    payer_id: &Secret<String>,
    client_id: &Secret<String>,
) -> String {
    let algorithm = consts::BASE64_ENGINE
        .encode("{\"alg\":\"none\"}")
        .to_string();
    let merchant_credentials = format!(
        "{{\"iss\":\"{}\",\"payer_id\":\"{}\"}}",
        client_id.clone().expose(),
        payer_id.clone().expose()
    );
    let encoded_credentials = consts::BASE64_ENGINE
        .encode(merchant_credentials)
        .to_string();
    format!("{algorithm}.{encoded_credentials}.")
}

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

    fn get_currency_unit(&self) -> api::CurrencyUnit {
        api::CurrencyUnit::Base
    }

    fn common_get_content_type(&self) -> &'static str {
        "application/json"
    }

    fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
        connectors.paypal.base_url.as_ref()
    }

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        let auth = paypal::PaypalAuthType::try_from(auth_type)?;
        let credentials = auth.get_credentials()?;

        Ok(vec![(
            headers::AUTHORIZATION.to_string(),
            credentials.get_client_secret().into_masked(),
        )])
    }

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

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

        let error_reason = response
            .details
            .clone()
            .map(|error_details| {
                error_details
                    .iter()
                    .try_fold(String::new(), |mut acc, error| {
                        if let Some(description) = &error.description {
                            write!(acc, "description - {description} ;")
                                .change_context(
                                    errors::ConnectorError::ResponseDeserializationFailed,
                                )
                                .attach_printable("Failed to concatenate error details")
                                .map(|_| acc)
                        } else {
                            Ok(acc)
                        }
                    })
            })
            .transpose()?;
        let reason = match error_reason {
            Some(err_reason) => err_reason
                .is_empty()
                .then(|| response.message.to_owned())
                .or(Some(err_reason)),
            None => Some(response.message.to_owned()),
        };
        let errors_list = response.details.unwrap_or_default();
        let option_error_code_message =
            connector_utils::get_error_code_error_message_based_on_priority(
                self.clone(),
                errors_list
                    .into_iter()
                    .map(|errors| errors.into())
                    .collect(),
            );

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: option_error_code_message
                .clone()
                .map(|error_code_message| error_code_message.error_code)
                .unwrap_or(NO_ERROR_CODE.to_string()),
            message: option_error_code_message
                .map(|error_code_message| error_code_message.error_message)
                .unwrap_or(NO_ERROR_MESSAGE.to_string()),
            reason,
            attempt_status: None,
            connector_transaction_id: response.debug_id,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}

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

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
    for Paypal
{
}

impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Paypal {}

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken> for Paypal {
    fn get_url(
        &self,
        _req: &RefreshTokenRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!("{}v1/oauth2/token", self.base_url(connectors)))
    }
    fn get_content_type(&self) -> &'static str {
        "application/x-www-form-urlencoded"
    }
    fn get_headers(
        &self,
        req: &RefreshTokenRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        let auth = paypal::PaypalAuthType::try_from(&req.connector_auth_type)?;
        let credentials = auth.get_credentials()?;
        let auth_val = credentials.generate_authorization_value();

        Ok(vec![
            (
                headers::CONTENT_TYPE.to_string(),
                RefreshTokenType::get_content_type(self).to_string().into(),
            ),
            (headers::AUTHORIZATION.to_string(), auth_val.into_masked()),
        ])
    }
    fn get_request_body(
        &self,
        req: &RefreshTokenRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = paypal::PaypalAuthUpdateRequest::try_from(req)?;

        Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &RefreshTokenRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let req = Some(
            RequestBuilder::new()
                .method(Method::Post)
                .headers(RefreshTokenType::get_headers(self, req, connectors)?)
                .url(&RefreshTokenType::get_url(self, req, connectors)?)
                .set_body(RefreshTokenType::get_request_body(self, req, connectors)?)
                .build(),
        );

        Ok(req)
    }

    fn handle_response(
        &self,
        data: &RefreshTokenRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<RefreshTokenRouterData, errors::ConnectorError> {
        let response: paypal::PaypalAuthUpdateResponse = res
            .response
            .parse_struct("Paypal PaypalAuthUpdateResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

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

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        let response: paypal::PaypalAccessTokenErrorResponse = res
            .response
            .parse_struct("Paypal AccessTokenErrorResponse")
            .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.error.clone(),
            message: response.error.clone(),
            reason: Some(response.error_description),
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoFulfill, PayoutsData, PayoutsResponseData> for Paypal {
    fn get_url(
        &self,
        _req: &PayoutsRouterData<PoFulfill>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!("{}v1/payments/payouts", self.base_url(connectors)))
    }

    fn get_headers(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_request_body(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = connector_utils::convert_amount(
            self.amount_converter,
            req.request.minor_amount,
            req.request.destination_currency,
        )?;
        let connector_router_data =
            paypal::PaypalRouterData::try_from((amount, None, None, None, req))?;
        let connector_req = paypal::PaypalFulfillRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&PayoutFulfillType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(PayoutFulfillType::get_headers(self, req, connectors)?)
            .set_body(PayoutFulfillType::get_request_body(self, req, connectors)?)
            .build();

        Ok(Some(request))
    }

    #[instrument(skip_all)]
    fn handle_response(
        &self,
        data: &PayoutsRouterData<PoFulfill>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PayoutsRouterData<PoFulfill>, errors::ConnectorError> {
        let response: paypal::PaypalFulfillResponse = res
            .response
            .parse_struct("PaypalFulfillResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

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

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoSync, PayoutsData, PayoutsResponseData> for Paypal {
    fn get_url(
        &self,
        req: &PayoutsRouterData<PoSync>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let batch_id = req.request.connector_payout_id.clone().ok_or(
            errors::ConnectorError::MissingRequiredField {
                field_name: "connector_payout_id",
            },
        )?;
        Ok(format!(
            "{}v1/payments/payouts/{}",
            self.base_url(connectors),
            batch_id
        ))
    }

    fn get_headers(
        &self,
        req: &PayoutsRouterData<PoSync>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn build_request(
        &self,
        req: &PayoutsRouterData<PoSync>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Get)
            .url(&PayoutSyncType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(PayoutSyncType::get_headers(self, req, connectors)?)
            .build();

        Ok(Some(request))
    }

    #[instrument(skip_all)]
    fn handle_response(
        &self,
        data: &PayoutsRouterData<PoSync>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PayoutsRouterData<PoSync>, errors::ConnectorError> {
        let response: paypal::PaypalPayoutSyncResponse = res
            .response
            .parse_struct("PaypalPayoutSyncResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

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

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[async_trait::async_trait]
#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoCreate, PayoutsData, PayoutsResponseData> for Paypal {
    fn build_request(
        &self,
        _req: &PayoutsRouterData<PoCreate>,
        _connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        // Eligibility check for wallet is not implemented
        Err(
            errors::ConnectorError::NotImplemented("Payout Eligibility for Paypal".to_string())
                .into(),
        )
    }
}

impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData> for Paypal {
    fn get_headers(
        &self,
        req: &SetupMandateRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }
    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }
    fn get_url(
        &self,
        _req: &SetupMandateRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}v3/vault/payment-tokens/",
            self.base_url(connectors)
        ))
    }
    fn get_request_body(
        &self,
        req: &SetupMandateRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = paypal::PaypalZeroMandateRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &SetupMandateRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&SetupMandateType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(SetupMandateType::get_headers(self, req, connectors)?)
                .set_body(SetupMandateType::get_request_body(self, req, connectors)?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &SetupMandateRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<SetupMandateRouterData, errors::ConnectorError> {
        let response: paypal::PaypalSetupMandatesResponse = res
            .response
            .parse_struct("PaypalSetupMandatesResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<PostSessionTokens, PaymentsPostSessionTokensData, PaymentsResponseData>
    for Paypal
{
    fn get_headers(
        &self,
        req: &PaymentsPostSessionTokensRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }
    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }
    fn get_url(
        &self,
        _req: &PaymentsPostSessionTokensRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!("{}v2/checkout/orders", self.base_url(connectors)))
    }
    fn build_request(
        &self,
        req: &PaymentsPostSessionTokensRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&PaymentsPostSessionTokensType::get_url(
                    self, req, connectors,
                )?)
                .attach_default_headers()
                .headers(PaymentsPostSessionTokensType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(PaymentsPostSessionTokensType::get_request_body(
                    self, req, connectors,
                )?)
                .build(),
        ))
    }

    fn get_request_body(
        &self,
        req: &PaymentsPostSessionTokensRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = connector_utils::convert_amount(
            self.amount_converter,
            req.request.amount,
            req.request.currency,
        )?;
        let shipping_cost = connector_utils::convert_amount(
            self.amount_converter,
            req.request.shipping_cost.unwrap_or(MinorUnit::zero()),
            req.request.currency,
        )?;
        let order_amount = connector_utils::convert_amount(
            self.amount_converter,
            req.request.order_amount,
            req.request.currency,
        )?;
        let connector_router_data = paypal::PaypalRouterData::try_from((
            amount,
            Some(shipping_cost),
            None,
            Some(order_amount),
            req,
        ))?;
        let connector_req = paypal::PaypalPaymentsRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn handle_response(
        &self,
        data: &PaymentsPostSessionTokensRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsPostSessionTokensRouterData, errors::ConnectorError> {
        let response: paypal::PaypalRedirectResponse = res
            .response
            .parse_struct("PaypalRedirectResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.get_order_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<SdkSessionUpdate, SdkPaymentsSessionUpdateData, PaymentsResponseData>
    for Paypal
{
    fn get_headers(
        &self,
        req: &SdkSessionUpdateRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        req: &SdkSessionUpdateRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let session_id =
            req.request
                .session_id
                .clone()
                .ok_or(errors::ConnectorError::MissingRequiredField {
                    field_name: "session_id",
                })?;
        Ok(format!(
            "{}v2/checkout/orders/{}",
            self.base_url(connectors),
            session_id
        ))
    }

    fn build_request(
        &self,
        req: &SdkSessionUpdateRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Patch)
                .url(&SdkSessionUpdateType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(SdkSessionUpdateType::get_headers(self, req, connectors)?)
                .set_body(SdkSessionUpdateType::get_request_body(
                    self, req, connectors,
                )?)
                .build(),
        ))
    }

    fn get_request_body(
        &self,
        req: &SdkSessionUpdateRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let order_amount = connector_utils::convert_amount(
            self.amount_converter,
            req.request.order_amount,
            req.request.currency,
        )?;
        let amount = connector_utils::convert_amount(
            self.amount_converter,
            req.request.amount,
            req.request.currency,
        )?;
        let order_tax_amount = connector_utils::convert_amount(
            self.amount_converter,
            req.request.order_tax_amount,
            req.request.currency,
        )?;
        let shipping_cost = connector_utils::convert_amount(
            self.amount_converter,
            req.request.shipping_cost.unwrap_or(MinorUnit::zero()),
            req.request.currency,
        )?;
        let connector_router_data = paypal::PaypalRouterData::try_from((
            amount,
            Some(shipping_cost),
            Some(order_tax_amount),
            Some(order_amount),
            req,
        ))?;

        let connector_req = paypal::PaypalUpdateOrderRequest::try_from(&connector_router_data)?;
        // encode only for for urlencoded things.
        Ok(RequestContent::Json(Box::new(
            connector_req.get_inner_value(),
        )))
    }

    fn handle_response(
        &self,
        data: &SdkSessionUpdateRouterData,
        _event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<SdkSessionUpdateRouterData, errors::ConnectorError> {
        router_env::logger::debug!("Expected zero bytes response, skipped parsing of the response");
        // https://developer.paypal.com/docs/api/orders/v2/#orders_patch
        // If 204 status code, then the session was updated successfully.
        let status = if res.status_code == 204 {
            enums::PaymentResourceUpdateStatus::Success
        } else {
            enums::PaymentResourceUpdateStatus::Failure
        };
        Ok(SdkSessionUpdateRouterData {
            response: Ok(PaymentsResponseData::PaymentResourceUpdateResponse { status }),
            ..data.clone()
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> for Paypal {
    fn get_headers(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        match &req.request.payment_method_data {
            PaymentMethodData::Wallet(WalletData::PaypalSdk(paypal_wallet_data)) => {
                let authorize_url = if req.request.is_auto_capture()? {
                    "capture".to_string()
                } else {
                    "authorize".to_string()
                };
                Ok(format!(
                    "{}v2/checkout/orders/{}/{authorize_url}",
                    self.base_url(connectors),
                    paypal_wallet_data.token
                ))
            }
            _ => Ok(format!("{}v2/checkout/orders", self.base_url(connectors))),
        }
    }

    fn get_request_body(
        &self,
        req: &PaymentsAuthorizeRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = connector_utils::convert_amount(
            self.amount_converter,
            req.request.minor_amount,
            req.request.currency,
        )?;
        let shipping_cost = connector_utils::convert_amount(
            self.amount_converter,
            req.request.shipping_cost.unwrap_or(MinorUnit::zero()),
            req.request.currency,
        )?;
        let connector_router_data =
            paypal::PaypalRouterData::try_from((amount, Some(shipping_cost), None, None, req))?;
        let connector_req = paypal::PaypalPaymentsRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let payment_method_data = req.request.payment_method_data.clone();
        let req = match payment_method_data {
            PaymentMethodData::Wallet(WalletData::PaypalSdk(_)) => RequestBuilder::new()
                .method(Method::Post)
                .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?)
                .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?)
                .build(),
            _ => RequestBuilder::new()
                .method(Method::Post)
                .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?)
                .set_body(PaymentsAuthorizeType::get_request_body(
                    self, req, connectors,
                )?)
                .build(),
        };

        Ok(Some(req))
    }

    fn handle_response(
        &self,
        data: &PaymentsAuthorizeRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsAuthorizeRouterData, errors::ConnectorError> {
        let response: PaypalAuthResponse =
            res.response
                .parse_struct("paypal PaypalAuthResponse")
                .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        match response {
            PaypalAuthResponse::PaypalOrdersResponse(response) => {
                event_builder.map(|i| i.set_response_body(&response));
                router_env::logger::info!(connector_response=?response);

                RouterData::try_from(ResponseRouterData {
                    response,
                    data: data.clone(),
                    http_code: res.status_code,
                })
            }
            PaypalAuthResponse::PaypalRedirectResponse(response) => {


// Function: get_irrelevant_merchant_id
// File: crates/common_utils/src/id_type/merchant.rs
// Module: common_utils

// Documentation: Get a merchant id with a value of `irrelevant_merchant_id`

pub fn get_irrelevant_merchant_id() -> Self


// Function: create_payment_method_intent_api
// File: crates/openapi/src/routes/payment_method.rs
// Module: openapi

pub fn create_payment_method_intent_api()


// Struct: PayeezyCard
// File: crates/hyperswitch_connectors/src/connectors/payeezy/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PayeezyCard