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

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

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

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

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

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

static CONNECTOR: PlacetopayTest = PlacetopayTest {};

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


pub fn third_party_sdk_session_next_action<Op>(
    payment_attempt: &storage::PaymentAttempt,
    operation: &Op,
) -> bool
where
    Op: Debug,
{
    // If the operation is confirm, we will send session token response in next action
    if format!("{operation:?}").eq("PaymentConfirm") {
        let condition1 = payment_attempt
            .connector
            .as_ref()
            .map(|connector| {
                matches!(connector.as_str(), "trustpay") || matches!(connector.as_str(), "payme")
            })
            .and_then(|is_connector_supports_third_party_sdk| {
                if is_connector_supports_third_party_sdk {
                    payment_attempt
                        .payment_method
                        .map(|pm| matches!(pm, diesel_models::enums::PaymentMethod::Wallet))
                } else {
                    Some(false)
                }
            })
            .unwrap_or(false);

        // This condition to be triggered for open banking connectors, third party SDK session token will be provided
        let condition2 = payment_attempt
            .connector
            .as_ref()
            .map(|connector| matches!(connector.as_str(), "plaid"))
            .and_then(|is_connector_supports_third_party_sdk| {
                if is_connector_supports_third_party_sdk {
                    payment_attempt
                        .payment_method
                        .map(|pm| matches!(pm, diesel_models::enums::PaymentMethod::OpenBanking))
                        .and_then(|first_match| {
                            payment_attempt
                                .payment_method_type
                                .map(|pmt| {
                                    matches!(
                                        pmt,
                                        diesel_models::enums::PaymentMethodType::OpenBankingPIS
                                    )
                                })
                                .map(|second_match| first_match && second_match)
                        })
                } else {
                    Some(false)
                }
            })
            .unwrap_or(false);

        condition1 || condition2
    } else {
        false
    }
}


pub async fn calculate_smart_retry_time(
    state: &SessionState,
    payment_intent: &PaymentIntent,
    token_with_retry_info: &PaymentProcessorTokenWithRetryInfo,
) -> Result<Option<time::PrimitiveDateTime>, errors::ProcessTrackerError> {
    let wait_hours = token_with_retry_info.retry_wait_time_hours;
    let current_time = time::OffsetDateTime::now_utc();
    let future_time = current_time + time::Duration::hours(wait_hours);

    // Timestamp after which retry can be done without penalty
    let future_timestamp = Some(prost_types::Timestamp {
        seconds: future_time.unix_timestamp(),
        nanos: 0,
    });

    get_schedule_time_for_smart_retry(
        state,
        payment_intent,
        future_timestamp,
        token_with_retry_info,
    )
    .await
}


pub struct NoonApplePaymentData {
    version: Secret<String>,
    data: Secret<String>,
    signature: Secret<String>,
    header: NoonApplePayHeader,
}


pub fn payments_external_authentication() {}


pub fn paypal_sdk_next_steps_check(
    payment_attempt: storage::PaymentAttempt,
) -> RouterResult<Option<api_models::payments::SdkNextActionData>> {
    let paypal_connector_metadata: Option<Result<api_models::payments::SdkNextActionData, _>> =
        payment_attempt.connector_metadata.map(|metadata| {
            metadata.parse_value("SdkNextActionData").map_err(|_| {
                crate::logger::warn!(
                    "SdkNextActionData parsing failed for paypal_connector_metadata"
                )
            })
        });

    let paypal_next_steps = paypal_connector_metadata.transpose().ok().flatten();
    Ok(paypal_next_steps)
}


impl api::PaymentCapture for Volt {}


OpenAPI Block Path: components.schemas.FutureUsage
{
  "type": "string",
  "description": "Specifies how the payment method can be used for future payments.\n- `off_session`: The payment method can be used for future payments when the customer is not present.\n- `on_session`: The payment method is intended for use only when the customer is present during checkout.\nIf omitted, defaults to `on_session`.",
  "enum": [
    "off_session",
    "on_session"
  ]
}


File: crates/hyperswitch_connectors/src/connectors/tsys.rs
 Public functions: 1
 Public structs: 1

pub mod transformers;

use std::sync::LazyLock;

use common_enums::enums;
use common_utils::{
    errors::CustomResult,
    ext_traits::BytesExt,
    request::{Method, Request, RequestBuilder, RequestContent},
    types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector},
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    router_data::{AccessToken, ErrorResponse, RouterData},
    router_flow_types::{
        access_token_auth::AccessTokenAuth,
        payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void},
        refunds::{Execute, RSync},
    },
    router_request_types::{
        AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
        PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData,
        RefundsData, SetupMandateRequestData,
    },
    router_response_types::{
        ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData,
        SupportedPaymentMethods, SupportedPaymentMethodsExt,
    },
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData,
    },
};
use hyperswitch_interfaces::{
    api::{
        self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications,
        ConnectorValidation,
    },
    configs::Connectors,
    errors,
    events::connector_api_logs::ConnectorEvent,
    types::{
        PaymentsAuthorizeType, PaymentsCaptureType, PaymentsSyncType, PaymentsVoidType,
        RefundExecuteType, RefundSyncType, Response,
    },
    webhooks,
};
use transformers as tsys;

use crate::{constants::headers, types::ResponseRouterData, utils};
#[derive(Clone)]
pub struct Tsys {
    amount_converter: &'static (dyn AmountConvertor<Output = StringMinorUnit> + Sync),
}

impl Tsys {
    pub fn new() -> &'static Self {
        &Self {
            amount_converter: &StringMinorUnitForConnector,
        }
    }
}
impl api::Payment for Tsys {}
impl api::PaymentSession for Tsys {}
impl api::ConnectorAccessToken for Tsys {}
impl api::MandateSetup for Tsys {}
impl api::PaymentAuthorize for Tsys {}
impl api::PaymentSync for Tsys {}
impl api::PaymentCapture for Tsys {}
impl api::PaymentVoid for Tsys {}
impl api::Refund for Tsys {}
impl api::RefundExecute for Tsys {}
impl api::RefundSync for Tsys {}
impl api::PaymentToken for Tsys {}

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

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Tsys
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    fn build_headers(
        &self,
        _req: &RouterData<Flow, Request, Response>,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let header = vec![(
            headers::CONTENT_TYPE.to_string(),
            Self::get_content_type(self).to_string().into(),
        )];
        Ok(header)
    }
}

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

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

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

impl ConnectorValidation for Tsys {}

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

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken> for Tsys {}

impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData> for Tsys {
    fn build_request(
        &self,
        _req: &RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>,
        _connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Err(
            errors::ConnectorError::NotImplemented("Setup Mandate flow for Tsys".to_string())
                .into(),
        )
    }
}

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

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

    fn get_url(
        &self,
        _req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}servlets/transnox_api_server",
            self.base_url(connectors)
        ))
    }

    fn get_request_body(
        &self,
        req: &PaymentsAuthorizeRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = utils::convert_amount(
            self.amount_converter,
            req.request.minor_amount,
            req.request.currency,
        )?;
        let connector_router_data = tsys::TsysRouterData::from((amount, req));
        let connector_req: transformers::TsysPaymentsRequest =
            tsys::TsysPaymentsRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

    fn handle_response(
        &self,
        data: &PaymentsAuthorizeRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsAuthorizeRouterData, errors::ConnectorError> {
        let response: tsys::TsysPaymentsResponse = res
            .response
            .parse_struct("Tsys PaymentsAuthorizeResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

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

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

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

impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Tsys {
    fn get_headers(
        &self,
        req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}servlets/transnox_api_server",
            self.base_url(connectors)
        ))
    }

    fn get_request_body(
        &self,
        req: &PaymentsSyncRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = tsys::TsysSyncRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

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

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

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

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

impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> for Tsys {
    fn get_headers(
        &self,
        req: &PaymentsCaptureRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &PaymentsCaptureRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}servlets/transnox_api_server",
            self.base_url(connectors)
        ))
    }

    fn get_request_body(
        &self,
        req: &PaymentsCaptureRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = utils::convert_amount(
            self.amount_converter,
            req.request.minor_amount_to_capture,
            req.request.currency,
        )?;
        let connector_router_data = tsys::TsysRouterData::from((amount, req));
        let connector_req = tsys::TsysPaymentsCaptureRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

    fn handle_response(
        &self,
        data: &PaymentsCaptureRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsCaptureRouterData, errors::ConnectorError> {
        let response: tsys::TsysPaymentsResponse = res
            .response
            .parse_struct("Tsys PaymentsCaptureResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

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

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

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

impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Tsys {
    fn get_headers(
        &self,
        req: &PaymentsCancelRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &PaymentsCancelRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}servlets/transnox_api_server",
            self.base_url(connectors),
        ))
    }
    fn get_request_body(
        &self,
        req: &PaymentsCancelRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = tsys::TsysPaymentsCancelRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

        Ok(Some(request))
    }

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

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

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }
    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Tsys {
    fn get_headers(
        &self,
        req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}servlets/transnox_api_server",
            self.base_url(connectors)
        ))
    }

    fn get_request_body(
        &self,
        req: &RefundsRouterData<Execute>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = utils::convert_amount(
            self.amount_converter,
            req.request.minor_refund_amount,
            req.request.currency,
        )?;
        let connector_router_data = tsys::TsysRouterData::from((amount, req));

        let connector_req = tsys::TsysRefundRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

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

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

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

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

impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Tsys {
    fn get_headers(
        &self,
        req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}servlets/transnox_api_server",
            self.base_url(connectors),
        ))
    }

    fn get_request_body(
        &self,
        req: &RefundSyncRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = tsys::TsysSyncRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

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

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

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

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

#[async_trait::async_trait]
impl webhooks::IncomingWebhook for Tsys {
    fn get_webhook_object_reference_id(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_event_type(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_resource_object(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }
}

static TSYS_SUPPORTED_PAYMENT_METHODS: LazyLock<SupportedPaymentMethods> = LazyLock::new(|| {
    let supported_capture_methods = vec![
        enums::CaptureMethod::Automatic,
        enums::CaptureMethod::Manual,
        enums::CaptureMethod::SequentialAutomatic,
    ];

    let supported_card_network = vec![
        common_enums::CardNetwork::Mastercard,
        common_enums::CardNetwork::Visa,
        common_enums::CardNetwork::AmericanExpress,
        common_enums::CardNetwork::Discover,
        common_enums::CardNetwork::JCB,
        common_enums::CardNetwork::UnionPay,
    ];

    let mut tsys_supported_payment_methods = SupportedPaymentMethods::new();

    tsys_supported_payment_methods.add(
        enums::PaymentMethod::Card,
        enums::PaymentMethodType::Credit,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: Some(
                api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({
                    api_models::feature_matrix::CardSpecificFeatures {
                        three_ds: common_enums::FeatureStatus::NotSupported,
                        no_three_ds: common_enums::FeatureStatus::Supported,
                        supported_card_networks: supported_card_network.clone(),
                    }
                }),
            ),
        },
    );

    tsys_supported_payment_methods.add(
        enums::PaymentMethod::Card,
        enums::PaymentMethodType::Debit,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: Some(
                api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({
                    api_models::feature_matrix::CardSpecificFeatures {
                        three_ds: common_enums::FeatureStatus::NotSupported,
                        no_three_ds: common_enums::FeatureStatus::Supported,
                        supported_card_networks: supported_card_network.clone(),
                    }
                }),
            ),
        },
    );

    tsys_supported_payment_methods
});

static TSYS_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
    display_name: "Tsys",
    description: "TSYS, a Global Payments company, is the payment stack for the future, powered by unmatched expertise.",
    connector_type: enums::HyperswitchConnectorCategory::BankAcquirer,
    integration_status: enums::ConnectorIntegrationStatus::Beta,
};

static TSYS_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = [];

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

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        Some(&*TSYS_SUPPORTED_PAYMENT_METHODS)
    }

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


impl api::PaymentSync for Cybersource {}

impl api::PaymentSync for for Cybersource


pub struct SepaBankTransfer {
    /// Bank name
    #[schema(value_type = Option<String>, example = "Deutsche Bank")]
    pub bank_name: Option<String>,

    /// Bank country code
    #[schema(value_type = Option<CountryAlpha2>, example = "US")]
    pub bank_country_code: Option<api_enums::CountryAlpha2>,

    /// Bank city
    #[schema(value_type = Option<String>, example = "California")]
    pub bank_city: Option<String>,

    /// International Bank Account Number (iban) - used in many countries for identifying a bank along with it's customer.
    #[schema(value_type = String, example = "DE89370400440532013000")]
    pub iban: Secret<String>,

    /// [8 / 11 digits] Bank Identifier Code (bic) / Swift Code - used in many countries for identifying a bank and it's branches
    #[schema(value_type = String, example = "HSBCGB2LXXX")]
    pub bic: Option<Secret<String>>,
}


impl GetPaymentMethodType for GiftCardData {
    fn get_payment_method_type(&self) -> api_enums::PaymentMethodType {
        match self {
            Self::Givex(_) => api_enums::PaymentMethodType::Givex,
            Self::PaySafeCard {} => api_enums::PaymentMethodType::PaySafeCard,
            Self::BhnCardNetwork(_) => api_enums::PaymentMethodType::BhnCardNetwork,
        }
    }
}

impl GetPaymentMethodType for for GiftCardData


pub fn set_token_details(
        self,
        network_token: &'a NetworkTokenizationResponse,
    ) -> NetworkTokenizationBuilder<'a, CardTokenized> {
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            network_token: Some(&network_token.0),
            customer: self.customer,
            card: self.card,
            card_cvc: self.card_cvc,
            stored_card: self.stored_card,
            stored_token: self.stored_token,
            payment_method_response: self.payment_method_response,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }


Documentation: api-reference/essentials/rate_limit.mdx
# Type: Doc File

---
title: Rate Limits
---

The Hyperswitch API has multiple checks in place to enhance its stability when faced with sudden surges of incoming traffic. Merchants who send numerous requests in rapid succession could encounter error responses indicated by the status code 429.

<Warning>
  The default rate limit for all Hyperswitch APIs is **80 requests per second**.
  Reach out to biz@hyperswitch.io if you have a requirement for higher limits.
</Warning>

## How to handle rate limits

Effectively handling rate limit 429 errors is crucial for maintaining a seamless user experience.

- Implement retry mechanisms with progressively increasing intervals to avoid overwhelming the system with repeated requests.
- To proactively manage these errors, monitoring tools can help track usage patterns and provide insights for adjusting rate limits as necessary.
- Ultimately, a well-orchestrated strategy for managing 429 errors not only prevents disruption but also fosters positive user engagement by transparently addressing limitations and promoting responsible API usage.

## Understanding API locking

If you see a 429 error with the following error message, it is due to API locking and not due to rate limiting:

