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

    Ok(())
}

pub async fn fulfill_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::PoFulfill,
        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
    let db = &*state.store;
    match router_data_resp.response {
        Ok(payout_response_data) => {
            let status = payout_response_data
                .status
                .unwrap_or(payout_data.payout_attempt.status.to_owned());
            payout_data.payouts.status = status;
            let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                connector_payout_id: payout_response_data.connector_payout_id,
                status,
                error_code: None,
                error_message: None,
                is_eligible: payout_response_data.payout_eligible,
                unified_code: None,
                unified_message: None,
            };
            payout_data.payout_attempt = db
                .update_payout_attempt(
                    &payout_data.payout_attempt,
                    updated_payout_attempt,
                    &payout_data.payouts,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payout_attempt in db")?;
            payout_data.payouts = db
                .update_payout(
                    &payout_data.payouts,
                    storage::PayoutsUpdate::StatusUpdate { status },
                    &payout_data.payout_attempt,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payouts in db")?;
            if helpers::is_payout_err_state(status) {
                return Err(report!(errors::ApiErrorResponse::PayoutFailed {
                    data: Some(
                        serde_json::json!({"payout_status": status.to_string(), "error_message": payout_data.payout_attempt.error_message.as_ref(), "error_code": payout_data.payout_attempt.error_code.as_ref()})
                    ),
                }));
            } else if payout_data.payouts.recurring
                && payout_data.payouts.payout_method_id.clone().is_none()
            {
                let payout_method_data = payout_data
                    .payout_method_data
                    .clone()
                    .get_required_value("payout_method_data")?;
                payout_data
                    .payouts
                    .customer_id
                    .clone()
                    .async_map(|customer_id| async move {
                        helpers::save_payout_data_to_locker(
                            state,
                            payout_data,
                            &customer_id,
                            &payout_method_data,
                            None,
                            merchant_context,
                        )
                        .await
                    })
                    .await
                    .transpose()
                    .attach_printable("Failed to save payout data to locker")?;
            }
        }
        Err(err) => {
            let status = storage_enums::PayoutStatus::Failed;
            let (error_code, error_message) = (Some(err.code), Some(err.message));
            let (unified_code, unified_message) = helpers::get_gsm_record(
                state,
                error_code.clone(),
                error_message.clone(),
                payout_data.payout_attempt.connector.clone(),
                consts::PAYOUT_FLOW_STR,
            )
            .await
            .map_or(
                (
                    Some(crate::consts::DEFAULT_UNIFIED_ERROR_CODE.to_string()),
                    Some(crate::consts::DEFAULT_UNIFIED_ERROR_MESSAGE.to_string()),
                ),
                |gsm| (gsm.unified_code, gsm.unified_message),
            );
            let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                connector_payout_id: payout_data.payout_attempt.connector_payout_id.to_owned(),
                status,
                error_code,
                error_message,
                is_eligible: None,
                unified_code: unified_code
                    .map(UnifiedCode::try_from)
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "unified_code",
                    })?,
                unified_message: unified_message
                    .map(UnifiedMessage::try_from)
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "unified_message",
                    })?,
            };
            payout_data.payout_attempt = db
                .update_payout_attempt(
                    &payout_data.payout_attempt,
                    updated_payout_attempt,
                    &payout_data.payouts,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payout_attempt in db")?;
            payout_data.payouts = db
                .update_payout(
                    &payout_data.payouts,
                    storage::PayoutsUpdate::StatusUpdate { status },
                    &payout_data.payout_attempt,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payouts in db")?;
        }
    };

    Ok(())
}

pub async fn trigger_webhook_and_handle_response(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payout_data: &PayoutData,
) -> RouterResponse<payouts::PayoutCreateResponse> {
    let response = response_handler(state, merchant_context, payout_data).await?;
    utils::trigger_payouts_webhook(state, merchant_context, &response).await?;
    Ok(services::ApplicationResponse::Json(response))
}

pub async fn response_handler(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payout_data: &PayoutData,
) -> RouterResult<payouts::PayoutCreateResponse> {
    let payout_attempt = payout_data.payout_attempt.to_owned();
    let payouts = payout_data.payouts.to_owned();

    let payout_method_id: Option<String> = payout_data.payment_method.as_ref().map(|pm| {
        #[cfg(feature = "v1")]
        {
            pm.payment_method_id.clone()
        }

        #[cfg(feature = "v2")]
        {
            pm.id.clone().get_string_repr().to_string()
        }
    });

    let payout_link = payout_data.payout_link.to_owned();
    let billing_address = payout_data.billing_address.to_owned();
    let customer_details = payout_data.customer_details.to_owned();
    let customer_id = payouts.customer_id;
    let billing = billing_address.map(From::from);

    let translated_unified_message = helpers::get_translated_unified_code_and_message(
        state,
        payout_attempt.unified_code.as_ref(),
        payout_attempt.unified_message.as_ref(),
        &payout_data.current_locale,
    )
    .await?;

    let additional_payout_method_data = payout_attempt.additional_payout_method_data.clone();

    let payout_method_data =
        additional_payout_method_data.map(payouts::PayoutMethodDataResponse::from);

    let response = api::PayoutCreateResponse {
        payout_id: payouts.payout_id.to_owned(),
        merchant_id: merchant_context.get_merchant_account().get_id().to_owned(),
        merchant_order_reference_id: payout_attempt.merchant_order_reference_id.clone(),
        amount: payouts.amount,
        currency: payouts.destination_currency.to_owned(),
        connector: payout_attempt.connector,
        payout_type: payouts.payout_type.to_owned(),
        payout_method_data,
        billing,
        auto_fulfill: payouts.auto_fulfill,
        customer_id,
        email: customer_details.as_ref().and_then(|c| c.email.clone()),
        name: customer_details.as_ref().and_then(|c| c.name.clone()),
        phone: customer_details.as_ref().and_then(|c| c.phone.clone()),
        phone_country_code: customer_details
            .as_ref()
            .and_then(|c| c.phone_country_code.clone()),
        customer: customer_details
            .as_ref()
            .map(payment_api_types::CustomerDetailsResponse::foreign_from),
        client_secret: payouts.client_secret.to_owned(),
        return_url: payouts.return_url.to_owned(),
        business_country: payout_attempt.business_country,
        business_label: payout_attempt.business_label,
        description: payouts.description.to_owned(),
        entity_type: payouts.entity_type.to_owned(),
        recurring: payouts.recurring,
        metadata: payouts.metadata,
        merchant_connector_id: payout_attempt.merchant_connector_id.to_owned(),
        status: payout_attempt.status.to_owned(),
        error_message: payout_attempt.error_message.to_owned(),
        error_code: payout_attempt.error_code,
        profile_id: payout_attempt.profile_id,
        created: Some(payouts.created_at),
        connector_transaction_id: payout_attempt.connector_payout_id,
        priority: payouts.priority,
        attempts: None,
        unified_code: payout_attempt.unified_code,
        unified_message: translated_unified_message,
        payout_link: payout_link
            .map(|payout_link| {
                url::Url::parse(payout_link.url.peek()).map(|link| PayoutLinkResponse {
                    payout_link_id: payout_link.link_id,
                    link: link.into(),
                })
            })
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse payout link's URL")?,
        payout_method_id,
    };

    Ok(response)
}

#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]
pub async fn payout_create_db_entries(
    _state: &SessionState,
    _merchant_context: &domain::MerchantContext,
    _req: &payouts::PayoutCreateRequest,
    _payout_id: &str,
    _profile_id: &str,
    _stored_payout_method_data: Option<&payouts::PayoutMethodData>,
    _locale: &str,
    _customer: Option<&domain::Customer>,
    _payment_method: Option<PaymentMethod>,
) -> RouterResult<PayoutData> {
    todo!()
}

