File: crates/router/src/routes/admin.rs
 Public functions: 34

use actix_web::{web, HttpRequest, HttpResponse};
use router_env::{instrument, tracing, Flow};

use super::app::AppState;
use crate::{
    core::{admin::*, api_locking, errors},
    services::{api, authentication as auth, authorization::permissions::Permission},
    types::{api::admin, domain},
};

#[cfg(all(feature = "olap", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::OrganizationCreate))]
pub async fn organization_create(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<admin::OrganizationCreateRequest>,
) -> HttpResponse {
    let flow = Flow::OrganizationCreate;
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, _, req, _| create_organization(state, req),
        &auth::AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
#[cfg(all(feature = "olap", feature = "v2"))]
#[instrument(skip_all, fields(flow = ?Flow::OrganizationCreate))]
pub async fn organization_create(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<admin::OrganizationCreateRequest>,
) -> HttpResponse {
    let flow = Flow::OrganizationCreate;
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, _, req, _| create_organization(state, req),
        &auth::V2AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
#[cfg(all(feature = "olap", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::OrganizationUpdate))]
pub async fn organization_update(
    state: web::Data<AppState>,
    req: HttpRequest,
    org_id: web::Path<common_utils::id_type::OrganizationId>,
    json_payload: web::Json<admin::OrganizationUpdateRequest>,
) -> HttpResponse {
    let flow = Flow::OrganizationUpdate;
    let organization_id = org_id.into_inner();
    let org_id = admin::OrganizationId {
        organization_id: organization_id.clone(),
    };
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, _, req, _| update_organization(state, org_id.clone(), req),
        auth::auth_type(
            &auth::PlatformOrgAdminAuth {
                is_admin_auth_allowed: true,
                organization_id: Some(organization_id.clone()),
            },
            &auth::JWTAuthOrganizationFromRoute {
                organization_id,
                required_permission: Permission::OrganizationAccountWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
#[cfg(all(feature = "olap", feature = "v2"))]
#[instrument(skip_all, fields(flow = ?Flow::OrganizationUpdate))]
pub async fn organization_update(
    state: web::Data<AppState>,
    req: HttpRequest,
    org_id: web::Path<common_utils::id_type::OrganizationId>,
    json_payload: web::Json<admin::OrganizationUpdateRequest>,
) -> HttpResponse {
    let flow = Flow::OrganizationUpdate;
    let organization_id = org_id.into_inner();
    let org_id = admin::OrganizationId {
        organization_id: organization_id.clone(),
    };
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, _, req, _| update_organization(state, org_id.clone(), req),
        auth::auth_type(
            &auth::V2AdminApiAuth,
            &auth::JWTAuthOrganizationFromRoute {
                organization_id,
                required_permission: Permission::OrganizationAccountWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
#[cfg(all(feature = "olap", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::OrganizationRetrieve))]
pub async fn organization_retrieve(
    state: web::Data<AppState>,
    req: HttpRequest,
    org_id: web::Path<common_utils::id_type::OrganizationId>,
) -> HttpResponse {
    let flow = Flow::OrganizationRetrieve;
    let organization_id = org_id.into_inner();
    let payload = admin::OrganizationId {
        organization_id: organization_id.clone(),
    };

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, _, req, _| get_organization(state, req),
        auth::auth_type(
            &auth::PlatformOrgAdminAuth {
                is_admin_auth_allowed: true,
                organization_id: Some(organization_id.clone()),
            },
            &auth::JWTAuthOrganizationFromRoute {
                organization_id,
                required_permission: Permission::OrganizationAccountRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(all(feature = "olap", feature = "v2"))]
#[instrument(skip_all, fields(flow = ?Flow::OrganizationRetrieve))]
pub async fn organization_retrieve(
    state: web::Data<AppState>,
    req: HttpRequest,
    org_id: web::Path<common_utils::id_type::OrganizationId>,
) -> HttpResponse {
    let flow = Flow::OrganizationRetrieve;
    let organization_id = org_id.into_inner();
    let payload = admin::OrganizationId {
        organization_id: organization_id.clone(),
    };

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, _, req, _| get_organization(state, req),
        auth::auth_type(
            &auth::V2AdminApiAuth,
            &auth::JWTAuthOrganizationFromRoute {
                organization_id,
                required_permission: Permission::OrganizationAccountRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(all(feature = "olap", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountCreate))]
pub async fn merchant_account_create(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<admin::MerchantAccountCreate>,
) -> HttpResponse {
    let flow = Flow::MerchantsAccountCreate;
    let payload = json_payload.into_inner();
    if let Err(api_error) = payload
        .webhook_details
        .as_ref()
        .map(|details| {
            details
                .validate()
                .map_err(|message| errors::ApiErrorResponse::InvalidRequestData { message })
        })
        .transpose()
    {
        return api::log_and_return_error_response(api_error.into());
    }

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth, req, _| create_merchant_account(state, req, auth),
        &auth::PlatformOrgAdminAuth {
            is_admin_auth_allowed: true,
            organization_id: None,
        },
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(all(feature = "olap", feature = "v2"))]
#[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountCreate))]
pub async fn merchant_account_create(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<api_models::admin::MerchantAccountCreateWithoutOrgId>,
) -> HttpResponse {
    let flow = Flow::MerchantsAccountCreate;
    let headers = req.headers();

    let org_id = match auth::HeaderMapStruct::new(headers).get_organization_id_from_header() {
        Ok(org_id) => org_id,
        Err(e) => return api::log_and_return_error_response(e),
    };

    // Converting from MerchantAccountCreateWithoutOrgId to MerchantAccountCreate so we can use the existing
    // `create_merchant_account` function for v2 as well
    let json_payload = json_payload.into_inner();
    let new_request_payload_with_org_id = api_models::admin::MerchantAccountCreate {
        merchant_name: json_payload.merchant_name,
        merchant_details: json_payload.merchant_details,
        metadata: json_payload.metadata,
        organization_id: org_id,
        product_type: json_payload.product_type,
    };

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        new_request_payload_with_org_id,
        |state, _, req, _| create_merchant_account(state, req, None),
        &auth::V2AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountRetrieve))]
pub async fn retrieve_merchant_account(
    state: web::Data<AppState>,
    req: HttpRequest,
    mid: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::MerchantsAccountRetrieve;
    let merchant_id = mid.into_inner();
    let payload = admin::MerchantId {
        merchant_id: merchant_id.clone(),
    };
    api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, _, req, _| get_merchant_account(state, req, None),
        auth::auth_type(
            &auth::PlatformOrgAdminAuthWithMerchantIdFromRoute {
                merchant_id_from_route: merchant_id.clone(),
                is_admin_auth_allowed: true,
            },
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                // This should ideally be MerchantAccountRead, but since FE is calling this API for
                // profile level users currently keeping this as ProfileAccountRead. FE is removing
                // this API call for profile level users.
                // TODO: Convert this to MerchantAccountRead once FE changes are done.
                required_permission: Permission::ProfileAccountRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    )
    .await
}

/// Merchant Account - Retrieve
///
/// Retrieve a merchant account details.
#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountRetrieve))]
pub async fn retrieve_merchant_account(
    state: web::Data<AppState>,
    req: HttpRequest,
    mid: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::MerchantsAccountRetrieve;
    let merchant_id = mid.into_inner();
    let payload = admin::MerchantId {
        merchant_id: merchant_id.clone(),
    };
    api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, _, req, _| get_merchant_account(state, req, None),
        auth::auth_type(
            &auth::V2AdminApiAuth,
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                // This should ideally be MerchantAccountRead, but since FE is calling this API for
                // profile level users currently keeping this as ProfileAccountRead. FE is removing
                // this API call for profile level users.
                // TODO: Convert this to MerchantAccountRead once FE changes are done.
                required_permission: Permission::ProfileAccountRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    )
    .await
}

#[cfg(all(feature = "olap", feature = "v2"))]
#[instrument(skip_all, fields(flow = ?Flow::MerchantAccountList))]
pub async fn merchant_account_list(
    state: web::Data<AppState>,
    req: HttpRequest,
    organization_id: web::Path<common_utils::id_type::OrganizationId>,
) -> HttpResponse {
    let flow = Flow::MerchantAccountList;

    let organization_id = admin::OrganizationId {
        organization_id: organization_id.into_inner(),
    };

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        organization_id,
        |state, _, request, _| list_merchant_account(state, request),
        auth::auth_type(
            &auth::V2AdminApiAuth,
            &auth::JWTAuthMerchantFromHeader {
                required_permission: Permission::MerchantAccountRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(all(feature = "olap", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::MerchantAccountList))]
pub async fn merchant_account_list(
    state: web::Data<AppState>,
    req: HttpRequest,
    query_params: web::Query<api_models::admin::MerchantAccountListRequest>,
) -> HttpResponse {
    let flow = Flow::MerchantAccountList;

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        query_params.into_inner(),
        |state, auth, request, _| list_merchant_account(state, request, auth),
        auth::auth_type(
            &auth::PlatformOrgAdminAuth {
                is_admin_auth_allowed: true,
                organization_id: None,
            },
            &auth::JWTAuthMerchantFromHeader {
                required_permission: Permission::MerchantAccountRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Merchant Account - Update
///
/// To update an existing merchant account. Helpful in updating merchant details such as email, contact details, or other configuration details like webhook, routing algorithm etc
#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountUpdate))]
pub async fn update_merchant_account(
    state: web::Data<AppState>,
    req: HttpRequest,
    mid: web::Path<common_utils::id_type::MerchantId>,
    json_payload: web::Json<admin::MerchantAccountUpdate>,
) -> HttpResponse {
    let flow = Flow::MerchantsAccountUpdate;
    let merchant_id = mid.into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, _, req, _| merchant_account_update(state, &merchant_id, None, req),
        auth::auth_type(
            &auth::V2AdminApiAuth,
            &auth::JWTAuthMerchantFromRoute {
                merchant_id: merchant_id.clone(),
                required_permission: Permission::MerchantAccountWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountUpdate))]
pub async fn update_merchant_account(
    state: web::Data<AppState>,
    req: HttpRequest,
    mid: web::Path<common_utils::id_type::MerchantId>,
    json_payload: web::Json<admin::MerchantAccountUpdate>,
) -> HttpResponse {
    let flow = Flow::MerchantsAccountUpdate;
    let merchant_id = mid.into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, _, req, _| merchant_account_update(state, &merchant_id, None, req),
        auth::auth_type(
            &auth::PlatformOrgAdminAuthWithMerchantIdFromRoute {
                merchant_id_from_route: merchant_id.clone(),
                is_admin_auth_allowed: true,
            },
            &auth::JWTAuthMerchantFromRoute {
                merchant_id: merchant_id.clone(),
                required_permission: Permission::MerchantAccountWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Merchant Account - Delete
///
/// To delete a merchant account
#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountDelete))]
pub async fn delete_merchant_account(
    state: web::Data<AppState>,
    req: HttpRequest,
    mid: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::MerchantsAccountDelete;
    let mid = mid.into_inner();

    let payload = web::Json(admin::MerchantId { merchant_id: mid }).into_inner();
    api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, _, req, _| merchant_account_delete(state, req.merchant_id),
        &auth::V2AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    )
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountDelete))]
pub async fn delete_merchant_account(
    state: web::Data<AppState>,
    req: HttpRequest,
    mid: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::MerchantsAccountDelete;
    let mid = mid.into_inner();

    let payload = web::Json(admin::MerchantId { merchant_id: mid }).into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, _, req, _| merchant_account_delete(state, req.merchant_id),
        &auth::AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Merchant Connector - Create
///
/// Create a new Merchant Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialized services like Fraud / Accounting etc."
#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsCreate))]
pub async fn connector_create(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
    json_payload: web::Json<admin::MerchantConnectorCreate>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsCreate;
    let payload = json_payload.into_inner();
    let merchant_id = path.into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth_data, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth_data.merchant_account, auth_data.key_store),
            ));
            create_connector(state, req, merchant_context, auth_data.profile_id)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
            &auth::JWTAuthMerchantFromRoute {
                merchant_id: merchant_id.clone(),
                required_permission: Permission::ProfileConnectorWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
/// Merchant Connector - Create
///
/// Create a new Merchant Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialized services like Fraud / Accounting etc."
#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsCreate))]
pub async fn connector_create(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<admin::MerchantConnectorCreate>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsCreate;
    let payload = json_payload.into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth_data: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth_data.merchant_account, auth_data.key_store),
            ));
            create_connector(state, req, merchant_context, None)
        },
        auth::auth_type(
            &auth::AdminApiAuthWithMerchantIdFromHeader,
            &auth::JWTAuthMerchantFromHeader {
                required_permission: Permission::MerchantConnectorWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
/// Merchant Connector - Retrieve
///
/// Retrieve Merchant Connector Details
#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsRetrieve))]
pub async fn connector_retrieve(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<(
        common_utils::id_type::MerchantId,
        common_utils::id_type::MerchantConnectorAccountId,
    )>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsRetrieve;
    let (merchant_id, merchant_connector_id) = path.into_inner();
    let payload = web::Json(admin::MerchantConnectorId {
        merchant_id: merchant_id.clone(),
        merchant_connector_id,
    })
    .into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth, req, _| {
            retrieve_connector(
                state,
                req.merchant_id,
                auth.profile_id,
                req.merchant_connector_id,
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                // This should ideally be ProfileConnectorRead, but since this API responds with
                // sensitive data, keeping this as ProfileConnectorWrite
                // TODO: Convert this to ProfileConnectorRead once data is masked.
                required_permission: Permission::ProfileConnectorWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
/// Merchant Connector - Retrieve
///
/// Retrieve Merchant Connector Details
#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsRetrieve))]
pub async fn connector_retrieve(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantConnectorAccountId>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsRetrieve;
    let id = path.into_inner();
    let payload = web::Json(admin::MerchantConnectorId { id: id.clone() }).into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state,
         auth::AuthenticationData {
             merchant_account,
             key_store,
             ..
         },
         req,
         _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(merchant_account, key_store),
            ));
            retrieve_connector(state, merchant_context, req.id.clone())
        },
        auth::auth_type(
            &auth::AdminApiAuthWithMerchantIdFromHeader,
            &auth::JWTAuthMerchantFromHeader {
                required_permission: Permission::MerchantConnectorRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(all(feature = "olap", feature = "v2"))]
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsList))]
pub async fn connector_list(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::ProfileId>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsList;
    let profile_id = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        profile_id.to_owned(),
        |state, auth::AuthenticationData { key_store, .. }, _, _| {
            list_connectors_for_a_profile(state, key_store, profile_id.clone())
        },
        auth::auth_type(
            &auth::AdminApiAuthWithMerchantIdFromHeader,
            &auth::JWTAuthMerchantFromHeader {
                required_permission: Permission::MerchantConnectorRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Merchant Connector - List
///
/// List Merchant Connector Details for the merchant
#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsList))]
pub async fn connector_list(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsList;
    let merchant_id = path.into_inner();

    api::server_wrap(
        flow,
        state,
        &req,
        merchant_id.to_owned(),
        |state, _auth, merchant_id, _| list_payment_connectors(state, merchant_id, None),
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                required_permission: Permission::MerchantConnectorRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    )
    .await
}

#[cfg(all(feature = "v1", feature = "olap"))]
/// Merchant Connector - List
///
/// List Merchant Connector Details for the merchant
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsList))]
pub async fn connector_list_profile(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsList;
    let merchant_id = path.into_inner();

    api::server_wrap(
        flow,
        state,
        &req,
        merchant_id.to_owned(),
        |state, auth, merchant_id, _| {
            list_payment_connectors(
                state,
                merchant_id,
                auth.profile_id.map(|profile_id| vec![profile_id]),
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                required_permission: Permission::ProfileConnectorRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    )
    .await
}

/// Merchant Connector - Update
///
/// To update an existing Merchant Connector. Helpful in enabling / disabling different payment methods and other settings for the connector etc.
#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsUpdate))]
pub async fn connector_update(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<(
        common_utils::id_type::MerchantId,
        common_utils::id_type::MerchantConnectorAccountId,
    )>,
    json_payload: web::Json<api_models::admin::MerchantConnectorUpdate>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsUpdate;
    let (merchant_id, merchant_connector_id) = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, auth, req, _| {
            update_connector(
                state,
                &merchant_id,
                auth.profile_id,
                &merchant_connector_id,
                req,
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
            &auth::JWTAuthMerchantFromRoute {
                merchant_id: merchant_id.clone(),
                required_permission: Permission::ProfileConnectorWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Merchant Connector - Update
///
/// To update an existing Merchant Connector. Helpful in enabling / disabling different payment methods and other settings for the connector etc.
#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsUpdate))]
pub async fn connector_update(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantConnectorAccountId>,
    json_payload: web::Json<api_models::admin::MerchantConnectorUpdate>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsUpdate;
    let id = path.into_inner();
    let payload = json_payload.into_inner();
    let merchant_id = payload.merchant_id.clone();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, _, req, _| update_connector(state, &merchant_id, None, &id, req),
        auth::auth_type(
            &auth::V2AdminApiAuth,
            &auth::JWTAuthMerchantFromRoute {
                merchant_id: merchant_id.clone(),
                required_permission: Permission::MerchantConnectorWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
/// Merchant Connector - Delete
///
/// Delete or Detach a Merchant Connector from Merchant Account
#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsDelete))]
pub async fn connector_delete(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<(
        common_utils::id_type::MerchantId,
        common_utils::id_type::MerchantConnectorAccountId,
    )>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsDelete;
    let (merchant_id, merchant_connector_id) = path.into_inner();

    let payload = web::Json(admin::MerchantConnectorId {
        merchant_id: merchant_id.clone(),
        merchant_connector_id,
    })
    .into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, _, req, _| delete_connector(state, req.merchant_id, req.merchant_connector_id),
        auth::auth_type(
            &auth::AdminApiAuth,
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                required_permission: Permission::MerchantConnectorWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
/// Merchant Connector - Delete
///
/// Delete or Detach a Merchant Connector from Merchant Account
#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsDelete))]
pub async fn connector_delete(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantConnectorAccountId>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsDelete;
    let id = path.into_inner();

    let payload = web::Json(admin::MerchantConnectorId { id: id.clone() }).into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state,
         auth::AuthenticationData {
             merchant_account,
             key_store,
             ..
         },
         req,
         _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(merchant_account, key_store),
            ));
            delete_connector(state, merchant_context, req.id)
        },
        auth::auth_type(
            &auth::AdminApiAuthWithMerchantIdFromHeader,
            &auth::JWTAuthMerchantFromHeader {
                required_permission: Permission::MerchantConnectorWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
/// Merchant Account - Toggle KV
///
/// Toggle KV mode for the Merchant Account
#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn merchant_account_toggle_kv(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
    json_payload: web::Json<admin::ToggleKVRequest>,
) -> HttpResponse {
    let flow = Flow::ConfigKeyUpdate;
    let mut payload = json_payload.into_inner();
    payload.merchant_id = path.into_inner();

    api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, _, payload, _| kv_for_merchant(state, payload.merchant_id, payload.kv_enabled),
        &auth::AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    )
    .await
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn merchant_account_toggle_kv(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
    json_payload: web::Json<admin::ToggleKVRequest>,
) -> HttpResponse {
    let flow = Flow::ConfigKeyUpdate;
    let mut payload = json_payload.into_inner();
    payload.merchant_id = path.into_inner();

    api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, _, payload, _| kv_for_merchant(state, payload.merchant_id, payload.kv_enabled),
        &auth::V2AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    )
    .await
}
/// Merchant Account - Transfer Keys
///
/// Transfer Merchant Encryption key to keymanager
#[instrument(skip_all)]
pub async fn merchant_account_toggle_all_kv(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<admin::ToggleAllKVRequest>,
) -> HttpResponse {
    let flow = Flow::MerchantTransferKey;
    let payload = json_payload.into_inner();

    api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, _, payload, _| toggle_kv_for_all_merchants(state, payload.kv_enabled),
        &auth::AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    )
    .await
}

/// Merchant Account - KV Status
///
/// Toggle KV mode for the Merchant Account
#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn merchant_account_kv_status(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::ConfigKeyFetch;
    let merchant_id = path.into_inner();

    api::server_wrap(
        flow,
        state,
        &req,
        merchant_id,
        |state, _, req, _| check_merchant_account_kv_status(state, req),
        &auth::AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    )
    .await
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn merchant_account_kv_status(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::ConfigKeyFetch;
    let merchant_id = path.into_inner();

    api::server_wrap(
        flow,
        state,
        &req,
        merchant_id,
        |state, _, req, _| check_merchant_account_kv_status(state, req),
        &auth::V2AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    )
    .await
}
/// Merchant Account - KV Status
///
/// Toggle KV mode for the Merchant Account
#[instrument(skip_all)]
pub async fn merchant_account_transfer_keys(
    state: web::Data<AppState>,
    req: HttpRequest,
    payload: web::Json<api_models::admin::MerchantKeyTransferRequest>,
) -> HttpResponse {
    let flow = Flow::ConfigKeyFetch;
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload.into_inner(),
        |state, _, req, _| transfer_key_store_to_key_manager(state, req),
        &auth::AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Merchant Account - Platform Account
///
/// Enable platform account
#[instrument(skip_all)]
pub async fn merchant_account_enable_platform_account(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::EnablePlatformAccount;
    let merchant_id = path.into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        merchant_id,
        |state, _, req, _| enable_platform_account(state, req),
        &auth::AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


            "Merchant Represent"
            | "Respond to Dispute"
            | "Respond to PreArb"
            | "Request Arbitration"
            | "Request Pre-Arbitration"
            | "Create Arbitration"
            | "Record Arbitration"
            | "Create Pre-Arbitration"
            | "File Arbitration"
            | "File Pre-Arbitration"
            | "File Visa Pre-Arbitration"
            | "Send Representment"
            | "Send Response"
            | "Arbitration"
            | "Arbitration (Mastercard)"
            | "Arbitration Chargeback"
            | "Issuer Declined Pre-Arbitration"
            | "Issuer Arbitration"
            | "Request Response to Pre-Arbitration"
            | "Vantiv Represent"
            | "Vantiv Respond"
            | "Auto Represent"
            | "Arbitration Ruling" => Ok(common_enums::DisputeStatus::DisputeChallenged),

            "Arbitration Lost" | "Unsuccessful Arbitration" | "Unsuccessful Pre-Arbitration" => {
                Ok(common_enums::DisputeStatus::DisputeLost)
            }

            "Arbitration Won"
            | "Arbitration Split"
            | "Successful Arbitration"
            | "Successful Pre-Arbitration" => Ok(common_enums::DisputeStatus::DisputeWon),

            "Chargeback Reversal" => Ok(common_enums::DisputeStatus::DisputeCancelled),

            "Receive Network Transaction" => Ok(common_enums::DisputeStatus::DisputeOpened),

            "Unaccept" | "Unrepresent" => Ok(common_enums::DisputeStatus::DisputeOpened),

            unexpected_activity => Err(errors::ConnectorError::UnexpectedResponseError(
                bytes::Bytes::from(format!("Dispute Activity: {unexpected_activity})")),
            )
            .into()),
        }
    } else {
        match connector_utils::normalize_string(dispute_cycle.clone())
            .change_context(errors::ConnectorError::RequestEncodingFailed)?
            .as_str()
        {
            "arbitration"
            | "arbitrationmastercard"
            | "arbitrationsplit"
            | "representment"
            | "issuerarbitration"
            | "prearbitration"
            | "responsetoissuerarbitration"
            | "arbitrationchargeback" => Ok(api_models::enums::DisputeStatus::DisputeChallenged),
            "chargebackreversal" | "issueracceptedprearbitration" | "arbitrationwon" => {
                Ok(api_models::enums::DisputeStatus::DisputeWon)
            }
            "arbitrationlost" | "issuerdeclinedprearbitration" => {
                Ok(api_models::enums::DisputeStatus::DisputeLost)
            }
            "firstchargeback" | "retrievalrequest" | "rapiddisputeresolution" => {
                Ok(api_models::enums::DisputeStatus::DisputeOpened)
            }
            dispute_cycle => Err(errors::ConnectorError::UnexpectedResponseError(
                bytes::Bytes::from(format!("Dispute Stage: {dispute_cycle}")),
            )
            .into()),
        }
    }
}

fn convert_string_to_primitive_date(
    item: Option<String>,
) -> Result<Option<time::PrimitiveDateTime>, error_stack::Report<errors::ConnectorError>> {
    item.map(|day| {
        let full_datetime_str = format!("{day}T00:00:00");
        let format =
            time::macros::format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
        time::PrimitiveDateTime::parse(&full_datetime_str, &format)
    })
    .transpose()
    .change_context(errors::ConnectorError::ParsingFailed)
}

impl TryFrom<ChargebackCase> for DisputeSyncResponse {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: ChargebackCase) -> Result<Self, Self::Error> {
        let amount = connector_utils::convert_amount(
            &StringMinorUnitForConnector,
            item.chargeback_amount,
            item.chargeback_currency_type,
        )?;
        Ok(Self {
            object_reference_id: api_models::webhooks::ObjectReferenceId::PaymentId(
                api_models::payments::PaymentIdType::ConnectorTransactionId(item.vantiv_cnp_txn_id),
            ),
            amount,
            currency: item.chargeback_currency_type,
            dispute_stage: get_dispute_stage(item.cycle.clone())?,
            dispute_status: get_dispute_status(item.cycle.clone(), item.activity)?,
            connector_status: item.cycle.clone(),
            connector_dispute_id: item.case_id.clone(),
            connector_reason: item.reason_code_description.clone(),
            connector_reason_code: item.reason_code.clone(),
            challenge_required_by: convert_string_to_primitive_date(item.reply_by_day.clone())?,
            created_at: convert_string_to_primitive_date(item.date_received_by_vantiv_cnp.clone())?,
            updated_at: None,
        })
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ActivityType {
    MerchantAcceptsLiability,
    MerchantRepresent,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename = "chargebackUpdateRequest", rename_all = "camelCase")]
pub struct ChargebackUpdateRequest {
    #[serde(rename = "@xmlns")]
    xmlns: String,
    activity_type: ActivityType,
}

impl From<&SubmitEvidenceRouterData> for ChargebackUpdateRequest {
    fn from(_item: &SubmitEvidenceRouterData) -> Self {
        Self {
            xmlns: worldpayvantiv_constants::XML_CHARGEBACK.to_string(),
            activity_type: ActivityType::MerchantRepresent,
        }
    }
}

impl From<&AcceptDisputeRouterData> for ChargebackUpdateRequest {
    fn from(_item: &AcceptDisputeRouterData) -> Self {
        Self {
            xmlns: worldpayvantiv_constants::XML_CHARGEBACK.to_string(),
            activity_type: ActivityType::MerchantAcceptsLiability,
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename = "chargebackDocumentUploadResponse", rename_all = "camelCase")]
pub struct ChargebackDocumentUploadResponse {
    #[serde(rename = "@xmlns")]
    pub xmlns: String,
    pub merchant_id: String,
    pub case_id: String,
    pub document_id: Option<String>,
    pub response_code: WorldpayvantivFileUploadResponseCode,
    pub response_message: String,
}

#[derive(Debug, strum::Display, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub enum WorldpayvantivFileUploadResponseCode {
    #[serde(rename = "000")]
    #[strum(serialize = "000")]
    Success,

    #[serde(rename = "001")]
    #[strum(serialize = "001")]
    InvalidMerchant,

    #[serde(rename = "002")]
    #[strum(serialize = "002")]
    FutureUse002,

    #[serde(rename = "003")]
    #[strum(serialize = "003")]
    CaseNotFound,

    #[serde(rename = "004")]
    #[strum(serialize = "004")]
    CaseNotInMerchantQueue,

    #[serde(rename = "005")]
    #[strum(serialize = "005")]
    DocumentAlreadyExists,

    #[serde(rename = "006")]
    #[strum(serialize = "006")]
    InternalError,

    #[serde(rename = "007")]
    #[strum(serialize = "007")]
    FutureUse007,

    #[serde(rename = "008")]
    #[strum(serialize = "008")]
    MaxDocumentLimitReached,

    #[serde(rename = "009")]
    #[strum(serialize = "009")]
    DocumentNotFound,

    #[serde(rename = "010")]
    #[strum(serialize = "010")]
    CaseNotInValidCycle,

    #[serde(rename = "011")]
    #[strum(serialize = "011")]
    ServerBusy,

    #[serde(rename = "012")]
    #[strum(serialize = "012")]
    FileSizeExceedsLimit,

    #[serde(rename = "013")]
    #[strum(serialize = "013")]
    InvalidFileContent,

    #[serde(rename = "014")]
    #[strum(serialize = "014")]
    UnableToConvert,

    #[serde(rename = "015")]
    #[strum(serialize = "015")]
    InvalidImageSize,

    #[serde(rename = "016")]
    #[strum(serialize = "016")]
    MaxDocumentPageCountReached,
}

fn is_file_uploaded(vantiv_file_upload_status: WorldpayvantivFileUploadResponseCode) -> bool {
    match vantiv_file_upload_status {
        WorldpayvantivFileUploadResponseCode::Success
        | WorldpayvantivFileUploadResponseCode::FutureUse002
        | WorldpayvantivFileUploadResponseCode::FutureUse007 => true,
        WorldpayvantivFileUploadResponseCode::InvalidMerchant
        | WorldpayvantivFileUploadResponseCode::CaseNotFound
        | WorldpayvantivFileUploadResponseCode::CaseNotInMerchantQueue
        | WorldpayvantivFileUploadResponseCode::DocumentAlreadyExists
        | WorldpayvantivFileUploadResponseCode::InternalError
        | WorldpayvantivFileUploadResponseCode::MaxDocumentLimitReached
        | WorldpayvantivFileUploadResponseCode::DocumentNotFound
        | WorldpayvantivFileUploadResponseCode::CaseNotInValidCycle
        | WorldpayvantivFileUploadResponseCode::ServerBusy
        | WorldpayvantivFileUploadResponseCode::FileSizeExceedsLimit
        | WorldpayvantivFileUploadResponseCode::InvalidFileContent
        | WorldpayvantivFileUploadResponseCode::UnableToConvert
        | WorldpayvantivFileUploadResponseCode::InvalidImageSize
        | WorldpayvantivFileUploadResponseCode::MaxDocumentPageCountReached => false,
    }
}

impl
    TryFrom<
        ResponseRouterData<
            Upload,
            ChargebackDocumentUploadResponse,
            UploadFileRequestData,
            UploadFileResponse,
        >,
    > for RouterData<Upload, UploadFileRequestData, UploadFileResponse>
{
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        item: ResponseRouterData<
            Upload,
            ChargebackDocumentUploadResponse,
            UploadFileRequestData,
            UploadFileResponse,
        >,
    ) -> Result<Self, Self::Error> {
        let response = if is_file_uploaded(item.response.response_code) {
            let provider_file_id = item
                .response
                .document_id
                .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?;

            Ok(UploadFileResponse { provider_file_id })
        } else {
            Err(ErrorResponse {
                code: item.response.response_code.to_string(),
                message: item.response.response_message.clone(),
                reason: Some(item.response.response_message.clone()),
                status_code: item.http_code,
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            })
        };

        Ok(Self {
            response,
            ..item.data
        })
    }
}

impl
    TryFrom<
        ResponseRouterData<
            Retrieve,
            ChargebackDocumentUploadResponse,
            RetrieveFileRequestData,
            RetrieveFileResponse,
        >,
    > for RouterData<Retrieve, RetrieveFileRequestData, RetrieveFileResponse>
{
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        item: ResponseRouterData<
            Retrieve,
            ChargebackDocumentUploadResponse,
            RetrieveFileRequestData,
            RetrieveFileResponse,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(RetrieveFileRouterData {
            response: Err(ErrorResponse {
                code: item.response.response_code.to_string(),
                message: item.response.response_message.clone(),
                reason: Some(item.response.response_message.clone()),
                status_code: item.http_code,
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
            ..item.data.clone()
        })
    }
}

fn get_last_non_auxiliary_activity_type(activities: Vec<Activity>) -> Option<String> {
    let auxiliary_activities: std::collections::HashSet<&'static str> = [
        "Add Note",
        "Attach Document",
        "Attempted Attach Document",
        "Delete Document",
        "Update Document",
        "Move To Error Queue",
        "Assign to Vantiv",
        "Assign To Merchant",
        "Merchant Auto Assign",
        "Issuer Recalled",
        "Network Decision",
        "Request Declined",
        "Sent Gift",
        "Successful PayPal",
    ]
    .iter()
    .copied()
    .collect();

    let mut last_non_auxiliary_activity = None;

    for activity in activities {
        let auxiliary_activity = activity
            .activity_type
            .as_deref()
            .map(|activity_type| auxiliary_activities.contains(activity_type))
            .unwrap_or(false);

        if !auxiliary_activity {
            last_non_auxiliary_activity = activity.activity_type.clone()
        }
    }
    last_non_auxiliary_activity
}


/// returns the inner bool value
        pub fn is_true(&self) -> bool {
            self.0
        }


impl api::PaymentToken for Zen {}


OpenAPI Block Path: components.schemas.SdkType
{
  "type": "string",
  "description": "Enum representing the type of 3DS SDK.",
  "enum": [
    "01",
    "02",
    "03",
    "04",
    "05"
  ]
}


File: crates/router/src/types/storage/payment_method.rs
 Public functions: 6
 Public structs: 7

use api_models::payment_methods;
use diesel_models::enums;
pub use diesel_models::payment_method::{
    PaymentMethod, PaymentMethodNew, PaymentMethodUpdate, PaymentMethodUpdateInternal,
    TokenizeCoreWorkflow,
};

use crate::types::{api, domain};

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PaymentTokenKind {
    Temporary,
    Permanent,
}

#[cfg(feature = "v1")]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CardTokenData {
    pub payment_method_id: Option<String>,
    pub locker_id: Option<String>,
    pub token: String,
    pub network_token_locker_id: Option<String>,
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CardTokenData {
    pub payment_method_id: common_utils::id_type::GlobalPaymentMethodId,
    pub locker_id: Option<String>,
    pub token: String,
}

#[derive(Debug, Clone, serde::Serialize, Default, serde::Deserialize)]
pub struct PaymentMethodDataWithId {
    pub payment_method: Option<enums::PaymentMethod>,
    pub payment_method_data: Option<domain::PaymentMethodData>,
    pub payment_method_id: Option<String>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct GenericTokenData {
    pub token: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct WalletTokenData {
    pub payment_method_id: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[cfg(feature = "v1")]
pub enum PaymentTokenData {
    // The variants 'Temporary' and 'Permanent' are added for backwards compatibility
    // with any tokenized data present in Redis at the time of deployment of this change
    Temporary(GenericTokenData),
    TemporaryGeneric(GenericTokenData),
    Permanent(CardTokenData),
    PermanentCard(CardTokenData),
    AuthBankDebit(payment_methods::BankAccountTokenData),
    WalletToken(WalletTokenData),
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[cfg(feature = "v2")]
pub enum PaymentTokenData {
    TemporaryGeneric(GenericTokenData),
    PermanentCard(CardTokenData),
    AuthBankDebit(payment_methods::BankAccountTokenData),
}

impl PaymentTokenData {
    #[cfg(feature = "v1")]
    pub fn permanent_card(
        payment_method_id: Option<String>,
        locker_id: Option<String>,
        token: String,
        network_token_locker_id: Option<String>,
    ) -> Self {
        Self::PermanentCard(CardTokenData {
            payment_method_id,
            locker_id,
            token,
            network_token_locker_id,
        })
    }

    #[cfg(feature = "v2")]
    pub fn permanent_card(
        payment_method_id: common_utils::id_type::GlobalPaymentMethodId,
        locker_id: Option<String>,
        token: String,
    ) -> Self {
        Self::PermanentCard(CardTokenData {
            payment_method_id,
            locker_id,
            token,
        })
    }

    pub fn temporary_generic(token: String) -> Self {
        Self::TemporaryGeneric(GenericTokenData { token })
    }

    #[cfg(feature = "v1")]
    pub fn wallet_token(payment_method_id: String) -> Self {
        Self::WalletToken(WalletTokenData { payment_method_id })
    }

    #[cfg(feature = "v1")]
    pub fn is_permanent_card(&self) -> bool {
        matches!(self, Self::PermanentCard(_) | Self::Permanent(_))
    }

    #[cfg(feature = "v2")]
    pub fn is_permanent_card(&self) -> bool {
        matches!(self, Self::PermanentCard(_))
    }
}

#[cfg(feature = "v1")]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PaymentMethodListContext {
    pub card_details: Option<api::CardDetailFromLocker>,
    pub hyperswitch_token_data: Option<PaymentTokenData>,
    #[cfg(feature = "payouts")]
    pub bank_transfer_details: Option<api::BankPayout>,
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum PaymentMethodListContext {
    Card {
        card_details: api::CardDetailFromLocker,
        // TODO: Why can't these fields be mandatory?
        token_data: Option<PaymentTokenData>,
    },
    Bank {
        token_data: Option<PaymentTokenData>,
    },
    #[cfg(feature = "payouts")]
    BankTransfer {
        bank_transfer_details: api::BankPayout,
        token_data: Option<PaymentTokenData>,
    },
    TemporaryToken {
        token_data: Option<PaymentTokenData>,
    },
}

#[cfg(feature = "v2")]
impl PaymentMethodListContext {
    pub(crate) fn get_token_data(&self) -> Option<PaymentTokenData> {
        match self {
            Self::Card { token_data, .. }
            | Self::Bank { token_data }
            | Self::BankTransfer { token_data, .. }
            | Self::TemporaryToken { token_data } => token_data.clone(),
        }
    }
}

#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PaymentMethodStatusTrackingData {
    pub payment_method_id: String,
    pub prev_status: enums::PaymentMethodStatus,
    pub curr_status: enums::PaymentMethodStatus,
    pub merchant_id: common_utils::id_type::MerchantId,
}


Documentation: api-reference/v1/routing/routing--update-default-config.mdx
# Type: Doc File

---
openapi: post /routing/default
---


pub fn get_entity_id(&self) -> &str {
        &self.entity_id
    }


impl api::PaymentAuthorize for Airwallex {}

impl api::PaymentAuthorize for for Airwallex


pub struct GocardlessPaymentsRequest {
    payments: GocardlessPayment,
}


pub async fn payments_redirect_response(
    state: web::Data<app::AppState>,
    req: actix_web::HttpRequest,
    json_payload: Option<web::Form<serde_json::Value>>,
    path: web::Path<(
        common_utils::id_type::PaymentId,
        common_utils::id_type::MerchantId,
        String,
    )


pub struct TtlForExtendedCardInfo(u16);


pub(crate) fn get_santander_webhook_event(
    event_type: FunctionType,
) -> api_models::webhooks::IncomingWebhookEvent {
    // need to confirm about the other possible webhook event statues, as of now only two known
    match event_type {
        FunctionType::Pagamento => api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess,
        FunctionType::Estorno => api_models::webhooks::IncomingWebhookEvent::RefundSuccess,
    }
}


pub async fn list_payment_methods_for_session(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile: domain::Profile,
    payment_method_session_id: id_type::GlobalPaymentMethodSessionId,
) -> RouterResponse<api::PaymentMethodListResponseForSession> {
    let key_manager_state = &(&state).into();

    let db = &*state.store;

    let payment_method_session = db
        .get_payment_methods_session(
            key_manager_state,
            merchant_context.get_merchant_key_store(),
            &payment_method_session_id,
        )
        .await
        .change_context(errors::ApiErrorResponse::PaymentMethodNotFound)
        .attach_printable("Unable to find payment method")?;

    let payment_connector_accounts = db
        .list_enabled_connector_accounts_by_profile_id(
            key_manager_state,
            profile.get_id(),
            merchant_context.get_merchant_key_store(),
            common_enums::ConnectorType::PaymentProcessor,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("error when fetching merchant connector accounts")?;

    let customer_payment_methods = list_customer_payment_methods_core(
        &state,
        &merchant_context,
        &payment_method_session.customer_id,
    )
    .await?;

    let response =
        hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled::from_payment_connectors_list(payment_connector_accounts)
            .perform_filtering()
            .get_required_fields(RequiredFieldsInput::new(state.conf.required_fields.clone()))
            .generate_response_for_session(customer_payment_methods);

    Ok(hyperswitch_domain_models::api::ApplicationResponse::Json(
        response,
    ))
}


impl ConnectorValidation for Coinbase {}

impl ConnectorValidation for for Coinbase


pub fn get_client_id(&self) -> Secret<String> {
        match self {
            Self::StandardIntegration(item) => item.client_id.clone(),
            Self::PartnerIntegration(item) => item.client_id.clone(),
        }
    }


pub struct RedsysThreeDsInvokeData {
    pub three_ds_method_url: String,
    pub three_ds_method_data: String,
    pub message_version: String,
    pub directory_server_id: String,
    pub three_ds_method_data_submission: bool,
}


impl api::ConnectorAccessToken for Trustpay {}

impl api::ConnectorAccessToken for for Trustpay


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

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

impl ConnectorSpecifications for for Stripebilling


Documentation: api-reference/v2/payment-methods/payment-method--create.mdx
# Type: Doc File

---
openapi: post /v2/payment-methods
---


pub struct BeginTotpResponse {
    pub secret: Option<TotpSecret>,
}


pub struct Authentication;


impl HealthCheckDbInterface for KafkaStore {
    async fn health_check_db(&self) -> CustomResult<(), errors::HealthCheckDBError> {
        self.diesel_store.health_check_db().await
    }
}

impl HealthCheckDbInterface for for KafkaStore


impl api::Payment for Trustpay {}


OpenAPI Block Path: paths."/payouts/{payout_id}"
{
  "get": {
    "tags": [
      "Payouts"
    ],
    "summary": "Payouts - Retrieve",
    "operationId": "Retrieve a Payout",
    "parameters": [
      {
        "name": "payout_id",
        "in": "path",
        "description": "The identifier for payout",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      {
        "name": "force_sync",
        "in": "query",
        "description": "Sync with the connector to get the payout details (defaults to false)",
        "required": false,
        "schema": {
          "type": "boolean",
          "nullable": true
        }
      }
    ],
    "responses": {
      "200": {
        "description": "Payout retrieved",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PayoutCreateResponse"
            }
          }
        }
      },
      "404": {
        "description": "Payout does not exist in our records"
      }
    },
    "security": [
      {
        "api_key": []
      }
    ]
  },
  "post": {
    "tags": [
      "Payouts"
    ],
    "summary": "Payouts - Update",
    "operationId": "Update a Payout",
    "parameters": [
      {
        "name": "payout_id",
        "in": "path",
        "description": "The identifier for payout",
        "required": true,
        "schema": {
          "type": "string"
        }
      }
    ],
    "requestBody": {
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PayoutUpdateRequest"
          }
        }
      },
      "required": true
    },
    "responses": {
      "200": {
        "description": "Payout updated",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PayoutCreateResponse"
            }
          }
        }
      },
      "400": {
        "description": "Missing Mandatory fields"
      }
    },
    "security": [
      {
        "api_key": []
      }
    ]
  }
}


pub fn http_response_json<T: body::MessageBody + 'static>(response: T) -> HttpResponse {
    HttpResponse::Ok()
        .content_type(mime::APPLICATION_JSON)
        .body(response)
}


pub async fn get_three_ds_decision_rule_output(
    state: &SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    request: api_models::three_ds_decision_rule::ThreeDsDecisionRuleExecuteRequest,
) -> errors::RouterResult<common_types::three_ds_decision_rule_engine::ThreeDSDecision> {
    let db = state.store.as_ref();
    // Retrieve the rule from database
    let routing_algorithm = db
        .find_routing_algorithm_by_algorithm_id_merchant_id(&request.routing_id, merchant_id)
        .await
        .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?;
    let algorithm: Algorithm = routing_algorithm
        .algorithm_data
        .parse_value("Algorithm")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error parsing program from three_ds_decision rule algorithm")?;
    let program: ast::Program<ThreeDSDecisionRule> = algorithm
        .data
        .parse_value("Program<ThreeDSDecisionRule>")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error parsing program from three_ds_decision rule algorithm")?;
    // Construct backend input from request
    let backend_input = dsl_inputs::BackendInput::foreign_from(request.clone());
    // Initialize interpreter with the rule program
    let interpreter = backend::VirInterpreterBackend::with_program(program)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error initializing DSL interpreter backend")?;
    // Execute the rule
    let result = interpreter
        .execute(backend_input)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error executing 3DS decision rule")?;
    // Apply PSD2 validations to the decision
    let final_decision =
        utils::apply_psd2_validations_during_execute(result.get_output().get_decision(), &request);
    Ok(final_decision)
}


impl common_utils::events::ApiEventMetric for ProxyRequest {}

impl common_utils::events::ApiEventMetric for for ProxyRequest


Web Documentation: Cost Observability | Hyperswitch
# Type: Web Doc

Enterprises often face challenges and find difficulty in understanding and managing their payment processing costs, due to complexity, variability of the underlying fee structures and the entities involved. These fees typically consist of:
Interchange Fees
: Paid to the customer bank for processing a card-based transaction. These fees vary based on factors such as card type, transaction type, region and certain qualification criteria defined by card networks.
Scheme Fees:
Paid to card networks (e.g., Visa, Mastercard) to cover the costs of maintaining and operating their global payment infrastructure. These fees often include charges for fraud prevention, security, network maintenance and also penalties for non-compliance.
Acquirer Fees
: Fees imposed by payment acquirers or processors on merchants managing and processing card transactions.
The complexity of these fee structures, combined with the need to manage multiple scheme categories, interchange programs, and Payment Service Providers (PSPs), makes the process particularly challenging for businesses to understand and solve payment costs.
Hyperswitch Cost Observability solves this problem by transforming unstructured payment fee data from multiple sources into comprehensive insights, providing better visibility into payment costs for different user personas within an organization. It enables businesses to monitor card payment cost, detect monthly anomalies (cost spikes), and identify optimization opportunities to reduce payment costs efficiently.
Key Features
Observe/Monitor
: Monitor trends across cost categories such as interchange fees, downgrades, scheme fees, and processor fees over different periods of time (e.g., 1 to N months). This system enables businesses to identify anomalies and cost deviations improving payment cost visibility.
Audit
: Detects discrepancies such as PSP errors, invoice mismatches, overcharges. This auditing process often uncovers significant cost savings without requiring additional engineering effort.
Optimize
: Offers actionable insights designed to help businesses achieve maximum savings by improving interchange qualifications, reducing scheme fees, and minimizing penalties. These recommendations ensure proactive cost optimization insights.
Forecast:
Merchants can forecast the cost impact of network updates on their payment costs for any organization via brand/region/connector with monthly deltas and effective dates.
Check out the
Cost Observability Demo
and try it out!
Exporting payments data
1 month ago
Was this helpful?

Cost Observability | Hyperswitch


pub struct ProcessingInfoResponse {
    pub processing_status: BluesnapProcessingStatus,
    pub authorization_code: Option<String>,
    pub network_transaction_id: Option<Secret<String>>,
}


impl api::PaymentSync for Billwerk {}


pub async fn mandate_procedure<F, FData>(
    state: &SessionState,
    resp: &types::RouterData<F, FData, types::PaymentsResponseData>,
    customer_id: &Option<id_type::CustomerId>,
    pm_id: Option<String>,
    merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
    storage_scheme: MerchantStorageScheme,
    payment_id: &id_type::PaymentId,
) -> errors::RouterResult<Option<String>>
where
    FData: MandateBehaviour,
{
    let Ok(ref response) = resp.response else {
        return Ok(None);
    };

    match resp.request.get_mandate_id() {
        Some(mandate_id) => {
            let Some(ref mandate_id) = mandate_id.mandate_id else {
                return Ok(None);
            };
            let orig_mandate = state
                .store
                .find_mandate_by_merchant_id_mandate_id(
                    &resp.merchant_id,
                    mandate_id,
                    storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?;
            let mandate = match orig_mandate.mandate_type {
                storage_enums::MandateType::SingleUse => state
                    .store
                    .update_mandate_by_merchant_id_mandate_id(
                        &resp.merchant_id,
                        mandate_id,
                        storage::MandateUpdate::StatusUpdate {
                            mandate_status: storage_enums::MandateStatus::Revoked,
                        },
                        orig_mandate,
                        storage_scheme,
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::MandateUpdateFailed),
                storage_enums::MandateType::MultiUse => state
                    .store
                    .update_mandate_by_merchant_id_mandate_id(
                        &resp.merchant_id,
                        mandate_id,
                        storage::MandateUpdate::CaptureAmountUpdate {
                            amount_captured: Some(
                                orig_mandate.amount_captured.unwrap_or(0)
                                    + resp.request.get_amount(),
                            ),
                        },
                        orig_mandate,
                        storage_scheme,
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::MandateUpdateFailed),
            }?;
            metrics::SUBSEQUENT_MANDATE_PAYMENT.add(
                1,
                router_env::metric_attributes!(("connector", mandate.connector)),
            );
            Ok(Some(mandate_id.clone()))
        }
        None => {
            let Some(_mandate_details) = resp.request.get_setup_mandate_details() else {
                return Ok(None);
            };
            let (mandate_reference, network_txn_id) = match &response {
                types::PaymentsResponseData::TransactionResponse {
                    mandate_reference,
                    network_txn_id,
                    ..
                } => (mandate_reference.clone(), network_txn_id.clone()),
                _ => (Box::new(None), None),
            };

            let mandate_ids = (*mandate_reference)
                .as_ref()
                .map(|md| {
                    md.encode_to_value()
                        .change_context(errors::ApiErrorResponse::MandateSerializationFailed)
                        .map(masking::Secret::new)
                })
                .transpose()?;

            let Some(new_mandate_data) = payment_helper::generate_mandate(
                resp.merchant_id.clone(),
                payment_id.to_owned(),
                resp.connector.clone(),
                resp.request.get_setup_mandate_details().cloned(),
                customer_id,
                pm_id.get_required_value("payment_method_id")?,
                mandate_ids,
                network_txn_id,
                get_insensitive_payment_method_data_if_exists(resp),
                *mandate_reference,
                merchant_connector_id,
            )?
            else {
                return Ok(None);
            };

            let connector = new_mandate_data.connector.clone();
            logger::debug!("{:?}", new_mandate_data);

            let res_mandate_id = new_mandate_data.mandate_id.clone();

            state
                .store
                .insert_mandate(new_mandate_data, storage_scheme)
                .await
                .to_duplicate_response(errors::ApiErrorResponse::DuplicateMandate)?;
            metrics::MANDATE_COUNT.add(1, router_env::metric_attributes!(("connector", connector)));
            Ok(Some(res_mandate_id))
        }
    }
}


impl Serialize for DecryptedData {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let data = BASE64_ENGINE.encode(self.0.peek());
        serializer.serialize_str(&data)
    }
}


pub async fn pg_connection_read<T: crate::DatabaseStore>(
    store: &T,
) -> errors::CustomResult<
    PooledConnection<'_, async_bb8_diesel::ConnectionManager<PgConnection>>,
    crate::errors::StorageError,
> {
    // If only OLAP is enabled get replica pool.
    #[cfg(all(feature = "olap", not(feature = "oltp")))]
    let pool = store.get_replica_pool();

    // If either one of these are true we need to get master pool.
    //  1. Only OLTP is enabled.
    //  2. Both OLAP and OLTP is enabled.
    //  3. Both OLAP and OLTP is disabled.
    #[cfg(any(
        all(not(feature = "olap"), feature = "oltp"),
        all(feature = "olap", feature = "oltp"),
        all(not(feature = "olap"), not(feature = "oltp"))
    ))]
    let pool = store.get_master_pool();

    pool.get()
        .await
        .change_context(crate::errors::StorageError::DatabaseConnectionError)
}


/// Result type for webhook processing path determination
pub struct WebhookProcessingResult {
    event_type: webhooks::IncomingWebhookEvent,
    source_verified: bool,
    transform_data: Option<Box<unified_connector_service::WebhookTransformData>>,
    decoded_body: Option<actix_web::web::Bytes>,
}


pub async fn update_default_fallback_routing_of_connectors_under_profile(
        self,
        db: &dyn StorageInterface,
        updated_config: &Vec<routing_types::RoutableConnectorChoice>,
        key_manager_state: &KeyManagerState,
        merchant_key_store: &domain::MerchantKeyStore,
    ) -> RouterResult<()> {
        let default_fallback_routing = Secret::from(
            updated_config
                .encode_to_value()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to convert routing ref to value")?,
        );
        let profile_update = domain::ProfileUpdate::DefaultRoutingFallbackUpdate {
            default_fallback_routing: Some(default_fallback_routing),
        };

        db.update_profile_by_profile_id(
            key_manager_state,
            merchant_key_store,
            self.profile,
            profile_update,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to update routing algorithm ref in business profile")?;
        Ok(())
    }


impl Responder {
    let flow = Flow::DecisionManagerDeleteConfig;
    Box::pin(oss_api::server_wrap(
        flow,
        state,
        &req,
        (),
        |state, auth: auth::AuthenticationData, (), _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            conditional_config::delete_conditional_config(state, merchant_context)
        },
        #[cfg(not(feature = "release"))]
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::MerchantThreeDsDecisionManagerWrite,
            },
            req.headers(),
        ),
        #[cfg(feature = "release")]
        &auth::JWTAuth {
            permission: Permission::MerchantThreeDsDecisionManagerWrite,
        },
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


impl Blocklist {
    pub fn server(state: AppState) -> Scope {
        web::scope("/blocklist")
            .app_data(web::Data::new(state))
            .service(
                web::resource("")
                    .route(web::get().to(blocklist::list_blocked_payment_methods))
                    .route(web::post().to(blocklist::add_entry_to_blocklist))
                    .route(web::delete().to(blocklist::remove_entry_from_blocklist)),
            )
            .service(
                web::resource("/toggle").route(web::post().to(blocklist::toggle_blocklist_guard)),
            )
    }
}


File: crates/common_utils/src/id_type/global_id.rs
 Public functions: 2
 Public structs: 1

pub(super) mod customer;
pub(super) mod payment;
pub(super) mod payment_methods;
pub(super) mod refunds;
pub(super) mod token;

use diesel::{backend::Backend, deserialize::FromSql, serialize::ToSql, sql_types};
use error_stack::ResultExt;
use thiserror::Error;

use crate::{
    consts::{CELL_IDENTIFIER_LENGTH, MAX_GLOBAL_ID_LENGTH, MIN_GLOBAL_ID_LENGTH},
    errors, generate_time_ordered_id,
    id_type::{AlphaNumericId, AlphaNumericIdError, LengthId, LengthIdError},
};

#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize)]
/// A global id that can be used to identify any entity
/// This id will have information about the entity and cell in a distributed system architecture
pub(crate) struct GlobalId(LengthId<MAX_GLOBAL_ID_LENGTH, MIN_GLOBAL_ID_LENGTH>);

#[derive(Clone, Copy)]
/// Entities that can be identified by a global id
pub(crate) enum GlobalEntity {
    Customer,
    Payment,
    Attempt,
    PaymentMethod,
    Refund,
    PaymentMethodSession,
    Token,
}

impl GlobalEntity {
    fn prefix(self) -> &'static str {
        match self {
            Self::Customer => "cus",
            Self::Payment => "pay",
            Self::PaymentMethod => "pm",
            Self::Attempt => "att",
            Self::Refund => "ref",
            Self::PaymentMethodSession => "pms",
            Self::Token => "tok",
        }
    }
}

/// Cell identifier for an instance / deployment of application
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct CellId(LengthId<CELL_IDENTIFIER_LENGTH, CELL_IDENTIFIER_LENGTH>);

#[derive(Debug, Error, PartialEq, Eq)]
pub enum CellIdError {
    #[error("cell id error: {0}")]
    InvalidCellLength(LengthIdError),

    #[error("{0}")]
    InvalidCellIdFormat(AlphaNumericIdError),
}

impl From<LengthIdError> for CellIdError {
    fn from(error: LengthIdError) -> Self {
        Self::InvalidCellLength(error)
    }
}

impl From<AlphaNumericIdError> for CellIdError {
    fn from(error: AlphaNumericIdError) -> Self {
        Self::InvalidCellIdFormat(error)
    }
}

impl CellId {
    /// Create a new cell id from a string
    fn from_str(cell_id_string: impl AsRef<str>) -> Result<Self, CellIdError> {
        let trimmed_input_string = cell_id_string.as_ref().trim().to_string();
        let alphanumeric_id = AlphaNumericId::from(trimmed_input_string.into())?;
        let length_id = LengthId::from_alphanumeric_id(alphanumeric_id)?;
        Ok(Self(length_id))
    }

    /// Create a new cell id from a string
    pub fn from_string(
        input_string: impl AsRef<str>,
    ) -> error_stack::Result<Self, errors::ValidationError> {
        Self::from_str(input_string).change_context(
            errors::ValidationError::IncorrectValueProvided {
                field_name: "cell_id",
            },
        )
    }

    /// Get the string representation of the cell id
    fn get_string_repr(&self) -> &str {
        &self.0 .0 .0
    }
}

impl<'de> serde::Deserialize<'de> for CellId {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let deserialized_string = String::deserialize(deserializer)?;
        Self::from_str(deserialized_string.as_str()).map_err(serde::de::Error::custom)
    }
}

/// Error generated from violation of constraints for MerchantReferenceId
#[derive(Debug, Error, PartialEq, Eq)]
pub(crate) enum GlobalIdError {
    /// The format for the global id is invalid
    #[error("The id format is invalid, expected format is {{cell_id:5}}_{{entity_prefix:3}}_{{uuid:32}}_{{random:24}}")]
    InvalidIdFormat,

    /// LengthIdError and AlphanumericIdError
    #[error("{0}")]
    LengthIdError(#[from] LengthIdError),

    /// CellIdError because of invalid cell id format
    #[error("{0}")]
    CellIdError(#[from] CellIdError),
}

impl GlobalId {
    /// Create a new global id from entity and cell information
    /// The entity prefix is used to identify the entity, `cus` for customers, `pay`` for payments etc.
    pub fn generate(cell_id: &CellId, entity: GlobalEntity) -> Self {
        let prefix = format!("{}_{}", cell_id.get_string_repr(), entity.prefix());
        let id = generate_time_ordered_id(&prefix);
        let alphanumeric_id = AlphaNumericId::new_unchecked(id);
        Self(LengthId::new_unchecked(alphanumeric_id))
    }

    pub(crate) fn from_string(
        input_string: std::borrow::Cow<'static, str>,
    ) -> Result<Self, GlobalIdError> {
        let length_id = LengthId::from(input_string)?;
        let input_string = &length_id.0 .0;
        let (cell_id, _remaining) = input_string
            .split_once("_")
            .ok_or(GlobalIdError::InvalidIdFormat)?;

        CellId::from_str(cell_id)?;

        Ok(Self(length_id))
    }

    pub(crate) fn get_string_repr(&self) -> &str {
        &self.0 .0 .0
    }
}

impl<DB> ToSql<sql_types::Text, DB> for GlobalId
where
    DB: Backend,
    String: ToSql<sql_types::Text, DB>,
{
    fn to_sql<'b>(
        &'b self,
        out: &mut diesel::serialize::Output<'b, '_, DB>,
    ) -> diesel::serialize::Result {
        self.0 .0 .0.to_sql(out)
    }
}

impl<DB> FromSql<sql_types::Text, DB> for GlobalId
where
    DB: Backend,
    String: FromSql<sql_types::Text, DB>,
{
    fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
        let string_val = String::from_sql(value)?;
        let alphanumeric_id = AlphaNumericId::from(string_val.into())?;
        let length_id = LengthId::from_alphanumeric_id(alphanumeric_id)?;
        Ok(Self(length_id))
    }
}

/// Deserialize the global id from string
/// The format should match {cell_id:5}_{entity_prefix:3}_{time_ordered_id:32}_{.*:24}
impl<'de> serde::Deserialize<'de> for GlobalId {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let deserialized_string = String::deserialize(deserializer)?;
        Self::from_string(deserialized_string.into()).map_err(serde::de::Error::custom)
    }
}

#[cfg(test)]
mod global_id_tests {
    #![allow(clippy::unwrap_used)]
    use super::*;

    #[test]
    fn test_cell_id_from_str() {
        let cell_id_string = "12345";
        let cell_id = CellId::from_str(cell_id_string).unwrap();
        assert_eq!(cell_id.get_string_repr(), cell_id_string);
    }

    #[test]
    fn test_global_id_generate() {
        let cell_id_string = "12345";
        let entity = GlobalEntity::Customer;
        let cell_id = CellId::from_str(cell_id_string).unwrap();
        let global_id = GlobalId::generate(&cell_id, entity);

        // Generate a regex for globalid
        // Eg - 12abc_cus_abcdefghijklmnopqrstuvwxyz1234567890
        let regex = regex::Regex::new(r"[a-z0-9]{5}_cus_[a-z0-9]{32}").unwrap();

        assert!(regex.is_match(&global_id.0 .0 .0));
    }

    #[test]
    fn test_global_id_from_string() {
        let input_string = "12345_cus_abcdefghijklmnopqrstuvwxyz1234567890";
        let global_id = GlobalId::from_string(input_string.into()).unwrap();
        assert_eq!(global_id.0 .0 .0, input_string);
    }

    #[test]
    fn test_global_id_deser() {
        let input_string_for_serde_json_conversion =
            r#""12345_cus_abcdefghijklmnopqrstuvwxyz1234567890""#;

        let input_string = "12345_cus_abcdefghijklmnopqrstuvwxyz1234567890";
        let global_id =
            serde_json::from_str::<GlobalId>(input_string_for_serde_json_conversion).unwrap();
        assert_eq!(global_id.0 .0 .0, input_string);
    }

    #[test]
    fn test_global_id_deser_error() {
        let input_string_for_serde_json_conversion =
            r#""123_45_cus_abcdefghijklmnopqrstuvwxyz1234567890""#;

        let global_id = serde_json::from_str::<GlobalId>(input_string_for_serde_json_conversion);
        assert!(global_id.is_err());

        let expected_error_message = format!(
            "cell id error: the minimum required length for this field is {CELL_IDENTIFIER_LENGTH}"
        );

        let error_message = global_id.unwrap_err().to_string();
        assert_eq!(error_message, expected_error_message);
    }
}


impl ConnectorAccessToken for Riskified {}

impl ConnectorAccessToken for for Riskified


pub struct CaptureResponseData {
    capture_transaction: CaptureTransactionData,
}


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

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

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

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

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let auth = novalnet::NovalnetAuthType::try_from(auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        let api_key: String = auth.payment_access_key.expose();
        let encoded_api_key = common_utils::consts::BASE64_ENGINE.encode(api_key);
        Ok(vec![(
            headers::X_NN_ACCESS_KEY.to_string(),
            encoded_api_key.into_masked(),
        )])
    }

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

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

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


pub async fn accept_dispute(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<String>,
) -> HttpResponse {
    let flow = Flow::DisputesRetrieve;
    let dispute_id = dispute_types::DisputeId {
        dispute_id: path.into_inner(),
    };
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        dispute_id,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::accept_dispute(state, merchant_context, auth.profile_id, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileDisputeWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}