```text
At this moment, access to this object is restricted due to ongoing utilization by another API request or an ongoing Hyperswitch process. Retry after some time if this error persists.
```

API locking is a robust feature that empowers us to proactively secure and optimize access to our APIs. Our API locks objects on some operations to prevent the disruption caused by concurrent workloads that could potentially lead to inconsistent outcomes.

This proactive measure not only ensures the continued integrity and performance of our APIs but also guarantees a high standard of security for our users. Once triggered, the API lock initiates a controlled pause in access, preventing any potential threats from compromising the system.


pub struct TrustpayErrorResponse {
    pub status: i64,
    pub description: Option<String>,
    pub errors: Option<Vec<Errors>>,
    pub instance_id: Option<String>,
    pub payment_description: Option<String>,
}


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


pub fn is_chargeback_event(event_code: &CheckoutWebhookEventType) -> bool {
    matches!(
        event_code,
        CheckoutWebhookEventType::DisputeReceived
            | CheckoutWebhookEventType::DisputeExpired
            | CheckoutWebhookEventType::DisputeAccepted
            | CheckoutWebhookEventType::DisputeCanceled
            | CheckoutWebhookEventType::DisputeEvidenceSubmitted
            | CheckoutWebhookEventType::DisputeEvidenceAcknowledgedByScheme
            | CheckoutWebhookEventType::DisputeEvidenceRequired
            | CheckoutWebhookEventType::DisputeArbitrationLost
            | CheckoutWebhookEventType::DisputeArbitrationWon
            | CheckoutWebhookEventType::DisputeWon
            | CheckoutWebhookEventType::DisputeLost
    )
}


pub struct StripeConnectRecipientCreateRequest {
    #[serde(rename = "type")]
    account_type: String,
    country: Option<enums::CountryAlpha2>,
    email: Option<Email>,
    #[serde(rename = "capabilities[card_payments][requested]")]
    capabilities_card_payments: Option<bool>,
    #[serde(rename = "capabilities[transfers][requested]")]
    capabilities_transfers: Option<bool>,
    #[serde(rename = "tos_acceptance[date]")]
    tos_acceptance_date: Option<i64>,
    #[serde(rename = "tos_acceptance[ip]")]
    tos_acceptance_ip: Option<Secret<String>>,
    business_type: String,
    #[serde(rename = "business_profile[mcc]")]
    business_profile_mcc: Option<i32>,
    #[serde(rename = "business_profile[url]")]
    business_profile_url: Option<String>,
    #[serde(rename = "business_profile[name]")]
    business_profile_name: Option<Secret<String>>,
    #[serde(rename = "company[name]")]
    company_name: Option<Secret<String>>,
    #[serde(rename = "company[address][line1]")]
    company_address_line1: Option<Secret<String>>,
    #[serde(rename = "company[address][line2]")]
    company_address_line2: Option<Secret<String>>,
    #[serde(rename = "company[address][postal_code]")]
    company_address_postal_code: Option<Secret<String>>,
    #[serde(rename = "company[address][city]")]
    company_address_city: Option<Secret<String>>,
    #[serde(rename = "company[address][state]")]
    company_address_state: Option<Secret<String>>,
    #[serde(rename = "company[phone]")]
    company_phone: Option<Secret<String>>,
    #[serde(rename = "company[tax_id]")]
    company_tax_id: Option<Secret<String>>,
    #[serde(rename = "company[owners_provided]")]
    company_owners_provided: Option<bool>,
    #[serde(rename = "individual[first_name]")]
    individual_first_name: Option<Secret<String>>,
    #[serde(rename = "individual[last_name]")]
    individual_last_name: Option<Secret<String>>,
    #[serde(rename = "individual[dob][day]")]
    individual_dob_day: Option<Secret<String>>,
    #[serde(rename = "individual[dob][month]")]
    individual_dob_month: Option<Secret<String>>,
    #[serde(rename = "individual[dob][year]")]
    individual_dob_year: Option<Secret<String>>,
    #[serde(rename = "individual[address][line1]")]
    individual_address_line1: Option<Secret<String>>,
    #[serde(rename = "individual[address][line2]")]
    individual_address_line2: Option<Secret<String>>,
    #[serde(rename = "individual[address][postal_code]")]
    individual_address_postal_code: Option<Secret<String>>,
    #[serde(rename = "individual[address][city]")]
    individual_address_city: Option<String>,
    #[serde(rename = "individual[address][state]")]
    individual_address_state: Option<Secret<String>>,
    #[serde(rename = "individual[email]")]
    individual_email: Option<Email>,
    #[serde(rename = "individual[phone]")]
    individual_phone: Option<Secret<String>>,
    #[serde(rename = "individual[id_number]")]
    individual_id_number: Option<Secret<String>>,
    #[serde(rename = "individual[ssn_last_4]")]
    individual_ssn_last_4: Option<Secret<String>>,
}