// DB entries
#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)]
pub async fn payout_create_db_entries(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    req: &payouts::PayoutCreateRequest,
    payout_id: &id_type::PayoutId,
    profile_id: &id_type::ProfileId,
    stored_payout_method_data: Option<&payouts::PayoutMethodData>,
    locale: &str,
    customer: Option<&domain::Customer>,
    payment_method: Option<PaymentMethod>,
) -> RouterResult<PayoutData> {
    let db = &*state.store;
    let merchant_id = merchant_context.get_merchant_account().get_id();
    let customer_id = customer.map(|cust| cust.customer_id.clone());

    // Validate whether profile_id passed in request is valid and is linked to the merchant
    let business_profile = validate_and_get_business_profile(
        state,
        merchant_context.get_merchant_key_store(),
        profile_id,
        merchant_id,
    )
    .await?;

    let payout_link = match req.payout_link {
        Some(true) => Some(
            create_payout_link(
                state,
                &business_profile,
                &customer_id
                    .clone()
                    .get_required_value("customer.id when payout_link is true")?,
                merchant_id,
                req,
                payout_id,
                locale,
            )
            .await?,
        ),
        _ => None,
    };

    // Get or create billing address
    let (billing_address, address_id) = helpers::resolve_billing_address_for_payout(
        state,
        req.billing.as_ref(),
        None,
        payment_method.as_ref(),
        merchant_context,
        customer_id.as_ref(),
        payout_id,
    )
    .await?;

    // Make payouts entry
    let currency = req.currency.to_owned().get_required_value("currency")?;

    let (payout_method_id, payout_type) = match stored_payout_method_data {
        Some(payout_method_data) => (
            payment_method
                .as_ref()
                .map(|pm| pm.payment_method_id.clone()),
            Some(api_enums::PayoutType::foreign_from(payout_method_data)),
        ),
        None => {
            (
                payment_method
                    .as_ref()
                    .map(|pm| pm.payment_method_id.clone()),
                match req.payout_type {
                    None => payment_method
                        .as_ref()
                        .and_then(|pm| pm.payment_method)
                        .map(|payment_method_enum| {
                            api_enums::PayoutType::foreign_try_from(payment_method_enum)
                                .change_context(errors::ApiErrorResponse::InvalidRequestData {
                                    message: format!(
                                        "PaymentMethod {payment_method_enum:?} is not supported for payouts"
                                    ),
                                })
                                .attach_printable("Failed to convert PaymentMethod to PayoutType")
                        })
                        .transpose()?,
                    payout_type => payout_type,
                },
            )
        }
    };

    let client_secret = utils::generate_id(
        consts::ID_LENGTH,
        format!("payout_{}_secret", payout_id.get_string_repr()).as_str(),
    );
    let amount = MinorUnit::from(req.amount.unwrap_or(api::Amount::Zero));
    let status = if req.payout_method_data.is_some()
        || req.payout_token.is_some()
        || stored_payout_method_data.is_some()
    {
        match req.confirm {
            Some(true) => storage_enums::PayoutStatus::RequiresCreation,
            _ => storage_enums::PayoutStatus::RequiresConfirmation,
        }
    } else {
        storage_enums::PayoutStatus::RequiresPayoutMethodData
    };

    let payouts_req = storage::PayoutsNew {
        payout_id: payout_id.clone(),
        merchant_id: merchant_id.to_owned(),
        customer_id: customer_id.to_owned(),
        address_id: address_id.to_owned(),
        payout_type,
        amount,
        destination_currency: currency,
        source_currency: currency,
        description: req.description.to_owned(),
        recurring: req.recurring.unwrap_or(false),
        auto_fulfill: req.auto_fulfill.unwrap_or(false),
        return_url: req.return_url.to_owned(),
        entity_type: req.entity_type.unwrap_or_default(),
        payout_method_id,
        profile_id: profile_id.to_owned(),
        attempt_count: 1,
        metadata: req.metadata.clone(),
        confirm: req.confirm,
        payout_link_id: payout_link
            .clone()
            .map(|link_data| link_data.link_id.clone()),
        client_secret: Some(client_secret),
        priority: req.priority,
        status,
        created_at: common_utils::date_time::now(),
        last_modified_at: common_utils::date_time::now(),
    };
    let payouts = db
        .insert_payout(
            payouts_req,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_duplicate_response(errors::ApiErrorResponse::DuplicatePayout {
            payout_id: payout_id.clone(),
        })
        .attach_printable("Error inserting payouts in db")?;
    // Make payout_attempt entry
    let payout_attempt_id = utils::get_payout_attempt_id(payout_id.get_string_repr(), 1);

    let additional_pm_data_value = req
        .payout_method_data
        .clone()
        .or(stored_payout_method_data.cloned())
        .async_and_then(|payout_method_data| async move {
            helpers::get_additional_payout_data(&payout_method_data, &*state.store, profile_id)
                .await
        })
        .await;

    let payout_attempt_req = storage::PayoutAttemptNew {
        payout_attempt_id: payout_attempt_id.to_string(),
        payout_id: payout_id.clone(),
        additional_payout_method_data: additional_pm_data_value,
        merchant_id: merchant_id.to_owned(),
        merchant_order_reference_id: req.merchant_order_reference_id.clone(),
        status,
        business_country: req.business_country.to_owned(),
        business_label: req.business_label.to_owned(),
        payout_token: req.payout_token.to_owned(),
        profile_id: profile_id.to_owned(),
        customer_id,
        address_id,
        connector: None,
        connector_payout_id: None,
        is_eligible: None,
        error_message: None,
        error_code: None,
        created_at: common_utils::date_time::now(),
        last_modified_at: common_utils::date_time::now(),
        merchant_connector_id: None,
        routing_info: None,
        unified_code: None,
        unified_message: None,
    };
    let payout_attempt = db
        .insert_payout_attempt(
            payout_attempt_req,
            &payouts,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_duplicate_response(errors::ApiErrorResponse::DuplicatePayout {
            payout_id: payout_id.clone(),
        })
        .attach_printable("Error inserting payout_attempt in db")?;

    // Make PayoutData
    Ok(PayoutData {
        billing_address,
        business_profile,
        customer_details: customer.map(ToOwned::to_owned),
        merchant_connector_account: None,
        payouts,
        payout_attempt,
        payout_method_data: req
            .payout_method_data
            .as_ref()
            .cloned()
            .or(stored_payout_method_data.cloned()),
        should_terminate: false,
        profile_id: profile_id.to_owned(),
        payout_link,
        current_locale: locale.to_string(),
        payment_method,
        connector_transfer_method_id: None,
    })
}

#[cfg(feature = "v2")]
pub async fn make_payout_data(
    _state: &SessionState,
    _merchant_context: &domain::MerchantContext,
    _auth_profile_id: Option<id_type::ProfileId>,
    _req: &payouts::PayoutRequest,
    locale: &str,
) -> RouterResult<PayoutData> {
    todo!()
}

#[cfg(feature = "v1")]
pub async fn make_payout_data(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    auth_profile_id: Option<id_type::ProfileId>,
    req: &payouts::PayoutRequest,
    locale: &str,
) -> RouterResult<PayoutData> {
    let db = &*state.store;
    let merchant_id = merchant_context.get_merchant_account().get_id();
    let payout_id = match req {
        payouts::PayoutRequest::PayoutActionRequest(r) => r.payout_id.clone(),
        payouts::PayoutRequest::PayoutCreateRequest(r) => {
            r.payout_id.clone().unwrap_or(id_type::PayoutId::generate())
        }
        payouts::PayoutRequest::PayoutRetrieveRequest(r) => r.payout_id.clone(),
    };

    let payouts = db
        .find_payout_by_merchant_id_payout_id(
            merchant_id,
            &payout_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?;
    core_utils::validate_profile_id_from_auth_layer(auth_profile_id, &payouts)?;

    let payout_attempt_id =
        utils::get_payout_attempt_id(payouts.payout_id.get_string_repr(), payouts.attempt_count);

    let mut payout_attempt = db
        .find_payout_attempt_by_merchant_id_payout_attempt_id(
            merchant_id,
            &payout_attempt_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?;

    let customer_id = payouts.customer_id.as_ref();

    // We have to do this because the function that is being used to create / get address is from payments
    // which expects a payment_id
    let payout_id_as_payment_id_type = id_type::PaymentId::try_from(std::borrow::Cow::Owned(
        payouts.payout_id.get_string_repr().to_string(),
    ))
    .change_context(errors::ApiErrorResponse::InvalidRequestData {
        message: "payout_id contains invalid data".to_string(),
    })
    .attach_printable("Error converting payout_id to PaymentId type")?;

    let billing_address = payment_helpers::create_or_find_address_for_payment_by_request(
        state,
        None,
        payouts.address_id.as_deref(),
        merchant_id,
        customer_id,
        merchant_context.get_merchant_key_store(),
        &payout_id_as_payment_id_type,
        merchant_context.get_merchant_account().storage_scheme,
    )
    .await?
    .map(|addr| domain_models::address::Address::from(&addr));

    let payout_id = &payouts.payout_id;

    let customer_details = customer_id
        .async_map(|customer_id| async move {
            db.find_customer_optional_by_customer_id_merchant_id(
                &state.into(),
                customer_id,
                merchant_id,
                merchant_context.get_merchant_key_store(),
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .map_err(|err| err.change_context(errors::ApiErrorResponse::InternalServerError))
            .attach_printable_lazy(|| {
                format!(
                    "Failed while fetching optional customer [id - {customer_id:?}] for payout [id - {}]", payout_id.get_string_repr()
                )
            })
        })
        .await
        .transpose()?
        .and_then(|c| c);
    let profile_id = payout_attempt.profile_id.clone();

    // Validate whether profile_id passed in request is valid and is linked to the merchant
    let business_profile = validate_and_get_business_profile(
        state,
        merchant_context.get_merchant_key_store(),
        &profile_id,
        merchant_id,
    )
    .await?;
    let payout_method_data_req = match req {
        payouts::PayoutRequest::PayoutCreateRequest(r) => r.payout_method_data.to_owned(),
        payouts::PayoutRequest::PayoutActionRequest(_) => {
            match payout_attempt.payout_token.to_owned() {
                Some(payout_token) => {
                    let customer_id = customer_details
                        .as_ref()
                        .map(|cd| cd.customer_id.to_owned())
                        .get_required_value("customer_id when payout_token is sent")?;
                    helpers::make_payout_method_data(
                        state,
                        None,
                        Some(&payout_token),
                        &customer_id,
                        merchant_context.get_merchant_account().get_id(),
                        payouts.payout_type,
                        merchant_context.get_merchant_key_store(),
                        None,
                        merchant_context.get_merchant_account().storage_scheme,
                    )
                    .await?
                }
                None => None,
            }
        }
        payouts::PayoutRequest::PayoutRetrieveRequest(_) => None,
    };

    if let Some(payout_method_data) = payout_method_data_req.clone() {
        let additional_payout_method_data =
            helpers::get_additional_payout_data(&payout_method_data, &*state.store, &profile_id)
                .await;

        let update_additional_payout_method_data =
            storage::PayoutAttemptUpdate::AdditionalPayoutMethodDataUpdate {
                additional_payout_method_data,
            };

        payout_attempt = db
            .update_payout_attempt(
                &payout_attempt,
                update_additional_payout_method_data,
                &payouts,
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error updating additional payout method data in payout_attempt")?;
    };

    let merchant_connector_account =
        if payout_attempt.connector.is_some() && payout_attempt.merchant_connector_id.is_some() {
            let connector_name = payout_attempt
                .connector
                .clone()
                .get_required_value("connector_name")?;
            Some(
                payment_helpers::get_merchant_connector_account(
                    state,
                    merchant_context.get_merchant_account().get_id(),
                    None,
                    merchant_context.get_merchant_key_store(),
                    &profile_id,
                    connector_name.as_str(),
                    payout_attempt.merchant_connector_id.as_ref(),
                )
                .await?,
            )
        } else {
            None
        };

    let payout_link = payouts
        .payout_link_id
        .clone()
        .async_map(|link_id| async move {
            db.find_payout_link_by_link_id(&link_id)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error fetching payout links from db")
        })
        .await
        .transpose()?;

    let payment_method = payouts
        .payout_method_id
        .clone()
        .async_map(|pm_id| async move {
            db.find_payment_method(
                &(state.into()),
                merchant_context.get_merchant_key_store(),
                &pm_id,
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::PaymentMethodNotFound)
            .attach_printable("Unable to find payment method")
        })
        .await
        .transpose()?;

    Ok(PayoutData {
        billing_address,
        business_profile,
        customer_details,
        payouts,
        payout_attempt,
        payout_method_data: payout_method_data_req.to_owned(),
        merchant_connector_account,
        should_terminate: false,
        profile_id,
        payout_link,
        current_locale: locale.to_string(),
        payment_method,
        connector_transfer_method_id: None,
    })
}

pub async fn add_external_account_addition_task(
    db: &dyn StorageInterface,
    payout_data: &PayoutData,
    schedule_time: time::PrimitiveDateTime,
) -> CustomResult<(), errors::StorageError> {
    let runner = storage::ProcessTrackerRunner::AttachPayoutAccountWorkflow;
    let task = "STRPE_ATTACH_EXTERNAL_ACCOUNT";
    let tag = ["PAYOUTS", "STRIPE", "ACCOUNT", "CREATE"];
    let process_tracker_id = pt_utils::get_process_tracker_id(
        runner,
        task,
        &payout_data.payout_attempt.payout_attempt_id,
        &payout_data.payout_attempt.merchant_id,
    );
    let tracking_data = api::PayoutRetrieveRequest {
        payout_id: payout_data.payouts.payout_id.to_owned(),
        force_sync: None,
        merchant_id: Some(payout_data.payouts.merchant_id.to_owned()),
    };
    let process_tracker_entry = storage::ProcessTrackerNew::new(
        process_tracker_id,
        task,
        runner,
        tag,
        tracking_data,
        None,
        schedule_time,
        common_types::consts::API_VERSION,
    )
    .map_err(errors::StorageError::from)?;

    db.insert_process(process_tracker_entry).await?;
    Ok(())
}

async fn validate_and_get_business_profile(
    state: &SessionState,
    merchant_key_store: &domain::MerchantKeyStore,
    profile_id: &id_type::ProfileId,
    merchant_id: &id_type::MerchantId,
) -> RouterResult<domain::Profile> {
    let db = &*state.store;
    let key_manager_state = &state.into();

    if let Some(business_profile) = core_utils::validate_and_get_business_profile(
        db,
        key_manager_state,
        merchant_key_store,
        Some(profile_id),
        merchant_id,
    )
    .await?
    {
        Ok(business_profile)
    } else {
        db.find_business_profile_by_profile_id(key_manager_state, merchant_key_store, profile_id)
            .await
            .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
                id: profile_id.get_string_repr().to_owned(),
            })
    }
}

#[allow(clippy::too_many_arguments)]
pub async fn create_payout_link(
    state: &SessionState,
    business_profile: &domain::Profile,
    customer_id: &id_type::CustomerId,
    merchant_id: &id_type::MerchantId,
    req: &payouts::PayoutCreateRequest,
    payout_id: &id_type::PayoutId,
    locale: &str,
) -> RouterResult<PayoutLink> {
    let payout_link_config_req = req.payout_link_config.to_owned();

    // Fetch all configs
    let default_config = &state.conf.generic_link.payout_link;
    let profile_config = &business_profile.payout_link_config;
    let profile_ui_config = profile_config.as_ref().map(|c| c.config.ui_config.clone());
    let ui_config = payout_link_config_req
        .as_ref()
        .and_then(|config| config.ui_config.clone())
        .or(profile_ui_config);

    let test_mode_in_config = payout_link_config_req
        .as_ref()
        .and_then(|config| config.test_mode)
        .or_else(|| profile_config.as_ref().and_then(|c| c.payout_test_mode));
    let is_test_mode_enabled = test_mode_in_config.unwrap_or(false);

    let allowed_domains = match (router_env::which(), is_test_mode_enabled) {
        // Throw error in case test_mode was enabled in production
        (Env::Production, true) => Err(report!(errors::ApiErrorResponse::LinkConfigurationError {
            message: "test_mode cannot be true for creating payout_links in production".to_string()
        })),
        // Send empty set of whitelisted domains
        (_, true) => {
            Ok(HashSet::new())
        },
        // Otherwise, fetch and use allowed domains from profile config
        (_, false) => {
            profile_config
            .as_ref()
            .map(|config| config.config.allowed_domains.to_owned())
            .get_required_value("allowed_domains")
            .change_context(errors::ApiErrorResponse::LinkConfigurationError {
                message:
                    "Payout links cannot be used without setting allowed_domains in profile. If you're using a non-production environment, you can set test_mode to true while in payout_link_config"
                        .to_string(),
            })
        }
    }?;

    // Form data to be injected in the link
    let (logo, merchant_name, theme) = match ui_config {
        Some(config) => (config.logo, config.merchant_name, config.theme),
        _ => (None, None, None),
    };
    let payout_link_config = GenericLinkUiConfig {
        logo,
        merchant_name,
        theme,
    };
    let client_secret = utils::generate_id(consts::ID_LENGTH, "payout_link_secret");
    let base_url = profile_config
        .as_ref()
        .and_then(|c| c.config.domain_name.as_ref())
        .map(|domain| format!("https://{domain}"))
        .unwrap_or(state.base_url.clone());
    let session_expiry = req
        .session_expiry
        .as_ref()
        .map_or(default_config.expiry, |expiry| *expiry);
    let url = format!(
        "{base_url}/payout_link/{}/{}?locale={}",
        merchant_id.get_string_repr(),
        payout_id.get_string_repr(),
        locale
    );
    let link = url::Url::parse(&url)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable_lazy(|| format!("Failed to form payout link URL - {url}"))?;
    let req_enabled_payment_methods = payout_link_config_req
        .as_ref()
        .and_then(|req| req.enabled_payment_methods.to_owned());
    let amount = req
        .amount
        .as_ref()
        .get_required_value("amount")
        .attach_printable("amount is a required value when creating payout links")?;
    let currency = req
        .currency
        .as_ref()
        .get_required_value("currency")
        .attach_printable("currency is a required value when creating payout links")?;
    let payout_link_id = core_utils::get_or_generate_id(
        "payout_link_id",
        &payout_link_config_req
            .as_ref()
            .and_then(|config| config.payout_link_id.clone()),
        "payout_link",
    )?;
    let form_layout = payout_link_config_req
        .as_ref()
        .and_then(|config| config.form_layout.to_owned())
        .or_else(|| {
            profile_config
                .as_ref()
                .and_then(|config| config.form_layout.to_owned())
        });

    let data = PayoutLinkData {
        payout_link_id: payout_link_id.clone(),
        customer_id: customer_id.clone(),
        payout_id: payout_id.clone(),
        link,
        client_secret: Secret::new(client_secret),
        session_expiry,
        ui_config: payout_link_config,
        enabled_payment_methods: req_enabled_payment_methods,
        amount: MinorUnit::from(*amount),
        currency: *currency,
        allowed_domains,
        form_layout,
        test_mode: test_mode_in_config,
    };

    create_payout_link_db_entry(state, merchant_id, &data, req.return_url.clone()).await
}

pub async fn create_payout_link_db_entry(
    state: &SessionState,
    merchant_id: &id_type::MerchantId,
    payout_link_data: &PayoutLinkData,
    return_url: Option<String>,
) -> RouterResult<PayoutLink> {
    let db: &dyn StorageInterface = &*state.store;

    let link_data = serde_json::to_value(payout_link_data)
        .map_err(|_| report!(errors::ApiErrorResponse::InternalServerError))
        .attach_printable("Failed to convert PayoutLinkData to Value")?;

    let payout_link = GenericLinkNew {
        link_id: payout_link_data.payout_link_id.to_string(),
        primary_reference: payout_link_data.payout_id.get_string_repr().to_string(),
        merchant_id: merchant_id.to_owned(),
        link_type: common_enums::GenericLinkType::PayoutLink,
        link_status: GenericLinkStatus::PayoutLink(PayoutLinkStatus::Initiated),
        link_data,
        url: payout_link_data.link.to_string().into(),
        return_url,
        expiry: common_utils::date_time::now()
            + Duration::seconds(payout_link_data.session_expiry.into()),
        ..Default::default()
    };

    db.insert_payout_link(payout_link)
        .await
        .to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError {
            message: "payout link already exists".to_string(),
        })
}

#[instrument(skip_all)]
pub async fn get_mca_from_profile_id(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    profile_id: &id_type::ProfileId,
    connector_name: &str,


pub struct Fiservemea {
    amount_converter: &'static (dyn AmountConvertor<Output = StringMajorUnit> + Sync),
}


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


impl ApplePayPaymentData {
    /// Get the encrypted Apple Pay payment data if it exists
    pub fn get_encrypted_apple_pay_payment_data_optional(&self) -> Option<&String> {
        match self {
            Self::Encrypted(encrypted_data) => Some(encrypted_data),
            Self::Decrypted(_) => None,
        }
    }

    /// Get the decrypted Apple Pay payment data if it exists
    pub fn get_decrypted_apple_pay_payment_data_optional(&self) -> Option<&ApplePayPredecryptData> {
        match self {
            Self::Encrypted(_) => None,
            Self::Decrypted(decrypted_data) => Some(decrypted_data),
        }
    }

    /// Get the encrypted Apple Pay payment data, returning an error if it does not exist
    pub fn get_encrypted_apple_pay_payment_data_mandatory(
        &self,
    ) -> Result<&String, errors::ValidationError> {
        self.get_encrypted_apple_pay_payment_data_optional()
            .get_required_value("Encrypted Apple Pay payment data")
            .attach_printable("Encrypted Apple Pay payment data is mandatory")
    }

    /// Get the decrypted Apple Pay payment data, returning an error if it does not exist
    pub fn get_decrypted_apple_pay_payment_data_mandatory(
        &self,
    ) -> Result<&ApplePayPredecryptData, errors::ValidationError> {
        self.get_decrypted_apple_pay_payment_data_optional()
            .get_required_value("Decrypted Apple Pay payment data")
            .attach_printable("Decrypted Apple Pay payment data is mandatory")
    }
}


pub struct RSyncResponseData {
    search: RSyncSearchData,
}


pub async fn refresh_connector_auth(
    state: &SessionState,
    connector: &api_types::ConnectorData,
    _merchant_context: &domain::MerchantContext,
    router_data: &types::RouterData<
        api_types::AccessTokenAuth,
        types::AccessTokenRequestData,
        types::AccessToken,
    >,
) -> RouterResult<Result<types::AccessToken, types::ErrorResponse>> {
    let connector_integration: services::BoxedAccessTokenConnectorIntegrationInterface<
        api_types::AccessTokenAuth,
        types::AccessTokenRequestData,
        types::AccessToken,
    > = connector.connector.get_connector_integration();

    let access_token_router_data_result = services::execute_connector_processing_step(
        state,
        connector_integration,
        router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await;

    let access_token_router_data = match access_token_router_data_result {
        Ok(router_data) => Ok(router_data.response),
        Err(connector_error) => {
            // If we receive a timeout error from the connector, then
            // the error has to be handled gracefully by updating the payment status to failed.
            // further payment flow will not be continued
            if connector_error.current_context().is_connector_timeout() {
                let error_response = types::ErrorResponse {
                    code: consts::REQUEST_TIMEOUT_ERROR_CODE.to_string(),
                    message: consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string(),
                    reason: Some(consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string()),
                    status_code: 504,
                    attempt_status: None,
                    connector_transaction_id: None,
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                };

                Ok(Err(error_response))
            } else {
                Err(connector_error
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Could not refresh access token"))
            }
        }
    }?;

    metrics::ACCESS_TOKEN_CREATION.add(
        1,
        router_env::metric_attributes!(("connector", connector.connector_name.to_string())),
    );
    Ok(access_token_router_data)
}


pub async fn filter_resources<D, R, M>(
        &self,
        state: &KeyManagerState,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
        filter_resource_db_fut: R,
        filter_fn: impl Fn(&M) -> bool,
        FilterResourceParams {
            key,
            pattern,
            limit,
        }


File: crates/router/tests/connectors/datatrans.rs

use masking::Secret;
use router::types::{self, api, domain, storage::enums};
use test_utils::connector_auth;

use crate::utils::{self, ConnectorActions};

#[derive(Clone, Copy)]
struct DatatransTest;
impl ConnectorActions for DatatransTest {}
impl utils::Connector for DatatransTest {
    fn get_data(&self) -> api::ConnectorData {
        use router::connector::Adyen;
        utils::construct_connector_data_old(
            Box::new(Adyen::new()),
            types::Connector::Adyen,
            api::GetToken::Connector,
            None,
        )
    }

    fn get_auth_token(&self) -> types::ConnectorAuthType {
        utils::to_connector_auth_type(
            connector_auth::ConnectorAuthentication::new()
                .datatrans
                .expect("Missing connector authentication configuration")
                .into(),
        )
    }

    fn get_name(&self) -> String {
        "datatrans".to_string()
    }
}

static CONNECTOR: DatatransTest = DatatransTest {};

fn get_default_payment_info() -> Option<utils::PaymentInfo> {
    None
}

fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
    None
}

// Cards Positive Tests
// Creates a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_only_authorize_payment() {
    let response = CONNECTOR
        .authorize_payment(payment_method_details(), get_default_payment_info())
        .await
        .expect("Authorize payment response");
    assert_eq!(response.status, enums::AttemptStatus::Authorized);
}

// Captures a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_capture_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info())
        .await
        .expect("Capture payment response");
    assert_eq!(response.status, enums::AttemptStatus::Charged);
}

// Partially captures a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_partially_capture_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_capture_payment(
            payment_method_details(),
            Some(types::PaymentsCaptureData {
                amount_to_capture: 50,
                ..utils::PaymentCaptureType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .expect("Capture payment response");
    assert_eq!(response.status, enums::AttemptStatus::Charged);
}

// Synchronizes a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_authorized_payment() {
    let authorize_response = CONNECTOR
        .authorize_payment(payment_method_details(), get_default_payment_info())
        .await
        .expect("Authorize payment response");
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Authorized,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    txn_id.unwrap(),
                ),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .expect("PSync response");
    assert_eq!(response.status, enums::AttemptStatus::Authorized,);
}

// Voids a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_void_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_void_payment(
            payment_method_details(),
            Some(types::PaymentsCancelData {
                connector_transaction_id: String::from(""),
                cancellation_reason: Some("requested_by_customer".to_string()),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .expect("Void payment response");
    assert_eq!(response.status, enums::AttemptStatus::Voided);
}

// Refunds a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_manually_captured_payment() {
    let response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Partially refunds a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_partially_refund_manually_captured_payment() {
    let response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Synchronizes a refund using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_manually_captured_refund() {
    let refund_response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    let response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            refund_response.response.unwrap().connector_refund_id,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Creates a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_make_payment() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
}

// Synchronizes a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_auto_captured_payment() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    assert_ne!(txn_id, None, "Empty connector transaction id");
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Charged,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    txn_id.unwrap(),
                ),
                capture_method: Some(enums::CaptureMethod::Automatic),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(response.status, enums::AttemptStatus::Charged,);
}

// Refunds a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_auto_captured_payment() {
    let response = CONNECTOR
        .make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Partially refunds a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_partially_refund_succeeded_payment() {
    let refund_response = CONNECTOR
        .make_payment_and_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        refund_response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_succeeded_payment_multiple_times() {
    CONNECTOR
        .make_payment_and_multiple_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await;
}

// Synchronizes a refund using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_refund() {
    let refund_response = CONNECTOR
        .make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
        .await
        .unwrap();
    let response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            refund_response.response.unwrap().connector_refund_id,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Cards Negative scenarios
// Creates a payment with incorrect CVC.
#[actix_web::test]
async fn should_fail_payment_for_incorrect_cvc() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                payment_method_data: domain::PaymentMethodData::Card(domain::Card {
                    card_cvc: Secret::new("12345".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Your card's security code is invalid.".to_string(),
    );
}

// Creates a payment with incorrect expiry month.
#[actix_web::test]
async fn should_fail_payment_for_invalid_exp_month() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                payment_method_data: domain::PaymentMethodData::Card(domain::Card {
                    card_exp_month: Secret::new("20".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Your card's expiration month is invalid.".to_string(),
    );
}

// Creates a payment with incorrect expiry year.
#[actix_web::test]
async fn should_fail_payment_for_incorrect_expiry_year() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                payment_method_data: domain::PaymentMethodData::Card(domain::Card {
                    card_exp_year: Secret::new("2000".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Your card's expiration year is invalid.".to_string(),
    );
}

// Voids a payment using automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_fail_void_payment_for_auto_capture() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    assert_ne!(txn_id, None, "Empty connector transaction id");
    let void_response = CONNECTOR
        .void_payment(txn_id.unwrap(), None, get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(
        void_response.response.unwrap_err().message,
        "You cannot cancel this PaymentIntent because it has a status of succeeded."
    );
}

// Captures a payment using invalid connector payment id.
#[actix_web::test]
async fn should_fail_capture_for_invalid_payment() {
    let capture_response = CONNECTOR
        .capture_payment("123456789".to_string(), None, get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(
        capture_response.response.unwrap_err().message,
        String::from("No such payment_intent: '123456789'")
    );
}

// Refunds a payment with refund amount higher than payment amount.
#[actix_web::test]
async fn should_fail_for_refund_amount_higher_than_payment_amount() {
    let response = CONNECTOR
        .make_payment_and_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 150,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Refund amount (₹1.50) is greater than charge amount (₹1.00)",
    );
}

// Connector dependent test cases goes here

// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests


impl api::PaymentAuthorize for Breadpay {}

impl api::PaymentAuthorize for for Breadpay


impl ConnectorSpecifications for Juspaythreedsserver {
    fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {
        Some(&JUSPAYTHREEDSSERVER_CONNECTOR_INFO)
    }

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        None
    }

    fn get_supported_webhook_flows(&self) -> Option<&'static [common_enums::enums::EventClass]> {
        None
    }
}

impl ConnectorSpecifications for for Juspaythreedsserver


impl PaymentSession for Threedsecureio {}

impl PaymentSession for for Threedsecureio


File: crates/hyperswitch_connectors/src/connectors/sift/transformers.rs
 Public structs: 8

use common_enums::enums;
use common_utils::types::StringMinorUnit;
use hyperswitch_domain_models::{
    payment_method_data::PaymentMethodData,
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RefundsResponseData},
    types::{PaymentsAuthorizeRouterData, RefundsRouterData},
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};

use crate::types::{RefundsResponseRouterData, ResponseRouterData};

//TODO: Fill the struct with respective fields
pub struct SiftRouterData<T> {
    pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc.
    pub router_data: T,
}

impl<T> From<(StringMinorUnit, T)> for SiftRouterData<T> {
    fn from((amount, item): (StringMinorUnit, T)) -> Self {
        //Todo :  use utils to convert the amount to the type of amount that a connector accepts
        Self {
            amount,
            router_data: item,
        }
    }
}

//TODO: Fill the struct with respective fields
#[derive(Default, Debug, Serialize, PartialEq)]
pub struct SiftPaymentsRequest {
    amount: StringMinorUnit,
    card: SiftCard,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct SiftCard {
    number: cards::CardNumber,
    expiry_month: Secret<String>,
    expiry_year: Secret<String>,
    cvc: Secret<String>,
    complete: bool,
}

impl TryFrom<&SiftRouterData<&PaymentsAuthorizeRouterData>> for SiftPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &SiftRouterData<&PaymentsAuthorizeRouterData>) -> Result<Self, Self::Error> {
        match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented(
                "Card payment method not implemented".to_string(),
            )
            .into()),
            _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()),
        }
    }
}

//TODO: Fill the struct with respective fields
// Auth Struct
pub struct SiftAuthType {
    pub(super) api_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for SiftAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
                api_key: api_key.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}
// PaymentsResponse
//TODO: Append the remaining status flags
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum SiftPaymentStatus {
    Succeeded,
    Failed,
    #[default]
    Processing,
}

impl From<SiftPaymentStatus> for common_enums::AttemptStatus {
    fn from(item: SiftPaymentStatus) -> Self {
        match item {
            SiftPaymentStatus::Succeeded => Self::Charged,
            SiftPaymentStatus::Failed => Self::Failure,
            SiftPaymentStatus::Processing => Self::Authorizing,
        }
    }
}

//TODO: Fill the struct with respective fields
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SiftPaymentsResponse {
    status: SiftPaymentStatus,
    id: String,
}

impl<F, T> TryFrom<ResponseRouterData<F, SiftPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, SiftPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: common_enums::AttemptStatus::from(item.response.status),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.id),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

//TODO: Fill the struct with respective fields
// REFUND :
// Type definition for RefundRequest
#[derive(Default, Debug, Serialize)]
pub struct SiftRefundRequest {
    pub amount: StringMinorUnit,
}

impl<F> TryFrom<&SiftRouterData<&RefundsRouterData<F>>> for SiftRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &SiftRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        Ok(Self {
            amount: item.amount.to_owned(),
        })
    }
}