pub async fn decide_connector_for_normal_or_recurring_payment<F: Clone, D>(
    state: &SessionState,
    payment_data: &mut D,
    routing_data: &mut storage::RoutingData,
    connectors: Vec<api::ConnectorRoutingData>,
    is_connector_agnostic_mit_enabled: Option<bool>,
    payment_method_info: &domain::PaymentMethod,
) -> RouterResult<ConnectorCallType>
where
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
{
    let connector_common_mandate_details = payment_method_info
        .get_common_mandate_reference()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get the common mandate reference")?;

    let connector_mandate_details = connector_common_mandate_details.payments.clone();

    let mut connector_choice = None;

    for connector_info in connectors {
        let connector_data = connector_info.connector_data;
        let merchant_connector_id = connector_data
            .merchant_connector_id
            .as_ref()
            .ok_or(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to find the merchant connector id")?;
        if connector_mandate_details
            .clone()
            .map(|connector_mandate_details| {
                connector_mandate_details.contains_key(merchant_connector_id)
            })
            .unwrap_or(false)
        {
            logger::info!("euclid_routing: using connector_mandate_id for MIT flow");
            if let Some(merchant_connector_id) = connector_data.merchant_connector_id.as_ref() {
                if let Some(mandate_reference_record) = connector_mandate_details.clone()
                        .get_required_value("connector_mandate_details")
                            .change_context(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration)
                            .attach_printable("no eligible connector found for token-based MIT flow since there were no connector mandate details")?
                            .get(merchant_connector_id)
                        {
                            common_utils::fp_utils::when(
                                mandate_reference_record
                                    .original_payment_authorized_currency
                                    .map(|mandate_currency| mandate_currency != payment_data.get_currency())
                                    .unwrap_or(false),
                                || {
                                    Err(report!(errors::ApiErrorResponse::MandateValidationFailed {
                                        reason: "cross currency mandates not supported".into()
                                    }))
                                },
                            )?;
                            let mandate_reference_id = Some(payments_api::MandateReferenceId::ConnectorMandateId(
                                api_models::payments::ConnectorMandateReferenceId::new(
                                    Some(mandate_reference_record.connector_mandate_id.clone()),
                                    Some(payment_method_info.get_id().clone()),
                                    // update_history
                                    None,
                                    mandate_reference_record.mandate_metadata.clone(),
                                    mandate_reference_record.connector_mandate_request_reference_id.clone(),
                                )
                            ));
                            payment_data.set_recurring_mandate_payment_data(
                                hyperswitch_domain_models::router_data::RecurringMandatePaymentData {
                                    payment_method_type: mandate_reference_record
                                        .payment_method_type,
                                    original_payment_authorized_amount: mandate_reference_record
                                        .original_payment_authorized_amount,
                                    original_payment_authorized_currency: mandate_reference_record
                                        .original_payment_authorized_currency,
                                    mandate_metadata: mandate_reference_record
                                        .mandate_metadata.clone()
                                });
                            connector_choice = Some((connector_data, mandate_reference_id.clone()));
                            break;
                        }
            }
        } else if is_network_transaction_id_flow(
            state,
            is_connector_agnostic_mit_enabled,
            connector_data.connector_name,
            payment_method_info,
        ) {
            logger::info!("using network_transaction_id for MIT flow");
            let network_transaction_id = payment_method_info
                .network_transaction_id
                .as_ref()
                .ok_or(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to fetch the network transaction id")?;

            let mandate_reference_id = Some(payments_api::MandateReferenceId::NetworkMandateId(
                network_transaction_id.to_string(),
            ));

            connector_choice = Some((connector_data, mandate_reference_id.clone()));
            break;
        } else {
            continue;
        }
    }

    let (chosen_connector_data, mandate_reference_id) = connector_choice
        .get_required_value("connector_choice")
        .change_context(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration)
        .attach_printable("no eligible connector found for token-based MIT payment")?;

    routing_data.routed_through = Some(chosen_connector_data.connector_name.to_string());

    routing_data
        .merchant_connector_id
        .clone_from(&chosen_connector_data.merchant_connector_id);

    payment_data.set_mandate_id(payments_api::MandateIds {
        mandate_id: None,
        mandate_reference_id,
    });

    Ok(ConnectorCallType::PreDetermined(
        chosen_connector_data.into(),
    ))
}


/// Implements the `Validate` trait to check if the config variable is present
/// Usage
/// ```
/// use router_derive::ConfigValidate;
///
/// #[derive(ConfigValidate)]
/// struct ConnectorParams {
///     base_url: String,
/// }
///
/// enum ApplicationError {
///     InvalidConfigurationValueError(String),
/// }
///
/// #[derive(ConfigValidate)]
/// struct Connectors {
///     pub stripe: ConnectorParams,
///     pub checkout: ConnectorParams
/// }
/// ```
///
/// This will call the `validate()` function for all the fields in the struct
///
/// ```rust, ignore
/// impl Connectors {
///      fn validate(&self) -> Result<(), ApplicationError> {
///         self.stripe.validate()?;
///         self.checkout.validate()?;
///      }
/// }
/// ```
#[proc_macro_derive(ConfigValidate)]
pub fn validate_config(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = syn::parse_macro_input!(input as syn::DeriveInput);

    macros::misc::validate_config(input)
        .unwrap_or_else(|error| error.into_compile_error())
        .into()
}


pub struct ApplepayPaymentData {
    payment_data: ApplePayEncodedPaymentData,
    payment_method: ApplepayPaymentMethod,
    transaction_identifier: String,
}


pub async fn create_token_vault_api() {}


pub struct StripeCreateRefundRequest {
    pub refund_id: Option<String>,
    pub amount: Option<i64>,
    pub payment_intent: common_utils::id_type::PaymentId,
    pub reason: Option<String>,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}


impl ApiEventMetric for CardInfoMigrationResponse {}

impl ApiEventMetric for for CardInfoMigrationResponse


impl PaymentSource {
    pub fn is_for_internal_use_only(self) -> bool {
        match self {
            Self::Dashboard | Self::Sdk | Self::MerchantServer | Self::Postman => false,
            Self::Webhook | Self::ExternalAuthenticator => true,
        }
    }
}


pub struct ConnectorConfig {
    pub authipay: Option<ConnectorTomlConfig>,
    pub juspaythreedsserver: Option<ConnectorTomlConfig>,
    pub katapult: Option<ConnectorTomlConfig>,
    pub aci: Option<ConnectorTomlConfig>,
    pub adyen: Option<ConnectorTomlConfig>,
    pub affirm: Option<ConnectorTomlConfig>,
    #[cfg(feature = "payouts")]
    pub adyen_payout: Option<ConnectorTomlConfig>,
    #[cfg(feature = "payouts")]
    pub adyenplatform_payout: Option<ConnectorTomlConfig>,
    pub airwallex: Option<ConnectorTomlConfig>,
    pub amazonpay: Option<ConnectorTomlConfig>,
    pub archipel: Option<ConnectorTomlConfig>,
    pub authorizedotnet: Option<ConnectorTomlConfig>,
    pub bamboraapac: Option<ConnectorTomlConfig>,
    pub bankofamerica: Option<ConnectorTomlConfig>,
    pub barclaycard: Option<ConnectorTomlConfig>,
    pub billwerk: Option<ConnectorTomlConfig>,
    pub bitpay: Option<ConnectorTomlConfig>,
    pub blackhawknetwork: Option<ConnectorTomlConfig>,
    pub bluecode: Option<ConnectorTomlConfig>,
    pub bluesnap: Option<ConnectorTomlConfig>,
    pub boku: Option<ConnectorTomlConfig>,
    pub braintree: Option<ConnectorTomlConfig>,
    pub breadpay: Option<ConnectorTomlConfig>,
    pub cashtocode: Option<ConnectorTomlConfig>,
    pub celero: Option<ConnectorTomlConfig>,
    pub chargebee: Option<ConnectorTomlConfig>,
    pub custombilling: Option<ConnectorTomlConfig>,
    pub checkbook: Option<ConnectorTomlConfig>,
    pub checkout: Option<ConnectorTomlConfig>,
    pub coinbase: Option<ConnectorTomlConfig>,
    pub coingate: Option<ConnectorTomlConfig>,
    pub cryptopay: Option<ConnectorTomlConfig>,
    pub ctp_visa: Option<ConnectorTomlConfig>,
    pub cybersource: Option<ConnectorTomlConfig>,
    #[cfg(feature = "payouts")]
    pub cybersource_payout: Option<ConnectorTomlConfig>,
    pub iatapay: Option<ConnectorTomlConfig>,
    pub itaubank: Option<ConnectorTomlConfig>,
    pub opennode: Option<ConnectorTomlConfig>,
    pub bambora: Option<ConnectorTomlConfig>,
    pub datatrans: Option<ConnectorTomlConfig>,
    pub deutschebank: Option<ConnectorTomlConfig>,
    pub digitalvirgo: Option<ConnectorTomlConfig>,
    pub dlocal: Option<ConnectorTomlConfig>,
    pub dwolla: Option<ConnectorTomlConfig>,
    pub ebanx_payout: Option<ConnectorTomlConfig>,
    pub elavon: Option<ConnectorTomlConfig>,
    pub facilitapay: Option<ConnectorTomlConfig>,
    pub fiserv: Option<ConnectorTomlConfig>,
    pub fiservemea: Option<ConnectorTomlConfig>,
    pub fiuu: Option<ConnectorTomlConfig>,
    pub flexiti: Option<ConnectorTomlConfig>,
    pub forte: Option<ConnectorTomlConfig>,
    pub getnet: Option<ConnectorTomlConfig>,
    pub globalpay: Option<ConnectorTomlConfig>,
    pub globepay: Option<ConnectorTomlConfig>,
    pub gocardless: Option<ConnectorTomlConfig>,
    pub gpayments: Option<ConnectorTomlConfig>,
    pub hipay: Option<ConnectorTomlConfig>,
    pub helcim: Option<ConnectorTomlConfig>,
    pub hyperswitch_vault: Option<ConnectorTomlConfig>,
    pub hyperwallet: Option<ConnectorTomlConfig>,
    pub inespay: Option<ConnectorTomlConfig>,
    pub jpmorgan: Option<ConnectorTomlConfig>,
    pub klarna: Option<ConnectorTomlConfig>,
    pub mifinity: Option<ConnectorTomlConfig>,
    pub mollie: Option<ConnectorTomlConfig>,
    pub moneris: Option<ConnectorTomlConfig>,
    pub mpgs: Option<ConnectorTomlConfig>,
    pub multisafepay: Option<ConnectorTomlConfig>,
    pub nexinets: Option<ConnectorTomlConfig>,
    pub nexixpay: Option<ConnectorTomlConfig>,
    pub nmi: Option<ConnectorTomlConfig>,
    pub nomupay_payout: Option<ConnectorTomlConfig>,
    pub noon: Option<ConnectorTomlConfig>,
    pub nordea: Option<ConnectorTomlConfig>,
    pub novalnet: Option<ConnectorTomlConfig>,
    pub nuvei: Option<ConnectorTomlConfig>,
    pub paybox: Option<ConnectorTomlConfig>,
    pub payload: Option<ConnectorTomlConfig>,
    pub payme: Option<ConnectorTomlConfig>,
    #[cfg(feature = "payouts")]
    pub payone_payout: Option<ConnectorTomlConfig>,
    pub paypal: Option<ConnectorTomlConfig>,
    pub paysafe: Option<ConnectorTomlConfig>,
    #[cfg(feature = "payouts")]
    pub paypal_payout: Option<ConnectorTomlConfig>,
    pub paystack: Option<ConnectorTomlConfig>,
    pub paytm: Option<ConnectorTomlConfig>,
    pub payu: Option<ConnectorTomlConfig>,
    pub phonepe: Option<ConnectorTomlConfig>,
    pub placetopay: Option<ConnectorTomlConfig>,
    pub plaid: Option<ConnectorTomlConfig>,
    pub powertranz: Option<ConnectorTomlConfig>,
    pub prophetpay: Option<ConnectorTomlConfig>,
    pub razorpay: Option<ConnectorTomlConfig>,
    pub recurly: Option<ConnectorTomlConfig>,
    pub riskified: Option<ConnectorTomlConfig>,
    pub rapyd: Option<ConnectorTomlConfig>,
    pub redsys: Option<ConnectorTomlConfig>,
    pub santander: Option<ConnectorTomlConfig>,
    pub shift4: Option<ConnectorTomlConfig>,
    pub sift: Option<ConnectorTomlConfig>,
    pub silverflow: Option<ConnectorTomlConfig>,
    pub stripe: Option<ConnectorTomlConfig>,
    #[cfg(feature = "payouts")]
    pub stripe_payout: Option<ConnectorTomlConfig>,
    pub stripebilling: Option<ConnectorTomlConfig>,
    pub signifyd: Option<ConnectorTomlConfig>,
    pub tokenio: Option<ConnectorTomlConfig>,
    pub trustpay: Option<ConnectorTomlConfig>,
    pub trustpayments: Option<ConnectorTomlConfig>,
    pub threedsecureio: Option<ConnectorTomlConfig>,
    pub netcetera: Option<ConnectorTomlConfig>,
    pub tsys: Option<ConnectorTomlConfig>,
    pub vgs: Option<ConnectorTomlConfig>,
    pub volt: Option<ConnectorTomlConfig>,
    pub wellsfargo: Option<ConnectorTomlConfig>,
    #[cfg(feature = "payouts")]
    pub wise_payout: Option<ConnectorTomlConfig>,
    pub worldline: Option<ConnectorTomlConfig>,
    pub worldpay: Option<ConnectorTomlConfig>,
    pub worldpayvantiv: Option<ConnectorTomlConfig>,
    pub worldpayxml: Option<ConnectorTomlConfig>,
    pub xendit: Option<ConnectorTomlConfig>,
    pub square: Option<ConnectorTomlConfig>,
    pub stax: Option<ConnectorTomlConfig>,
    pub dummy_connector: Option<ConnectorTomlConfig>,
    pub stripe_test: Option<ConnectorTomlConfig>,
    pub paypal_test: Option<ConnectorTomlConfig>,
    pub zen: Option<ConnectorTomlConfig>,
    pub zsl: Option<ConnectorTomlConfig>,
    pub taxjar: Option<ConnectorTomlConfig>,
    pub ctp_mastercard: Option<ConnectorTomlConfig>,
    pub unified_authentication_service: Option<ConnectorTomlConfig>,
}


pub fn struct_to_xml<T: Serialize>(
    item: &T,
) -> Result<HashMap<String, Secret<String, WithoutType>>, errors::ConnectorError> {
    let xml_content = quick_xml::se::to_string_with_root("txn", &item).map_err(|e| {
        router_env::logger::error!("Error serializing Struct: {:?}", e);
        errors::ConnectorError::ResponseDeserializationFailed
    })?;

    let mut result = HashMap::new();
    result.insert(
        "xmldata".to_string(),
        Secret::<_, WithoutType>::new(xml_content),
    );
    Ok(result)
}


pub struct UserFromTokenWithRoleInfo {
    pub user: UserFromToken,
    pub role_info: authorization::roles::RoleInfo,
}


pub struct CustomerNew {
    pub customer_id: common_utils::id_type::CustomerId,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub name: Option<Encryption>,
    pub email: Option<Encryption>,
    pub phone: Option<Encryption>,
    pub description: Option<Description>,
    pub phone_country_code: Option<String>,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub connector_customer: Option<pii::SecretSerdeValue>,
    pub created_at: PrimitiveDateTime,
    pub modified_at: PrimitiveDateTime,
    pub address_id: Option<String>,
    pub updated_by: Option<String>,
    pub version: ApiVersion,
    pub tax_registration_id: Option<Encryption>,
}


pub struct CardStored;


File: crates/diesel_models/src/file.rs
 Public structs: 3

use common_utils::custom_serde;
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use masking::{Deserialize, Serialize};

use crate::schema::file_metadata;

#[derive(Clone, Debug, Deserialize, Insertable, Serialize, router_derive::DebugAsDisplay)]
#[diesel(table_name = file_metadata)]
#[serde(deny_unknown_fields)]
pub struct FileMetadataNew {
    pub file_id: String,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub file_name: Option<String>,
    pub file_size: i32,
    pub file_type: String,
    pub provider_file_id: Option<String>,
    pub file_upload_provider: Option<common_enums::FileUploadProvider>,
    pub available: bool,
    pub connector_label: Option<String>,
    pub profile_id: Option<common_utils::id_type::ProfileId>,
    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
}

#[derive(Clone, Debug, Deserialize, Serialize, Identifiable, Queryable, Selectable)]
#[diesel(table_name = file_metadata, primary_key(file_id, merchant_id), check_for_backend(diesel::pg::Pg))]
pub struct FileMetadata {
    #[serde(skip_serializing)]
    pub file_id: String,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub file_name: Option<String>,
    pub file_size: i32,
    pub file_type: String,
    pub provider_file_id: Option<String>,
    pub file_upload_provider: Option<common_enums::FileUploadProvider>,
    pub available: bool,
    #[serde(with = "custom_serde::iso8601")]
    pub created_at: time::PrimitiveDateTime,
    pub connector_label: Option<String>,
    pub profile_id: Option<common_utils::id_type::ProfileId>,
    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
}

#[derive(Debug)]
pub enum FileMetadataUpdate {
    Update {
        provider_file_id: Option<String>,
        file_upload_provider: Option<common_enums::FileUploadProvider>,
        available: bool,
        profile_id: Option<common_utils::id_type::ProfileId>,
        merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
    },
}

#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
#[diesel(table_name = file_metadata)]
pub struct FileMetadataUpdateInternal {
    provider_file_id: Option<String>,
    file_upload_provider: Option<common_enums::FileUploadProvider>,
    available: bool,
    profile_id: Option<common_utils::id_type::ProfileId>,
    merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
}

impl From<FileMetadataUpdate> for FileMetadataUpdateInternal {
    fn from(merchant_account_update: FileMetadataUpdate) -> Self {
        match merchant_account_update {
            FileMetadataUpdate::Update {
                provider_file_id,
                file_upload_provider,
                available,
                profile_id,
                merchant_connector_id,
            } => Self {
                provider_file_id,
                file_upload_provider,
                available,
                profile_id,
                merchant_connector_id,
            },
        }
    }
}


pub struct PaymentIntentUpdateFields {
    pub amount: Option<MinorUnit>,
    pub currency: Option<common_enums::Currency>,
    pub shipping_cost: Option<MinorUnit>,
    pub tax_details: Option<diesel_models::TaxDetails>,
    pub skip_external_tax_calculation: Option<common_enums::TaxCalculationOverride>,
    pub skip_surcharge_calculation: Option<common_enums::SurchargeCalculationOverride>,
    pub surcharge_amount: Option<MinorUnit>,
    pub tax_on_surcharge: Option<MinorUnit>,
    pub routing_algorithm_id: Option<id_type::RoutingId>,
    pub capture_method: Option<common_enums::CaptureMethod>,
    pub authentication_type: Option<common_enums::AuthenticationType>,
    pub billing_address: Option<Encryptable<Address>>,
    pub shipping_address: Option<Encryptable<Address>>,
    pub customer_present: Option<common_enums::PresenceOfCustomerDuringPayment>,
    pub description: Option<common_utils::types::Description>,
    pub return_url: Option<common_utils::types::Url>,
    pub setup_future_usage: Option<common_enums::FutureUsage>,
    pub apply_mit_exemption: Option<common_enums::MitExemptionRequest>,
    pub statement_descriptor: Option<common_utils::types::StatementDescriptor>,
    pub order_details: Option<Vec<Secret<diesel_models::types::OrderDetailsWithAmount>>>,
    pub allowed_payment_method_types: Option<Vec<common_enums::PaymentMethodType>>,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub connector_metadata: Option<pii::SecretSerdeValue>,
    pub feature_metadata: Option<diesel_models::types::FeatureMetadata>,
    pub payment_link_config: Option<diesel_models::PaymentLinkConfigRequestForPayments>,
    pub request_incremental_authorization: Option<common_enums::RequestIncrementalAuthorization>,
    pub session_expiry: Option<PrimitiveDateTime>,
    pub frm_metadata: Option<pii::SecretSerdeValue>,
    pub request_external_three_ds_authentication:
        Option<common_enums::External3dsAuthenticationRequest>,
    pub active_attempt_id: Option<Option<id_type::GlobalAttemptId>>,
    // updated_by is set internally, field not present in request
    pub updated_by: String,
    pub force_3ds_challenge: Option<bool>,
    pub is_iframe_redirection_enabled: Option<bool>,
}


impl UploadFile for Adyen {}


OpenAPI Block Path: components.schemas.BrowserInformation
{
  "type": "object",
  "description": "Browser information to be used for 3DS 2.0",
  "properties": {
    "color_depth": {
      "type": "integer",
      "format": "int32",
      "description": "Color depth supported by the browser",
      "nullable": true,
      "minimum": 0
    },
    "java_enabled": {
      "type": "boolean",
      "description": "Whether java is enabled in the browser",
      "nullable": true
    },
    "java_script_enabled": {
      "type": "boolean",
      "description": "Whether javascript is enabled in the browser",
      "nullable": true
    },
    "language": {
      "type": "string",
      "description": "Language supported",
      "nullable": true
    },
    "screen_height": {
      "type": "integer",
      "format": "int32",
      "description": "The screen height in pixels",
      "nullable": true,
      "minimum": 0
    },
    "screen_width": {
      "type": "integer",
      "format": "int32",
      "description": "The screen width in pixels",
      "nullable": true,
      "minimum": 0
    },
    "time_zone": {
      "type": "integer",
      "format": "int32",
      "description": "Time zone of the client",
      "nullable": true
    },
    "ip_address": {
      "type": "string",
      "description": "Ip address of the client",
      "nullable": true
    },
    "accept_header": {
      "type": "string",
      "description": "List of headers that are accepted",
      "example": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
      "nullable": true
    },
    "user_agent": {
      "type": "string",
      "description": "User-agent of the browser",
      "nullable": true
    },
    "os_type": {
      "type": "string",
      "description": "The os type of the client device",
      "nullable": true
    },
    "os_version": {
      "type": "string",
      "description": "The os version of the client device",
      "nullable": true
    },
    "device_model": {
      "type": "string",
      "description": "The device model of the client",
      "nullable": true
    }
  }
}


pub fn payments_update_intent() {}


pub struct ThreeDSecureResData {
    pub redirect_url: Option<Url>,
}


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

---
openapi: post /payments/{payment_id}/cancel
---


impl api::PaymentSync for Ebanx {}


File: crates/router/src/core/payouts/retry.rs
 Public functions: 8

use std::vec::IntoIter;

use common_enums::PayoutRetryType;
use error_stack::ResultExt;
use router_env::{
    logger,
    tracing::{self, instrument},
};

use super::{call_connector_payout, PayoutData};
use crate::{
    core::{
        errors::{self, RouterResult, StorageErrorExt},
        payouts,
    },
    db::StorageInterface,
    routes::{self, app, metrics},
    types::{api, domain, storage},
    utils,
};

#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn do_gsm_multiple_connector_actions(
    state: &app::SessionState,
    mut connectors_routing_data: IntoIter<api::ConnectorRoutingData>,
    original_connector_data: api::ConnectorData,
    payout_data: &mut PayoutData,
    merchant_context: &domain::MerchantContext,
) -> RouterResult<()> {
    let mut retries = None;

    metrics::AUTO_PAYOUT_RETRY_ELIGIBLE_REQUEST_COUNT.add(1, &[]);

    let mut connector = original_connector_data;

    loop {
        let gsm = get_gsm(state, &connector, payout_data).await?;

        match get_gsm_decision(gsm) {
            common_enums::GsmDecision::Retry => {
                retries = get_retries(
                    state,
                    retries,
                    merchant_context.get_merchant_account().get_id(),
                    PayoutRetryType::MultiConnector,
                )
                .await;

                if retries.is_none() || retries == Some(0) {
                    metrics::AUTO_PAYOUT_RETRY_EXHAUSTED_COUNT.add(1, &[]);
                    logger::info!("retries exhausted for auto_retry payout");
                    break;
                }

                if connectors_routing_data.len() == 0 {
                    logger::info!("connectors exhausted for auto_retry payout");
                    metrics::AUTO_PAYOUT_RETRY_EXHAUSTED_COUNT.add(1, &[]);
                    break;
                }

                connector = super::get_next_connector(&mut connectors_routing_data)?.connector_data;

                Box::pin(do_retry(
                    &state.clone(),
                    connector.to_owned(),
                    merchant_context,
                    payout_data,
                ))
                .await?;

                retries = retries.map(|i| i - 1);
            }
            common_enums::GsmDecision::DoDefault => break,
        }
    }
    Ok(())
}

#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn do_gsm_single_connector_actions(
    state: &app::SessionState,
    original_connector_data: api::ConnectorData,
    payout_data: &mut PayoutData,
    merchant_context: &domain::MerchantContext,
) -> RouterResult<()> {
    let mut retries = None;

    metrics::AUTO_PAYOUT_RETRY_ELIGIBLE_REQUEST_COUNT.add(1, &[]);

    let mut previous_gsm = None; // to compare previous status

    loop {
        let gsm = get_gsm(state, &original_connector_data, payout_data).await?;

        // if the error config is same as previous, we break out of the loop
        if gsm == previous_gsm {
            break;
        }
        previous_gsm.clone_from(&gsm);

        match get_gsm_decision(gsm) {
            common_enums::GsmDecision::Retry => {
                retries = get_retries(
                    state,
                    retries,
                    merchant_context.get_merchant_account().get_id(),
                    PayoutRetryType::SingleConnector,
                )
                .await;

                if retries.is_none() || retries == Some(0) {
                    metrics::AUTO_PAYOUT_RETRY_EXHAUSTED_COUNT.add(1, &[]);
                    logger::info!("retries exhausted for auto_retry payment");
                    break;
                }

                Box::pin(do_retry(
                    &state.clone(),
                    original_connector_data.to_owned(),
                    merchant_context,
                    payout_data,
                ))
                .await?;

                retries = retries.map(|i| i - 1);
            }
            common_enums::GsmDecision::DoDefault => break,
        }
    }
    Ok(())
}