// Type definition for Refund Response

#[allow(dead_code)]
#[derive(Debug, Copy, Serialize, Default, Deserialize, Clone)]
pub enum RefundStatus {
    Succeeded,
    Failed,
    #[default]
    Processing,
}

impl From<RefundStatus> for enums::RefundStatus {
    fn from(item: RefundStatus) -> Self {
        match item {
            RefundStatus::Succeeded => Self::Success,
            RefundStatus::Failed => Self::Failure,
            RefundStatus::Processing => Self::Pending,
            //TODO: Review mapping
        }
    }
}

//TODO: Fill the struct with respective fields
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct RefundResponse {
    id: String,
    status: RefundStatus,
}

impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>> for RefundsRouterData<Execute> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, RefundResponse>> for RefundsRouterData<RSync> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

//TODO: Fill the struct with respective fields
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct SiftErrorResponse {
    pub status_code: u16,
    pub code: String,
    pub message: String,
    pub reason: Option<String>,
    pub network_advice_code: Option<String>,
    pub network_decline_code: Option<String>,
    pub network_error_message: Option<String>,
}


impl api::RefundExecute for Paysafe {}


File: crates/common_utils/src/types/user.rs

/// User related types
pub mod core;

/// Theme related types
pub mod theme;

pub use core::*;