#[instrument(skip_all)]
pub async fn get_retries(
    state: &app::SessionState,
    retries: Option<i32>,
    merchant_id: &common_utils::id_type::MerchantId,
    retry_type: PayoutRetryType,
) -> Option<i32> {
    match retries {
        Some(retries) => Some(retries),
        None => {
            let key = merchant_id.get_max_auto_single_connector_payout_retries_enabled(retry_type);
            let db = &*state.store;
            db.find_config_by_key(key.as_str())
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .and_then(|retries_config| {
                    retries_config
                        .config
                        .parse::<i32>()
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Retries config parsing failed")
                })
                .map_err(|err| {
                    logger::error!(retries_error=?err);
                    None::<i32>
                })
                .ok()
        }
    }
}

#[instrument(skip_all)]
pub async fn get_gsm(
    state: &app::SessionState,
    original_connector_data: &api::ConnectorData,
    payout_data: &PayoutData,
) -> RouterResult<Option<hyperswitch_domain_models::gsm::GatewayStatusMap>> {
    let error_code = payout_data.payout_attempt.error_code.to_owned();
    let error_message = payout_data.payout_attempt.error_message.to_owned();
    let connector_name = Some(original_connector_data.connector_name.to_string());

    Ok(payouts::helpers::get_gsm_record(
        state,
        error_code,
        error_message,
        connector_name,
        common_utils::consts::PAYOUT_FLOW_STR,
    )
    .await)
}