pub use theme::*;


Web Documentation: Payment Orchestration | Hyperswitch
# Type: Web Doc

Hyperswitch’s Payment Orchestration platform is designed to simplify the complexities of managing multiple PSPs and various payment methods, ensuring optimal performance at every stage of the payment lifecycle.
Whether you're:
Accepting payments
from customers
Managing payouts
to vendors
Making recurring payments
Hyperswitch provides a
unified solution
to maximize efficiency while minimizing costs.
Built for businesses of all sizes and types—
e-commerce platforms, SaaS companies, and marketplaces
—our orchestration platform ensures
reliability and flexibility
, helping you stay ahead in a competitive landscape.
Accept Payments
Process Payouts
Smart Routing
Smart Retries
3DS / Strong Customer Authentication
Fraud & Risk Management
Subscriptions
Have Questions?
Join our
Slack Community
Prefer direct support? Use our
Contact Us
page to reach out.
\
6 months ago
Was this helpful?

Payment Orchestration | Hyperswitch


impl RecoveryPaymentTuple {
    pub fn new(
        payment_intent: &revenue_recovery::RecoveryPaymentIntent,
        payment_attempt: &revenue_recovery::RecoveryPaymentAttempt,
    ) -> Self {
        Self(payment_intent.clone(), payment_attempt.clone())
    }