#[instrument(skip_all)]
pub fn get_gsm_decision(
    option_gsm: Option<hyperswitch_domain_models::gsm::GatewayStatusMap>,
) -> common_enums::GsmDecision {
    let option_gsm_decision = option_gsm.map(|gsm| gsm.feature_data.get_decision());

    if option_gsm_decision.is_some() {
        metrics::AUTO_PAYOUT_RETRY_GSM_MATCH_COUNT.add(1, &[]);
    }
    option_gsm_decision.unwrap_or_default()
}

#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub async fn do_retry(
    state: &routes::SessionState,
    connector: api::ConnectorData,
    merchant_context: &domain::MerchantContext,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    metrics::AUTO_RETRY_PAYOUT_COUNT.add(1, &[]);

    modify_trackers(state, &connector, merchant_context, payout_data).await?;

    Box::pin(call_connector_payout(
        state,
        merchant_context,
        &connector,
        payout_data,
    ))
    .await
}

#[instrument(skip_all)]
pub async fn modify_trackers(
    state: &routes::SessionState,
    connector: &api::ConnectorData,
    merchant_context: &domain::MerchantContext,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    let new_attempt_count = payout_data.payouts.attempt_count + 1;

    let db = &*state.store;

    // update payout table's attempt count
    let payouts = payout_data.payouts.to_owned();
    let updated_payouts = storage::PayoutsUpdate::AttemptCountUpdate {
        attempt_count: new_attempt_count,
    };

    let payout_id = payouts.payout_id.clone();
    payout_data.payouts = db
        .update_payout(
            &payout_data.payouts,
            updated_payouts,
            &payout_data.payout_attempt,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error updating payouts")?;

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

    let payout_attempt_req = storage::PayoutAttemptNew {
        payout_attempt_id: payout_attempt_id.to_string(),
        payout_id: payout_id.to_owned(),
        merchant_order_reference_id: payout_data
            .payout_attempt
            .merchant_order_reference_id
            .clone(),
        customer_id: payout_data.payout_attempt.customer_id.to_owned(),
        connector: Some(connector.connector_name.to_string()),
        merchant_id: payout_data.payout_attempt.merchant_id.to_owned(),
        address_id: payout_data.payout_attempt.address_id.to_owned(),
        business_country: payout_data.payout_attempt.business_country.to_owned(),
        business_label: payout_data.payout_attempt.business_label.to_owned(),
        payout_token: payout_data.payout_attempt.payout_token.to_owned(),
        profile_id: payout_data.payout_attempt.profile_id.to_owned(),
        connector_payout_id: None,
        status: common_enums::PayoutStatus::default(),
        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,
        additional_payout_method_data: payout_data
            .payout_attempt
            .additional_payout_method_data
            .to_owned(),
    };
    payout_data.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 })
        .attach_printable("Error inserting payouts in db")?;

    payout_data.merchant_connector_account = None;

    Ok(())
}

pub async fn config_should_call_gsm_payout(
    db: &dyn StorageInterface,
    merchant_id: &common_utils::id_type::MerchantId,
    retry_type: PayoutRetryType,
) -> bool {
    let key = merchant_id.get_should_call_gsm_payout_key(retry_type);
    let config = db
        .find_config_by_key_unwrap_or(key.as_str(), Some("false".to_string()))
        .await;
    match config {
        Ok(conf) => conf.config == "true",
        Err(error) => {
            logger::error!(?error);
            false
        }
    }
}

pub trait GsmValidation {
    // TODO : move this function to appropriate place later.
    fn should_call_gsm(&self) -> bool;
}

impl GsmValidation for PayoutData {
    #[inline(always)]
    fn should_call_gsm(&self) -> bool {
        match self.payout_attempt.status {
            common_enums::PayoutStatus::Success
            | common_enums::PayoutStatus::RequiresConfirmation
            | common_enums::PayoutStatus::Cancelled
            | common_enums::PayoutStatus::Pending
            | common_enums::PayoutStatus::Initiated
            | common_enums::PayoutStatus::Reversed
            | common_enums::PayoutStatus::Expired
            | common_enums::PayoutStatus::Ineligible
            | common_enums::PayoutStatus::RequiresCreation
            | common_enums::PayoutStatus::RequiresPayoutMethodData
            | common_enums::PayoutStatus::RequiresVendorAccountCreation
            | common_enums::PayoutStatus::RequiresFulfillment => false,
            common_enums::PayoutStatus::Failed => true,
        }
    }
}