    pub async fn publish_revenue_recovery_event_to_kafka(
        state: &SessionState,
        recovery_payment_tuple: &Self,
        retry_count: Option<i32>,
    ) -> CustomResult<(), errors::RevenueRecoveryError> {
        let recovery_payment_intent = &recovery_payment_tuple.0;
        let recovery_payment_attempt = &recovery_payment_tuple.1;
        let revenue_recovery_feature_metadata = recovery_payment_intent
            .feature_metadata
            .as_ref()
            .and_then(|metadata| metadata.revenue_recovery.as_ref());

        let billing_city = recovery_payment_intent
            .billing_address
            .as_ref()
            .and_then(|billing_address| billing_address.address.as_ref())
            .and_then(|address| address.city.clone())
            .map(Secret::new);

        let billing_state = recovery_payment_intent
            .billing_address
            .as_ref()
            .and_then(|billing_address| billing_address.address.as_ref())
            .and_then(|address| address.state.clone());

        let billing_country = recovery_payment_intent
            .billing_address
            .as_ref()
            .and_then(|billing_address| billing_address.address.as_ref())
            .and_then(|address| address.country);

        let card_info = revenue_recovery_feature_metadata.and_then(|metadata| {
            metadata
                .billing_connector_payment_method_details
                .as_ref()
                .and_then(|details| details.get_billing_connector_card_info())
        });

        #[allow(clippy::as_conversions)]
        let retry_count = Some(retry_count.unwrap_or_else(|| {
            revenue_recovery_feature_metadata
                .map(|data| data.total_retry_count as i32)
                .unwrap_or(0)
        }));

        let event = kafka::revenue_recovery::RevenueRecovery {
            merchant_id: &recovery_payment_intent.merchant_id,
            invoice_amount: recovery_payment_intent.invoice_amount,
            invoice_currency: &recovery_payment_intent.invoice_currency,
            invoice_date: revenue_recovery_feature_metadata.and_then(|data| {
                data.invoice_billing_started_at_time
                    .map(|time| time.assume_utc())
            }),
            invoice_due_date: revenue_recovery_feature_metadata
                .and_then(|data| data.invoice_next_billing_time.map(|time| time.assume_utc())),
            billing_city,
            billing_country: billing_country.as_ref(),
            billing_state,
            attempt_amount: recovery_payment_attempt.amount,
            attempt_currency: &recovery_payment_intent.invoice_currency.clone(),
            attempt_status: &recovery_payment_attempt.attempt_status.clone(),
            pg_error_code: recovery_payment_attempt.error_code.clone(),
            network_advice_code: recovery_payment_attempt.network_advice_code.clone(),
            network_error_code: recovery_payment_attempt.network_decline_code.clone(),
            first_pg_error_code: revenue_recovery_feature_metadata
                .and_then(|data| data.first_payment_attempt_pg_error_code.clone()),
            first_network_advice_code: revenue_recovery_feature_metadata
                .and_then(|data| data.first_payment_attempt_network_advice_code.clone()),
            first_network_error_code: revenue_recovery_feature_metadata
                .and_then(|data| data.first_payment_attempt_network_decline_code.clone()),
            attempt_created_at: recovery_payment_attempt.created_at.assume_utc(),
            payment_method_type: revenue_recovery_feature_metadata
                .map(|data| &data.payment_method_type),
            payment_method_subtype: revenue_recovery_feature_metadata
                .map(|data| &data.payment_method_subtype),
            card_network: card_info
                .as_ref()
                .and_then(|info| info.card_network.as_ref()),
            card_issuer: card_info.and_then(|data| data.card_issuer.clone()),
            retry_count,
            payment_gateway: revenue_recovery_feature_metadata.map(|data| data.connector),
        };
        state.event_handler.log_event(&event);
        Ok(())
    }
}


pub struct ResponseInsights {
    category_code: Option<String>,
    category: Option<String>,
}


impl ConnectorValidation for Phonepe {
    fn validate_mandate_payment(
        &self,
        _pm_type: Option<enums::PaymentMethodType>,
        pm_data: PaymentMethodData,
    ) -> CustomResult<(), errors::ConnectorError> {
        match pm_data {
            PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented(
                "validate_mandate_payment does not support cards".to_string(),
            )
            .into()),
            _ => Ok(()),
        }
    }

    fn validate_psync_reference_id(
        &self,
        _data: &PaymentsSyncData,
        _is_three_ds: bool,
        _status: enums::AttemptStatus,
        _connector_meta_data: Option<common_utils::pii::SecretSerdeValue>,
    ) -> CustomResult<(), errors::ConnectorError> {
        Ok(())
    }
}

impl ConnectorValidation for for Phonepe


impl Serialize for FiservCheckoutChargesRequest {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            Self::Checkout(inner) => inner.serialize(serializer),
            Self::Charges(inner) => inner.serialize(serializer),
        }
    }
}

impl Serialize for for FiservCheckoutChargesRequest


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

    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.razorpay.base_url.as_ref()
    }

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        let auth = razorpay::RazorpayAuthType::try_from(auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        let encoded_api_key = common_utils::consts::BASE64_ENGINE.encode(format!(
            "{}:{}",
            auth.razorpay_id.peek(),
            auth.razorpay_secret.peek()
        ));
        Ok(vec![(
            headers::AUTHORIZATION.to_string(),
            format!("Basic {encoded_api_key}").into_masked(),
        )])
    }

    fn build_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        let response: Result<razorpay::ErrorResponse, Report<common_utils::errors::ParsingError>> =
            res.response.parse_struct("Razorpay ErrorResponse");

        match response {
            Ok(response_data) => {
                event_builder.map(|i| i.set_error_response_body(&response_data));
                router_env::logger::info!(connector_response=?response_data);
                match response_data {
                    razorpay::ErrorResponse::RazorpayErrorResponse(error_response) => {
                        Ok(ErrorResponse {
                            status_code: res.status_code,
                            code: error_response.error.code,
                            message: error_response.error.description,
                            reason: error_response.error.reason,
                            attempt_status: None,
                            connector_transaction_id: None,
                            network_advice_code: None,
                            network_decline_code: None,
                            network_error_message: None,
                            connector_metadata: None,
                        })
                    }
                    razorpay::ErrorResponse::RazorpayError(error_response) => Ok(ErrorResponse {
                        status_code: res.status_code,
                        code: error_response.message.clone(),
                        message: error_response.message.clone(),
                        reason: Some(error_response.message),
                        attempt_status: None,
                        connector_transaction_id: None,
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    }),
                    razorpay::ErrorResponse::RazorpayStringError(error_string) => {
                        Ok(ErrorResponse {
                            status_code: res.status_code,
                            code: NO_ERROR_CODE.to_string(),
                            message: error_string.clone(),
                            reason: Some(error_string.clone()),
                            attempt_status: None,
                            connector_transaction_id: None,
                            network_advice_code: None,
                            network_decline_code: None,
                            network_error_message: None,
                            connector_metadata: None,
                        })
                    }
                }
            }
            Err(error_msg) => {
                event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code})));
                logger::error!(deserialization_error =? error_msg);
                handle_json_response_deserialization_failure(res, "razorpay")
            }
        }
    }
}