pub async fn payouts_update(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<id_type::PayoutId>,
    json_payload: web::Json<payout_types::PayoutCreateRequest>,
) -> HttpResponse {
    let flow = Flow::PayoutsUpdate;
    let payout_id = path.into_inner();
    let mut payout_update_payload = json_payload.into_inner();
    payout_update_payload.payout_id = Some(payout_id);
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payout_update_payload,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            payouts_update_core(state, merchant_context, req)
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub async fn ephemeral_key_create(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<api_models::ephemeral_key::EphemeralKeyCreateRequest>,
) -> HttpResponse {
    let flow = Flow::EphemeralKeyCreate;
    let payload = json_payload.into_inner();
    api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth: auth::AuthenticationData, payload, _| {
            helpers::make_ephemeral_key(
                state,
                payload.customer_id,
                auth.merchant_account.get_id().to_owned(),
            )
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    )
    .await
}


pub async fn make_payout_method_data(
    state: &SessionState,
    payout_method_data: Option<&api::PayoutMethodData>,
    payout_token: Option<&str>,
    customer_id: &id_type::CustomerId,
    merchant_id: &id_type::MerchantId,
    payout_type: Option<api_enums::PayoutType>,
    merchant_key_store: &domain::MerchantKeyStore,
    payout_data: Option<&mut PayoutData>,
    storage_scheme: storage::enums::MerchantStorageScheme,
) -> RouterResult<Option<api::PayoutMethodData>> {
    let db = &*state.store;
    let hyperswitch_token = if let Some(payout_token) = payout_token {
        if payout_token.starts_with("temporary_token_") {
            Some(payout_token.to_string())
        } else {
            let certain_payout_type = payout_type.get_required_value("payout_type")?.to_owned();
            let key = format!(
                "pm_token_{}_{}_hyperswitch",
                payout_token,
                api_enums::PaymentMethod::foreign_from(certain_payout_type)
            );

            let redis_conn = state
                .store
                .get_redis_conn()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to get redis connection")?;

            let hyperswitch_token = redis_conn
                .get_key::<Option<String>>(&key.into())
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to fetch the token from redis")?
                .ok_or(error_stack::Report::new(
                    errors::ApiErrorResponse::UnprocessableEntity {
                        message: "Token is invalid or expired".to_owned(),
                    },
                ))?;
            let payment_token_data = hyperswitch_token
                .clone()
                .parse_struct("PaymentTokenData")
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("failed to deserialize hyperswitch token data")?;

            let payment_token = match payment_token_data {
                storage::PaymentTokenData::PermanentCard(storage::CardTokenData {
                    locker_id,
                    token,
                    ..
                }) => locker_id.or(Some(token)),
                storage::PaymentTokenData::TemporaryGeneric(storage::GenericTokenData {
                    token,
                }) => Some(token),
                _ => None,
            };
            payment_token.or(Some(payout_token.to_string()))
        }
    } else {
        None
    };

    match (
        payout_method_data.to_owned(),
        hyperswitch_token,
        payout_data,
    ) {
        // Get operation
        (None, Some(payout_token), _) => {
            if payout_token.starts_with("temporary_token_")
                || payout_type == Some(api_enums::PayoutType::Bank)
            {
                let (pm, supplementary_data) = vault::Vault::get_payout_method_data_from_temporary_locker(
                    state,
                    &payout_token,
                    merchant_key_store,
                )
                .await
                .attach_printable(
                    "Payout method for given token not found or there was a problem fetching it",
                )?;
                utils::when(
                    supplementary_data
                        .customer_id
                        .ne(&Some(customer_id.to_owned())),
                    || {
                        Err(errors::ApiErrorResponse::PreconditionFailed { message: "customer associated with payout method and customer passed in payout are not same".into() })
                    },
                )?;
                Ok(pm)
            } else {
                let resp = cards::get_card_from_locker(
                    state,
                    customer_id,
                    merchant_id,
                    payout_token.as_ref(),
                )
                .await
                .attach_printable("Payout method [card] could not be fetched from HS locker")?;
                Ok(Some({
                    api::PayoutMethodData::Card(api::CardPayout {
                        card_number: resp.card_number,
                        expiry_month: resp.card_exp_month,
                        expiry_year: resp.card_exp_year,
                        card_holder_name: resp.name_on_card,
                    })
                }))
            }
        }

        // Create / Update operation
        (Some(payout_method), payout_token, Some(payout_data)) => {
            let lookup_key = vault::Vault::store_payout_method_data_in_locker(
                state,
                payout_token.to_owned(),
                payout_method,
                Some(customer_id.to_owned()),
                merchant_key_store,
            )
            .await?;

            // Update payout_token in payout_attempt table
            if payout_token.is_none() {
                let updated_payout_attempt = storage::PayoutAttemptUpdate::PayoutTokenUpdate {
                    payout_token: lookup_key,
                };
                payout_data.payout_attempt = db
                    .update_payout_attempt(
                        &payout_data.payout_attempt,
                        updated_payout_attempt,
                        &payout_data.payouts,
                        storage_scheme,
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Error updating token in payout attempt")?;
            }
            Ok(Some(payout_method.clone()))
        }

        // Ignore if nothing is passed
        _ => Ok(None),
    }
}


pub fn payments_dynamic_tax_calculation() {}


pub struct BlackhawknetworkVerifyAccountResponse {
    account: AccountInformation,
}


pub async fn generic_update<T, V, P>(
    conn: &PgPooledConn,
    predicate: P,
    values: V,
) -> StorageResult<usize>
where
    T: FilterDsl<P> + HasTable<Table = T> + Table + 'static,
    V: AsChangeset<Target = <Filter<T, P> as HasTable>::Table> + Debug,
    Filter<T, P>: IntoUpdateTarget,
    UpdateStatement<
        <Filter<T, P> as HasTable>::Table,
        <Filter<T, P> as IntoUpdateTarget>::WhereClause,
        <V as AsChangeset>::Changeset,
    >: AsQuery + QueryFragment<Pg> + QueryId + Send + 'static,
{
    let debug_values = format!("{values:?}");

    let query = diesel::update(<T as HasTable>::table().filter(predicate)).set(values);
    logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());

    track_database_call::<T, _, _>(query.execute_async(conn), DatabaseOperation::Update)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable_lazy(|| format!("Error while updating {debug_values}"))
}


pub struct PaymentMethodCollectLinkDetails {
    pub publishable_key: masking::Secret<String>,
    pub client_secret: masking::Secret<String>,
    pub pm_collect_link_id: String,
    pub customer_id: id_type::CustomerId,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub session_expiry: time::PrimitiveDateTime,
    pub return_url: Option<String>,
    #[serde(flatten)]
    pub ui_config: link_utils::GenericLinkUiConfigFormData,
    pub enabled_payment_methods: Option<Vec<link_utils::EnabledPaymentMethod>>,
}


pub struct CardPaymentRequest {
    #[serde(rename = "type")]
    pub payment_type: PaymentMethodType,
    pub card: CardInfo,
    pub reusability: TransactionType,
    pub reference_id: Secret<String>,
}


pub struct PaymentCaptureResponse {
    pub action_id: String,
    pub reference: Option<String>,
}


OpenAPI Block Path: components.schemas.ResponsePaymentMethodsEnabled
{
  "type": "object",
  "required": [
    "payment_method",
    "payment_method_types"
  ],
  "properties": {
    "payment_method": {
      "$ref": "#/components/schemas/PaymentMethod"
    },
    "payment_method_types": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/ResponsePaymentMethodTypes"
      },
      "description": "The list of payment method types enabled for a connector account"
    }
  }
}