pub struct WiseRecipientCreateResponse {
    id: i64,
    business: Option<i64>,
    profile: Option<i64>,
    account_holder_name: Secret<String>,
    currency: String,
    country: String,
    #[serde(rename = "type")]
    request_type: String,
    details: Option<WiseBankDetails>,
}


File: crates/router/src/compatibility/wrap.rs
 Public functions: 1

use std::{future::Future, sync::Arc, time::Instant};

use actix_web::{HttpRequest, HttpResponse, Responder};
use common_utils::errors::{CustomResult, ErrorSwitch};
use router_env::{instrument, tracing, Tag};
use serde::Serialize;

use crate::{
    core::{api_locking, errors},
    events::api_logs::ApiEventMetric,
    routes::{
        app::{AppStateInfo, ReqState},
        AppState, SessionState,
    },
    services::{self, api, authentication as auth, logger},
};

#[instrument(skip(request, payload, state, func, api_authentication))]
pub async fn compatibility_api_wrap<'a, 'b, U, T, Q, F, Fut, S, E, E2>(
    flow: impl router_env::types::FlowMetric,
    state: Arc<AppState>,
    request: &'a HttpRequest,
    payload: T,
    func: F,
    api_authentication: &dyn auth::AuthenticateAndFetch<U, SessionState>,
    lock_action: api_locking::LockAction,
) -> HttpResponse
where
    F: Fn(SessionState, U, T, ReqState) -> Fut,
    Fut: Future<Output = CustomResult<api::ApplicationResponse<Q>, E2>>,
    E2: ErrorSwitch<E> + std::error::Error + Send + Sync + 'static,
    Q: Serialize + std::fmt::Debug + 'a + ApiEventMetric,
    S: TryFrom<Q> + Serialize,
    E: Serialize + error_stack::Context + actix_web::ResponseError + Clone,
    error_stack::Report<E>: services::EmbedError,
    errors::ApiErrorResponse: ErrorSwitch<E>,
    T: std::fmt::Debug + Serialize + ApiEventMetric,
{
    let request_method = request.method().as_str();
    let url_path = request.path();
    tracing::Span::current().record("request_method", request_method);
    tracing::Span::current().record("request_url_path", url_path);

    let start_instant = Instant::now();
    logger::info!(tag = ?Tag::BeginRequest, payload = ?payload);

    let server_wrap_util_res = api::server_wrap_util(
        &flow,
        state.clone().into(),
        request.headers(),
        request,
        payload,
        func,
        api_authentication,
        lock_action,
    )
    .await
    .map(|response| {
        logger::info!(api_response =? response);
        response
    });

    let res = match server_wrap_util_res {
        Ok(api::ApplicationResponse::Json(response)) => {
            let response = S::try_from(response);
            match response {
                Ok(response) => match serde_json::to_string(&response) {
                    Ok(res) => api::http_response_json(res),
                    Err(_) => api::http_response_err(
                        r#"{
                                "error": {
                                    "message": "Error serializing response from connector"
                                }
                            }"#,
                    ),
                },
                Err(_) => api::http_response_err(
                    r#"{
                        "error": {
                            "message": "Error converting juspay response to stripe response"
                        }
                    }"#,
                ),
            }
        }
        Ok(api::ApplicationResponse::JsonWithHeaders((response, headers))) => {
            let response = S::try_from(response);
            match response {
                Ok(response) => match serde_json::to_string(&response) {
                    Ok(res) => api::http_response_json_with_headers(res, headers, None, None),
                    Err(_) => api::http_response_err(
                        r#"{
                                "error": {
                                    "message": "Error serializing response from connector"
                                }
                            }"#,
                    ),
                },
                Err(_) => api::http_response_err(
                    r#"{
                        "error": {
                            "message": "Error converting juspay response to stripe response"
                        }
                    }"#,
                ),
            }
        }
        Ok(api::ApplicationResponse::StatusOk) => api::http_response_ok(),
        Ok(api::ApplicationResponse::TextPlain(text)) => api::http_response_plaintext(text),
        Ok(api::ApplicationResponse::FileData((file_data, content_type))) => {
            api::http_response_file_data(file_data, content_type)
        }
        Ok(api::ApplicationResponse::JsonForRedirection(response)) => {
            match serde_json::to_string(&response) {
                Ok(res) => api::http_redirect_response(res, response),
                Err(_) => api::http_response_err(
                    r#"{
                    "error": {
                        "message": "Error serializing response from connector"
                    }
                }"#,
                ),
            }
        }
        Ok(api::ApplicationResponse::Form(redirection_data)) => {
            let config = state.conf();
            api::build_redirection_form(
                &redirection_data.redirect_form,
                redirection_data.payment_method_data,
                redirection_data.amount,
                redirection_data.currency,
                config,
            )
            .respond_to(request)
            .map_into_boxed_body()
        }

        Ok(api::ApplicationResponse::GenericLinkForm(boxed_generic_link_data)) => {
            let link_type = (boxed_generic_link_data).data.to_string();
            match services::generic_link_response::build_generic_link_html(
                boxed_generic_link_data.data,
                boxed_generic_link_data.locale,
            ) {
                Ok(rendered_html) => api::http_response_html_data(rendered_html, None),
                Err(_) => {
                    api::http_response_err(format!("Error while rendering {link_type} HTML page"))
                }
            }
        }

        Ok(api::ApplicationResponse::PaymentLinkForm(boxed_payment_link_data)) => {
            match *boxed_payment_link_data {
                api::PaymentLinkAction::PaymentLinkFormData(payment_link_data) => {
                    match api::build_payment_link_html(payment_link_data) {
                        Ok(rendered_html) => api::http_response_html_data(rendered_html, None),
                        Err(_) => api::http_response_err(
                            r#"{
                                "error": {
                                    "message": "Error while rendering payment link html page"
                                }
                            }"#,
                        ),
                    }
                }
                api::PaymentLinkAction::PaymentLinkStatus(payment_link_data) => {
                    match api::get_payment_link_status(payment_link_data) {
                        Ok(rendered_html) => api::http_response_html_data(rendered_html, None),
                        Err(_) => api::http_response_err(
                            r#"{
                                "error": {
                                    "message": "Error while rendering payment link status page"
                                }
                            }"#,
                        ),
                    }
                }
            }
        }
        Err(error) => api::log_and_return_error_response(error),
    };

    let response_code = res.status().as_u16();
    let end_instant = Instant::now();
    let request_duration = end_instant.saturating_duration_since(start_instant);
    logger::info!(
        tag = ?Tag::EndRequest,
        status_code = response_code,
        time_taken_ms = request_duration.as_millis(),
    );

    res
}


Web Documentation: Web | Hyperswitch
# Type: Web Doc

Global Checkout Experience
Hyperswitch Unified Checkout is an inclusive, consistent and blended payment experience optimized for the best conversion rates.
Inclusive
A variety of global payment methods including cards, buy now pay later and digital wallets are supported by the Unified Checkout, with adaptation to local preferences and ability to local language customization.
Consistent
With a diverse set of payment methods supported, the Unified Checkout provides a singular consistent payment experience across platforms (web, android and ios) powered by smart payment forms, minimal redirections and intelligent retries.
Blended
The Unified Checkout includes 40+ styling APIs, which could be tweaked to make the payment experience blend with your product. Your users will get a fully native and embedded payment experience within your app or website
Modify and Experiment
While the Unified Checkout is pre-optimized for maximum conversions, Hyperswitch does not restrict you to stick to a one-size-fits-all approach. Using Hyperswitch SDK APIs, you get complete control over modifying the payment experience by,
Prioritizing payment methods
You can make an impact on the payment mix or conversion rates by prioritizing/ promoting specific payment methods for your customers.
Switching themes and layouts of checkout page
The Unified Checkout comes with a wide range of pre-designed themes and layouts which you can choose from.
Optimize
You can further optimize Unified Checkout web SDK by preloading all the resources that are needed by the iframe. By the time iframe is to be mounted (checkout button), everything that is required can be fetched from their server and stored in the disk cache.
<Elements/>
wrapper has to be used in the top-level of the merchants app , say web app has two pages eg: homepage and checkout page, the wrapper must be added in the homepage itself.
<Elements/>
has the required props to load our Hyperloader (script) which will
Preload the all the resources that are required by the SDK ie. files, svgs, icons, css, fonts etc.
Prefetch the two main API calls and is ready with response
This will significantly decrease the SDK load time from ~10-15s (in slow 3G network) to just ~1-5ms.
10 months ago
Was this helpful?

Web | Hyperswitch


OpenAPI Block Path: components.schemas.Currency
{
  "type": "string",
  "description": "The three-letter ISO 4217 currency code (e.g., \"USD\", \"EUR\") for the payment amount. This field is mandatory for creating a payment.",
  "enum": [
    "AED",
    "AFN",
    "ALL",
    "AMD",
    "ANG",
    "AOA",
    "ARS",
    "AUD",
    "AWG",
    "AZN",
    "BAM",
    "BBD",
    "BDT",
    "BGN",
    "BHD",
    "BIF",
    "BMD",
    "BND",
    "BOB",
    "BRL",
    "BSD",
    "BTN",
    "BWP",
    "BYN",
    "BZD",
    "CAD",
    "CDF",
    "CHF",
    "CLF",
    "CLP",
    "CNY",
    "COP",
    "CRC",
    "CUC",
    "CUP",
    "CVE",
    "CZK",
    "DJF",
    "DKK",
    "DOP",
    "DZD",
    "EGP",
    "ERN",
    "ETB",
    "EUR",
    "FJD",
    "FKP",
    "GBP",
    "GEL",
    "GHS",
    "GIP",
    "GMD",
    "GNF",
    "GTQ",
    "GYD",
    "HKD",
    "HNL",
    "HRK",
    "HTG",
    "HUF",
    "IDR",
    "ILS",
    "INR",
    "IQD",
    "IRR",
    "ISK",
    "JMD",
    "JOD",
    "JPY",
    "KES",
    "KGS",
    "KHR",
    "KMF",
    "KPW",
    "KRW",
    "KWD",
    "KYD",
    "KZT",
    "LAK",
    "LBP",
    "LKR",
    "LRD",
    "LSL",
    "LYD",
    "MAD",
    "MDL",
    "MGA",
    "MKD",
    "MMK",
    "MNT",
    "MOP",
    "MRU",
    "MUR",
    "MVR",
    "MWK",
    "MXN",
    "MYR",
    "MZN",
    "NAD",
    "NGN",
    "NIO",
    "NOK",
    "NPR",
    "NZD",
    "OMR",
    "PAB",
    "PEN",
    "PGK",
    "PHP",
    "PKR",
    "PLN",
    "PYG",
    "QAR",
    "RON",
    "RSD",
    "RUB",
    "RWF",
    "SAR",
    "SBD",
    "SCR",
    "SDG",
    "SEK",
    "SGD",
    "SHP",
    "SLE",
    "SLL",
    "SOS",
    "SRD",
    "SSP",
    "STD",
    "STN",
    "SVC",
    "SYP",
    "SZL",
    "THB",
    "TJS",
    "TMT",
    "TND",
    "TOP",
    "TRY",
    "TTD",
    "TWD",
    "TZS",
    "UAH",
    "UGX",
    "USD",
    "UYU",
    "UZS",
    "VES",
    "VND",
    "VUV",
    "WST",
    "XAF",
    "XCD",
    "XOF",
    "XPF",
    "YER",
    "ZAR",
    "ZMW",
    "ZWL"
  ]
}


pub fn check_refund_integrity<T, Request>(
    request: &Request,
    refund_response_data: &Result<types::RefundsResponseData, ErrorResponse>,
) -> Result<(), common_utils::errors::IntegrityCheckError>
where
    T: FlowIntegrity,
    Request: GetIntegrityObject<T> + CheckIntegrity<Request, T>,
{
    let connector_refund_id = refund_response_data
        .as_ref()
        .map(|resp_data| resp_data.connector_refund_id.clone())
        .ok();

    request.check_integrity(request, connector_refund_id.to_owned())
}


File: crates/test_utils/tests/connectors/authorizedotnet_wh_ui.rs

use rand::Rng;
use thirtyfour::{prelude::*, WebDriver};

use crate::{selenium::*, tester};

struct AuthorizedotnetSeleniumTest;

impl SeleniumTest for AuthorizedotnetSeleniumTest {
    fn get_connector_name(&self) -> String {
        "authorizedotnet".to_string()
    }
}

async fn should_make_webhook(web_driver: WebDriver) -> Result<(), WebDriverError> {
    let conn = AuthorizedotnetSeleniumTest {};
    let amount = rand::thread_rng().gen_range(50..1000); //This connector detects it as fraudulent payment if the same amount is used for multiple payments so random amount is passed for testing(
    conn.make_webhook_test(
        web_driver,
        &format!("{CHECKOUT_BASE_URL}/saved/227?amount={amount}"),
        vec![
            Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
            Event::Assert(Assert::IsPresent("status")),
            Event::Assert(Assert::IsPresent("processing")),
        ],
        10,
        "processing",
    )
    .await?;
    Ok(())
}

#[test]
fn should_make_webhook_test() {
    tester!(should_make_webhook);
}


impl ProcessTrackerDeprecated {
    pub fn server(state: AppState) -> Scope {
        use super::process_tracker::revenue_recovery;
        // TODO: Deprecated. Remove this in favour of /v2/process-trackers
        web::scope("/v2/process_tracker/revenue_recovery_workflow")
            .app_data(web::Data::new(state.clone()))
            .service(
                web::resource("/{revenue_recovery_id}")
                    .route(web::get().to(revenue_recovery::revenue_recovery_pt_retrieve_api)),
            )
    }
}


pub struct AcquirerData {
    /// Acquiring institution identification code as assigned by the DS receiving the AReq message.
    ///
    /// This field is limited to 11 characters. This field can be omitted if it there is a MerchantAcquirer already configured for 3DS Server,
    /// referenced by the acquirerMerchantId.
    ///
    /// This field is required if no MerchantAcquirer is present for the acquirer BIN in the 3DS Server configuration and
    /// for requests where messageCategory = 01 (PA). For requests where messageCategory=02 (NPA), the field is required
    /// only if scheme is Mastercard, for other schemes it is optional.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub acquirer_bin: Option<String>,

    /// Acquirer-assigned Merchant identifier.
    ///
    /// This may be the same value that is used in authorization requests sent on behalf of the 3DS Requestor and is represented in ISO 8583 formatting requirements.
    /// The field is limited to maximum 35 characters. Individual Directory Servers may impose specific format and character requirements on
    /// the contents of this field.
    ///
    /// This field will be used to identify the Directory Server where the AReq will be sent and the acquirerBin from the 3DS Server configuration.
    /// If no MerchantAcquirer configuration is present in the 3DS Server, the DirectoryServer information will be resolved from the scheme to which the cardholder account belongs to.
    ///
    /// This field is required if merchantConfigurationId is not provided in the request and messageCategory = 01 (PA).
    /// For Mastercard, if merchantConfigurationId is not provided, the field must be present if messageCategory = 02 (NPA).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub acquirer_merchant_id: Option<String>,

    /// Acquirer Country Code.
    ///
    /// This is the code of the country where the acquiring institution is located. The specified
    /// length of this field is 3 characters and will accept values according to the ISO 3166-1 numeric three-digit
    /// country code.
    ///
    /// The Directory Server may edit the value of this field provided by the 3DS Server.
    ///
    /// This field is required.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub acquirer_country_code: Option<String>,
}


pub struct MerchantAccountMetadata {
    pub compatible_connector: Option<api_enums::Connector>,

    #[serde(flatten)]
    pub data: Option<pii::SecretSerdeValue>,
}


impl api::PaymentSync for Katapult {}