// File: connector-template/mod.rs
// Module: connector-template
// Public functions: 1

pub mod transformers;

use error_stack::{report, ResultExt};
use masking::{ExposeInterface, Mask};

use common_utils::{
    errors::CustomResult,
    ext_traits::BytesExt,
    types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector},
    request::{Method, Request, RequestBuilder, RequestContent},
};

use hyperswitch_domain_models::{
    router_data::{AccessToken, ConnectorAuthType, 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::{PaymentsResponseData, RefundsResponseData},
    types::{
        PaymentsAuthorizeRouterData,
        PaymentsCaptureRouterData, PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData,
    },
};
use hyperswitch_interfaces::{
    api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation, ConnectorSpecifications},
    configs::Connectors,
    errors,
    events::connector_api_logs::ConnectorEvent,
    types::{self, Response},
    webhooks,
};
use std::sync::LazyLock;

use common_enums::enums;
use hyperswitch_interfaces::api::ConnectorSpecifications;
use hyperswitch_domain_models::router_response_types::{ConnectorInfo, SupportedPaymentMethods};
use crate::{
    constants::headers,
    types::ResponseRouterData,
    utils,
};
use hyperswitch_domain_models::payment_method_data::PaymentMethodData;

use transformers as {{project-name | downcase}};

#[derive(Clone)]
pub struct {{project-name | downcase | pascal_case}} {
    amount_converter: &'static (dyn AmountConvertor<Output = StringMinorUnit> + Sync)
}

impl {{project-name | downcase | pascal_case}} {
    pub fn new() -> &'static Self {
        &Self {
            amount_converter: &StringMinorUnitForConnector
        }
    }
}

impl api::Payment for {{project-name | downcase | pascal_case}} {}
impl api::PaymentSession for {{project-name | downcase | pascal_case}} {}
impl api::ConnectorAccessToken for {{project-name | downcase | pascal_case}} {}
impl api::MandateSetup for {{project-name | downcase | pascal_case}} {}
impl api::PaymentAuthorize for {{project-name | downcase | pascal_case}} {}
impl api::PaymentSync for {{project-name | downcase | pascal_case}} {}
impl api::PaymentCapture for {{project-name | downcase | pascal_case}} {}
impl api::PaymentVoid for {{project-name | downcase | pascal_case}} {}
impl api::Refund for {{project-name | downcase | pascal_case}} {}
impl api::RefundExecute for {{project-name | downcase | pascal_case}} {}
impl api::RefundSync for {{project-name | downcase | pascal_case}} {}
impl api::PaymentToken for {{project-name | downcase | pascal_case}} {}

impl
    ConnectorIntegration<
        PaymentMethodToken,
        PaymentMethodTokenizationData,
        PaymentsResponseData,
    > for {{project-name | downcase | pascal_case}}
{
    // Not Implemented (R)
}

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for {{project-name | downcase | pascal_case}}
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 mut header = vec![(
            headers::CONTENT_TYPE.to_string(),
            self.get_content_type().to_string().into(),
        )];
        let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
        header.append(&mut api_key);
        Ok(header)
    }
}

impl ConnectorCommon for {{project-name | downcase | pascal_case}} {
    fn id(&self) -> &'static str {
        "{{project-name | downcase}}"
    }

    fn get_currency_unit(&self) -> api::CurrencyUnit {
        todo!()
    //    TODO! Check connector documentation, on which unit they are processing the currency.
    //    If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor,
    //    if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base
    }

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

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

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

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

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

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

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

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

impl
    ConnectorIntegration<
        Session,
        PaymentsSessionData,
        PaymentsResponseData,
    > for {{project-name | downcase | pascal_case}}
{
    //TODO: implement sessions flow
}

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken>
    for {{project-name | downcase | pascal_case}}
{
}

impl
    ConnectorIntegration<
        SetupMandate,
        SetupMandateRequestData,
        PaymentsResponseData,
    > for {{project-name | downcase | pascal_case}}
{
}

impl
    ConnectorIntegration<
        Authorize,
        PaymentsAuthorizeData,
        PaymentsResponseData,
    > for {{project-name | downcase | pascal_case}} {
    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> {
        Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
    }

    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 =
            {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RouterData::from((
                amount,
                req,
            ));
        let connector_req = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}PaymentsRequest::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(&types::PaymentsAuthorizeType::get_url(
                    self, req, connectors,
                )?)
                .attach_default_headers()
                .headers(types::PaymentsAuthorizeType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(types::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: {{project-name | downcase}}::{{project-name | downcase | pascal_case}}PaymentsResponse = res.response.parse_struct("{{project-name | downcase | pascal_case}} 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 {{project-name | downcase | pascal_case}}
{
    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> {
        Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
    }

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

    fn handle_response(
        &self,
        data: &PaymentsSyncRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsSyncRouterData, errors::ConnectorError> {
        let response: {{project-name | downcase}}:: {{project-name | downcase | pascal_case}}PaymentsResponse = res
            .response
            .parse_struct("{{project-name | downcase}} 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 {{project-name | downcase | pascal_case}}
{
    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> {
        Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
    }

    fn get_request_body(
        &self,
        _req: &PaymentsCaptureRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into())
    }

    fn build_request(
        &self,
        req: &PaymentsCaptureRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(types::PaymentsCaptureType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(types::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: {{project-name | downcase }}::{{project-name | downcase | pascal_case}}PaymentsResponse = res
            .response
            .parse_struct("{{project-name | downcase | pascal_case}} 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 {{project-name | downcase | pascal_case}}
{}

impl
    ConnectorIntegration<
        Execute,
        RefundsData,
        RefundsResponseData,
    > for {{project-name | downcase | pascal_case}} {
    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> {
        Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
    }

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

        let connector_router_data =
            {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RouterData::from((
                refund_amount,
                req,
            ));
        let connector_req = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RefundRequest::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(&types::RefundExecuteType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(types::RefundExecuteType::get_headers(self, req, connectors)?)
            .set_body(types::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: {{project-name| downcase}}::RefundResponse = res.response.parse_struct("{{project-name | downcase}} 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 {{project-name | downcase | pascal_case}} {
    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> {
        Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
    }

    fn build_request(
        &self,
        req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Get)
                .url(&types::RefundSyncType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(types::RefundSyncType::get_headers(self, req, connectors)?)
                .set_body(types::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: {{project-name | downcase}}::RefundResponse = res.response.parse_struct("{{project-name | downcase}} 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 {{project-name | downcase | pascal_case}} {
    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 {{project-name | upcase}}_SUPPORTED_PAYMENT_METHODS: LazyLock<SupportedPaymentMethods> =
    LazyLock::new(SupportedPaymentMethods::new);

static {{project-name | upcase}}_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
    display_name: "{{project-name | downcase | pascal_case}}",
    description: "{{project-name | downcase | pascal_case}} connector",
    connector_type: enums::HyperswitchConnectorCategory::PaymentGateway,
};

static {{project-name | upcase}}_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = [];

impl ConnectorSpecifications for {{project-name | downcase | pascal_case}} {
    fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {
        Some(&{{project-name | upcase}}_CONNECTOR_INFO)
    }

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        Some(&*{{project-name | upcase}}_SUPPORTED_PAYMENT_METHODS)
    }

    fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> {
        Some(&{{project-name | upcase}}_SUPPORTED_WEBHOOK_FLOWS)
    }
}


// Function: retrieve_revenue_recovery_process_tracker
// File: crates/router/src/core/revenue_recovery.rs
// Module: router

pub fn retrieve_revenue_recovery_process_tracker(
    state: SessionState,
    id: id_type::GlobalPaymentId,
) -> RouterResponse<revenue_recovery::RevenueRecoveryResponse>


// Function: currency_match
// File: crates/currency_conversion/src/types.rs
// Module: currency_conversion

pub fn currency_match(currency: Currency) -> &'static iso::Currency


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

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


// Function: get_hashicorp_client
// File: crates/external_services/src/hashicorp_vault/core.rs
// Module: external_services

// Documentation: Asynchronously retrieves a HashiCorp Vault client based on the provided configuration.  # Parameters  - `config`: A reference to a `HashiCorpVaultConfig` containing the configuration details.

pub fn get_hashicorp_client(
    config: &HashiCorpVaultConfig,
) -> error_stack::Result<&'static HashiCorpVault, HashiCorpError>


// Struct: ChatRequest
// File: crates/api_models/src/chat.rs
// Module: api_models
// Implementations: 0

pub struct ChatRequest


// Implementation: impl api::Refund for for Worldpayxml
// File: crates/hyperswitch_connectors/src/connectors/worldpayxml.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::Refund for for Worldpayxml


// Implementation: impl Placetopay
// File: crates/hyperswitch_connectors/src/connectors/placetopay.rs
// Module: hyperswitch_connectors
// Methods: 1 total (1 public)

impl Placetopay


// Implementation: impl PaymentIntentMetricsAccumulator
// File: crates/analytics/src/payment_intents/accumulator.rs
// Module: analytics
// Methods: 1 total (1 public)

impl PaymentIntentMetricsAccumulator


// Function: get_compatible_connector
// File: crates/hyperswitch_domain_models/src/merchant_account.rs
// Module: hyperswitch_domain_models

pub fn get_compatible_connector(&self) -> Option<api_models::enums::Connector>


// Implementation: impl ConnectorCommon for for Razorpay
// File: crates/hyperswitch_connectors/src/connectors/razorpay.rs
// Module: hyperswitch_connectors
// Methods: 6 total (0 public)

impl ConnectorCommon for for Razorpay


// Struct: SignifydPaymentsCheckoutRequest
// File: crates/hyperswitch_connectors/src/connectors/signifyd/transformers/api.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct SignifydPaymentsCheckoutRequest


// Implementation: impl ValidateStatusForOperation for for PaymentsCapture
// File: crates/router/src/core/payments/operations/payment_capture_v2.rs
// Module: router
// Methods: 1 total (0 public)

impl ValidateStatusForOperation for for PaymentsCapture


// Function: update_by_theme_id
// File: crates/diesel_models/src/query/user/theme.rs
// Module: diesel_models

pub fn update_by_theme_id(
        conn: &PgPooledConn,
        theme_id: String,
        update: ThemeUpdate,
    ) -> StorageResult<Self>


// Implementation: impl ConnectorSpecifications for for Deutschebank
// File: crates/hyperswitch_connectors/src/connectors/deutschebank.rs
// Module: hyperswitch_connectors
// Methods: 3 total (0 public)

impl ConnectorSpecifications for for Deutschebank


// Implementation: impl ConnectorSpecifications for for Datatrans
// File: crates/hyperswitch_connectors/src/connectors/datatrans.rs
// Module: hyperswitch_connectors
// Methods: 3 total (0 public)

impl ConnectorSpecifications for for Datatrans


// Struct: DeviceRenderingOptionsSupported
// File: crates/hyperswitch_connectors/src/connectors/netcetera/netcetera_types.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct DeviceRenderingOptionsSupported


// Struct: PaypalFulfillResponse
// File: crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PaypalFulfillResponse


// Struct: RuleThreeDsDecisionRule
// File: crates/api_models/src/routing.rs
// Module: api_models
// Implementations: 0

pub struct RuleThreeDsDecisionRule


// Function: is_overcapture_supported_by_connector
// File: crates/common_enums/src/connector_enums.rs
// Module: common_enums

pub fn is_overcapture_supported_by_connector(self) -> bool


// Implementation: impl Hash for for PaymentIntentMetricsBucketIdentifier
// File: crates/api_models/src/analytics/payment_intents.rs
// Module: api_models
// Methods: 1 total (0 public)

impl Hash for for PaymentIntentMetricsBucketIdentifier


// Function: server
// File: crates/router/src/routes/app.rs
// Module: router

pub fn server(state: AppState) -> Scope


// Struct: Args
// File: crates/test_utils/src/newman_runner.rs
// Module: test_utils
// Implementations: 1

pub struct Args


// Function: into_inner
// File: crates/redis_interface/src/types.rs
// Module: redis_interface

pub fn into_inner(self) -> FredRedisValue


    /// - with message version = 2.1.0 and deviceChannel = 02 (BRW).
    /// - with message version = 2.2.0 and deviceChannel = 02 (BRW) and browserJavascriptEnabled = true.
    browser_screen_width: Option<u32>,

    /// Time difference between UTC time and the Cardholder browser local time, in minutes. The field is limited to 1-5
    /// characters where the vauyes is returned from the getTimezoneOffset() method.
    ///
    /// Depending on the message version, the field is required for requests:
    /// - with message version = 2.1.0 and deviceChannel = 02 (BRW).
    /// - with message version = 2.2.0 and deviceChannel = 02 (BRW) and browserJavascriptEnabled = true.
    #[serde(rename = "browserTZ")]
    browser_tz: Option<i32>,

    /// Exact content of the HTTP user-agent header. The field is limited to maximum 2048 characters. If the total length of
    /// the User-Agent sent by the browser exceeds 2048 characters, the 3DS Server truncates the excess portion.
    ///
    /// This field is required for requests where deviceChannel = 02 (BRW).
    browser_user_agent: Option<String>,

    /// Dimensions of the challenge window that has been displayed to the Cardholder. The ACS shall reply with content
    /// that is formatted to appropriately render in this window to provide the best possible user experience.
    ///
    /// Preconfigured sizes are width X height in pixels of the window displayed in the Cardholder browser window. This is
    /// used only to prepare the CReq request and it is not part of the AReq flow. If not present it will be omitted.
    ///
    /// However, when sending the Challenge Request, this field is required when deviceChannel = 02 (BRW).
    ///
    /// Accepted values are:
    ///  - 01 -> 250 x 400
    ///  - 02 -> 390 x 400
    ///  - 03 -> 500 x 600
    ///  - 04 -> 600 x 400
    ///  - 05 -> Full screen
    challenge_window_size: Option<ChallengeWindowSizeEnum>,

    /// Boolean that represents the ability of the cardholder browser to execute JavaScript.
    ///
    /// This field is required for requests where deviceChannel = 02 (BRW).
    /// Available for supporting EMV 3DS 2.2.0 and later versions.
    browser_javascript_enabled: Option<bool>,

    /// Value representing the browser language preference present in the http header, as defined in IETF BCP 47.
    ///
    /// The value is limited to 1-99 elements. Each element should contain a maximum of 100 characters.
    ///
    /// This field is required for requests where deviceChannel = 02 (BRW).
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    accept_language: Option<Vec<String>>,
}

// Split by comma and return the list of accept languages
// If Accept-Language is : fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, List should be [fr-CH, fr, en, de]
pub fn get_list_of_accept_languages(accept_language: String) -> Vec<String> {
    accept_language
        .split(',')
        .map(|lang| lang.split(';').next().unwrap_or(lang).trim().to_string())
        .collect()
}

impl From<BrowserInformation> for Browser {
    fn from(value: BrowserInformation) -> Self {
        Self {
            browser_accept_header: value.accept_header,
            browser_ip: value
                .ip_address
                .map(|ip| masking::Secret::new(ip.to_string())),
            browser_java_enabled: value.java_enabled,
            browser_language: value.language,
            browser_color_depth: value.color_depth.map(|cd| cd.to_string()),
            browser_screen_height: value.screen_height,
            browser_screen_width: value.screen_width,
            browser_tz: value.time_zone,
            browser_user_agent: value.user_agent,
            challenge_window_size: Some(ChallengeWindowSizeEnum::FullScreen),
            browser_javascript_enabled: value.java_script_enabled,
            // Default to ["en"] locale if accept_language is not provided
            accept_language: value
                .accept_language
                .map(get_list_of_accept_languages)
                .or(Some(vec!["en".to_string()])),
        }
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ChallengeWindowSizeEnum {
    #[serde(rename = "01")]
    Size250x400,
    #[serde(rename = "02")]
    Size390x400,
    #[serde(rename = "03")]
    Size500x600,
    #[serde(rename = "04")]
    Size600x400,
    #[serde(rename = "05")]
    FullScreen,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Sdk {
    /// Universally unique ID created upon all installations and updates of the 3DS Requestor App on a Customer Device.
    /// This will be newly generated and stored by the 3DS SDK for each installation or update. The field is limited to 36
    /// characters and it shall have a canonical format as defined in IETF RFC 4122. This may utilize any of the specified
    /// versions as long as the output meets specified requirements.
    ///
    /// Starting from EMV 3DS 2.3.1:
    /// In case of Browser-SDK, the SDK App ID value is not reliable, and may change for each transaction.
    #[serde(rename = "sdkAppID")]
    sdk_app_id: Option<String>,

    /// JWE Object as defined Section 6.2.2.1 containing data encrypted by the SDK for the DS to decrypt. This element is
    /// the only field encrypted in this version of the EMV 3-D Secure specification. The field is sent from the SDK and it
    /// is limited to 64.000 characters. The data will be present when sending to DS, but not present from DS to ACS.
    sdk_enc_data: Option<String>,

    /// Public key component of the ephemeral key pair generated by the 3DS SDK and used to establish session keys between
    /// the 3DS SDK and ACS. In AReq, this data element is contained within the ACS Signed Content JWS Object. The field is
    /// limited to maximum 256 characters.
    sdk_ephem_pub_key: Option<HashMap<String, String>>,

    /// Indicates the maximum amount of time (in minutes) for all exchanges. The field shall have value greater or equals
    /// than 05.
    sdk_max_timeout: Option<u8>,

    /// Identifies the vendor and version of the 3DS SDK that is integrated in a 3DS Requestor App, assigned by EMVCo when
    /// the 3DS SDK is approved. The field is limited to 32 characters.
    ///
    /// Starting from EMV 3DS 2.3.1:
    /// Identifies the vendor and version of the 3DS SDK that is utilised for a specific transaction. The value is
    /// assigned by EMVCo when the Letter of Approval of the specific 3DS SDK is issued.
    sdk_reference_number: Option<String>,

    /// Universally unique transaction identifier assigned by the 3DS SDK to identify a single transaction. The field is
    /// limited to 36 characters and it shall be in a canonical format as defined in IETF RFC 4122. This may utilize any of
    /// the specified versions as long as the output meets specific requirements.
    #[serde(rename = "sdkTransID")]
    sdk_trans_id: Option<String>,

    /// Contains the JWS object(represented as a string) created by the Split-SDK Server for the AReq message. A
    /// Split-SDK Server creates a time-stamped signature on certain transaction data that is sent to the DS for
    /// verification. As a prerequisite, the Split-SDK Server has a key pair PbSDK, PvSDK certificate Cert (PbSDK). This
    /// certificate is an X.509 certificate signed by a DS CA whose public key is known to the DS.
    ///
    /// The Split-SDK Server:
    ///    Creates a JSON object of the following data as the JWS payload to be signed:
    ///
    ///    - SDK Reference Number -> Identifies the vendor and version of the 3DS SDK that is utilised for a specific
    ///      transaction. The value is assigned by EMVCo when the Letter of Approval of the
    ///      specific 3DS SDK is issued. The field is limited to 32 characters.
    ///    - SDK Signature Timestamp -> Date and time indicating when the 3DS SDK generated the Split-SDK Server Signed
    ///      Content converted into UTC. The value is limited to 14 characters. Accepted
    ///      format: YYYYMMDDHHMMSS.
    ///    - SDK Transaction ID -> Universally unique transaction identifier assigned by the 3DS SDK to identify a
    ///      single transaction. The field is limited to 36 characters and it shall be in a
    ///      canonical format as defined in IETF RFC 4122. This may utilize any of the specified
    ///      versions as long as the output meets specific requirements.
    ///    - Split-SDK Server ID -> DS assigned Split-SDK Server identifier. Each DS can provide a unique ID to each
    ///      Split-SDK Server on an individual basis. The field is limited to 32 characters.
    ///      Any individual DS may impose specific formatting and character requirements on the
    ///      contents of this field.
    ///
    ///    Generates a digital signature of the full JSON object according to JWS (RFC 7515) using JWS Compact
    ///    Serialization. The parameter values for this version of the specification and to be included in the JWS
    ///    header are:
    ///
    ///    - `alg`: PS2567 or ES256
    ///    - `x5c`: X.5C v3: Cert (PbSDK) and chaining certificates if present
    ///
    ///    All other parameters: optional
    ///
    ///    Includes the resulting JWS in the AReq message as SDK Server Signed Content
    ///
    /// This field is required if sdkType = 02 or 03 and deviceChannel = 01 (APP)
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    sdk_server_signed_content: Option<String>,

    /// Indicates the type of 3DS SDK.
    /// This data element provides additional information to the DS and ACS to determine the best approach for handling
    /// the transaction. Accepted values are:
    ///
    ///    - 01 -> Default SDK
    ///    - 02 -> Split-SDK
    ///    - 03 -> Limited-SDK
    ///    - 04 -> Browser-SDK
    ///    - 05 -> Shell-SDK
    ///    - 80-99 -> PS-specific value (dependent on the payment scheme type)
    ///
    /// This field is required for requests where deviceChannel = 01 (APP).
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    sdk_type: Option<SdkType>,

    /// Indicates the characteristics of a Default-SDK.
    ///
    /// This field is required for requests where deviceChannel = 01 (APP) and SDK Type = 01.
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    default_sdk_type: Option<DefaultSdkType>,

    /// Indicates the characteristics of a Split-SDK.
    ///
    /// This field is required for requests where deviceChannel = 01 (APP) and SDK Type = 02.
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    split_sdk_type: Option<SplitSdkType>,
}

impl From<api_models::payments::SdkInformation> for Sdk {
    fn from(sdk_info: api_models::payments::SdkInformation) -> Self {
        Self {
            sdk_app_id: Some(sdk_info.sdk_app_id),
            sdk_enc_data: Some(sdk_info.sdk_enc_data),
            sdk_ephem_pub_key: Some(sdk_info.sdk_ephem_pub_key),
            sdk_max_timeout: Some(sdk_info.sdk_max_timeout),
            sdk_reference_number: Some(sdk_info.sdk_reference_number),
            sdk_trans_id: Some(sdk_info.sdk_trans_id),
            sdk_server_signed_content: None,
            sdk_type: sdk_info
                .sdk_type
                .map(SdkType::from)
                .or(Some(SdkType::DefaultSdk)),
            default_sdk_type: Some(DefaultSdkType {
                // hardcoding this value because, it's the only value that is accepted
                sdk_variant: "01".to_string(),
                wrapped_ind: None,
            }),
            split_sdk_type: None,
        }
    }
}

impl From<api_models::payments::SdkType> for SdkType {
    fn from(sdk_type: api_models::payments::SdkType) -> Self {
        match sdk_type {
            api_models::payments::SdkType::DefaultSdk => Self::DefaultSdk,
            api_models::payments::SdkType::SplitSdk => Self::SplitSdk,
            api_models::payments::SdkType::LimitedSdk => Self::LimitedSdk,
            api_models::payments::SdkType::BrowserSdk => Self::BrowserSdk,
            api_models::payments::SdkType::ShellSdk => Self::ShellSdk,
        }
    }
}

/// Enum representing the type of 3DS SDK.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum SdkType {
    #[serde(rename = "01")]
    DefaultSdk,
    #[serde(rename = "02")]
    SplitSdk,
    #[serde(rename = "03")]
    LimitedSdk,
    #[serde(rename = "04")]
    BrowserSdk,
    #[serde(rename = "05")]
    ShellSdk,
    ///    - 80-99 -> PS-specific value (dependent on the payment scheme type)
    #[serde(untagged)]
    PsSpecific(String),
}

/// Struct representing characteristics of a Default-SDK.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DefaultSdkType {
    /// SDK Variant: SDK implementation characteristics
    ///    - Length: 2 characters
    ///    - Values accepted:
    ///       - 01 = Native
    ///       - 02–79 = Reserved for EMVCo future use (values invalid until defined by EMVCo)
    ///       - 80–99 = Reserved for DS use
    sdk_variant: String,

    /// Wrapped Indicator: If the Default-SDK is embedded as a wrapped component in the 3DS Requestor App
    ///    - Length: 1 character
    ///    - Value accepted: Y = Wrapped
    ///    - Only present if value = Y
    wrapped_ind: Option<String>,
}

/// Struct representing characteristics of a Split-SDK.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SplitSdkType {
    /// Split-SDK Variant: Implementation characteristics of the Split-SDK client
    ///    - Length: 2 characters
    ///    - Values accepted:
    ///       - 01 = Native Client
    ///       - 02 = Browser
    ///       - 03 = Shell
    ///       - 04–79 = Reserved for EMVCo future use (values invalid until defined by EMVCo)
    ///       - 80–99 = Reserved for DS use
    sdk_variant: String,

    /// Limited Split-SDK Indicator: If the Split-SDK client has limited capabilities
    ///    - Length: 1 character
    ///    - Value accepted:
    ///      • Y = Limited
    ///    - Only present if value = Y
    limited_ind: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ThreeDSReqAuthMethod {
    /// No 3DS Requestor authentication occurred (i.e. cardholder "logged in" as guest)
    #[serde(rename = "01")]
    Guest,
    /// Login to the cardholder account at the 3DS Requestor system using 3DS Requestor's own credentials
    #[serde(rename = "02")]
    ThreeDsRequestorCredentials,
    /// Login to the cardholder account at the 3DS Requestor system using federated ID
    #[serde(rename = "03")]
    FederatedID,
    /// Login to the cardholder account at the 3DS Requestor system using issuer credentials
    #[serde(rename = "04")]
    IssuerCredentials,
    /// Login to the cardholder account at the 3DS Requestor system using third-party authentication
    #[serde(rename = "05")]
    ThirdPartyAuthentication,
    /// Login to the cardholder account at the 3DS Requestor system using FIDO Authenticator.
    #[serde(rename = "06")]
    FidoAuthenticator,
    /// Login to the cardholder account at the 3DS Requestor system using FIDO Authenticator(FIDO assurance data signed).
    #[serde(rename = "07")]
    FidoAssuranceData,
    /// SRC Assurance Data.
    #[serde(rename = "08")]
    SRCAssuranceData,
    /// Additionally, 80-99 can be used for PS-specific values, regardless of protocol version.
    #[serde(untagged)]
    PsSpecificValue(String),
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DeviceRenderingOptionsSupported {
    pub sdk_interface: SdkInterface,
    /// For Native UI SDK Interface accepted values are 01-04 and for HTML UI accepted values are 01-05.
    pub sdk_ui_type: Vec<SdkUiType>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum SdkInterface {
    #[serde(rename = "01")]
    Native,
    #[serde(rename = "02")]
    Html,
    #[serde(rename = "03")]
    Both,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum SdkUiType {
    #[serde(rename = "01")]
    Text,
    #[serde(rename = "02")]
    SingleSelect,
    #[serde(rename = "03")]
    MultiSelect,
    #[serde(rename = "04")]
    Oob,
    #[serde(rename = "05")]
    HtmlOther,
}


            capture: None,
            items: None,
            recurring_model: if item.router_data.request.is_mandate_payment() {
                Some(MandateType::Unscheduled)
            } else {
                None
            },
            recurring_id: item
                .router_data
                .request
                .mandate_id
                .clone()
                .and_then(|mandate_ids| match mandate_ids.mandate_reference_id {
                    Some(api_models::payments::MandateReferenceId::ConnectorMandateId(
                        connector_mandate_ids,
                    )) => connector_mandate_ids
                        .get_connector_mandate_id()
                        .map(Secret::new),
                    _ => None,
                }),
            days_active: Some(30),
            seconds_active: Some(259200),
            var1: None,
            var2: None,
            var3: None,
        })
    }
}

// Auth Struct
pub struct MultisafepayAuthType {
    pub(super) api_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for MultisafepayAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        if let ConnectorAuthType::HeaderKey { api_key } = auth_type {
            Ok(Self {
                api_key: api_key.to_owned(),
            })
        } else {
            Err(errors::ConnectorError::FailedToObtainAuthType.into())
        }
    }
}
// PaymentsResponse
#[derive(Debug, Clone, Default, Eq, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum MultisafepayPaymentStatus {
    Completed,
    Declined,
    #[default]
    Initialized,
    Void,
    Uncleared,
}

#[derive(Debug, Clone, Eq, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum MandateType {
    Unscheduled,
}

impl From<MultisafepayPaymentStatus> for AttemptStatus {
    fn from(item: MultisafepayPaymentStatus) -> Self {
        match item {
            MultisafepayPaymentStatus::Completed => Self::Charged,
            MultisafepayPaymentStatus::Declined => Self::Failure,
            MultisafepayPaymentStatus::Initialized => Self::AuthenticationPending,
            MultisafepayPaymentStatus::Uncleared => Self::Pending,
            MultisafepayPaymentStatus::Void => Self::Voided,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct Data {
    #[serde(rename = "type")]
    pub payment_type: Option<String>,
    pub order_id: String,
    pub currency: Option<String>,
    pub amount: Option<MinorUnit>,
    pub description: Option<String>,
    pub capture: Option<String>,
    pub payment_url: Option<Url>,
    pub status: Option<MultisafepayPaymentStatus>,
    pub reason: Option<String>,
    pub reason_code: Option<String>,
    pub payment_details: Option<MultisafepayPaymentDetails>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct MultisafepayPaymentDetails {
    pub account_holder_name: Option<Secret<String>>,
    pub account_id: Option<Secret<String>>,
    pub card_expiry_date: Option<Secret<String>>,
    pub external_transaction_id: Option<serde_json::Value>,
    pub last4: Option<Secret<String>>,
    pub recurring_flow: Option<String>,
    pub recurring_id: Option<Secret<String>>,
    pub recurring_model: Option<String>,
    #[serde(rename = "type")]
    pub payment_type: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MultisafepayPaymentsResponse {
    pub success: bool,
    pub data: Data,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(untagged)]
pub enum MultisafepayAuthResponse {
    ErrorResponse(MultisafepayErrorResponse),
    PaymentResponse(Box<MultisafepayPaymentsResponse>),
}

impl<F, T> TryFrom<ResponseRouterData<F, MultisafepayAuthResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<ParsingError>;
    fn try_from(
        item: ResponseRouterData<F, MultisafepayAuthResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        match item.response {
            MultisafepayAuthResponse::PaymentResponse(payment_response) => {
                let redirection_data = payment_response
                    .data
                    .payment_url
                    .clone()
                    .map(|url| RedirectForm::from((url, Method::Get)));

                let default_status = if payment_response.success {
                    MultisafepayPaymentStatus::Initialized
                } else {
                    MultisafepayPaymentStatus::Declined
                };

                let status =
                    AttemptStatus::from(payment_response.data.status.unwrap_or(default_status));

                Ok(Self {
                    status,
                    response: if utils::is_payment_failure(status) {
                        Err(populate_error_reason(
                            payment_response.data.reason_code,
                            payment_response.data.reason.clone(),
                            payment_response.data.reason,
                            item.http_code,
                            Some(status),
                            Some(payment_response.data.order_id),
                        ))
                    } else {
                        Ok(PaymentsResponseData::TransactionResponse {
                            resource_id: ResponseId::ConnectorTransactionId(
                                payment_response.data.order_id.clone(),
                            ),
                            redirection_data: Box::new(redirection_data),
                            mandate_reference: Box::new(
                                payment_response
                                    .data
                                    .payment_details
                                    .and_then(|payment_details| payment_details.recurring_id)
                                    .map(|id| MandateReference {
                                        connector_mandate_id: Some(id.expose()),
                                        payment_method_id: None,
                                        mandate_metadata: None,
                                        connector_mandate_request_reference_id: None,
                                    }),
                            ),
                            connector_metadata: None,
                            network_txn_id: None,
                            connector_response_reference_id: Some(
                                payment_response.data.order_id.clone(),
                            ),
                            incremental_authorization_allowed: None,
                            charges: None,
                        })
                    },
                    ..item.data
                })
            }
            MultisafepayAuthResponse::ErrorResponse(error_response) => {
                let attempt_status = Option::<AttemptStatus>::from(error_response.clone());
                Ok(Self {
                    response: Err(populate_error_reason(
                        Some(error_response.error_code.to_string()),
                        Some(error_response.error_info.clone()),
                        Some(error_response.error_info),
                        item.http_code,
                        attempt_status,
                        None,
                    )),
                    ..item.data
                })
            }
        }
    }
}
pub fn populate_error_reason(
    code: Option<String>,
    message: Option<String>,
    reason: Option<String>,
    http_code: u16,
    attempt_status: Option<AttemptStatus>,
    connector_transaction_id: Option<String>,
) -> ErrorResponse {
    ErrorResponse {
        code: code.unwrap_or(NO_ERROR_CODE.to_string()),
        message: message.clone().unwrap_or(NO_ERROR_MESSAGE.to_string()),
        reason,
        status_code: http_code,
        attempt_status,
        connector_transaction_id,
        network_advice_code: None,
        network_decline_code: None,
        network_error_message: None,
        connector_metadata: None,
    }
}
// REFUND :
// Type definition for RefundRequest
#[derive(Debug, Serialize)]
pub struct MultisafepayRefundRequest {
    pub currency: enums::Currency,
    pub amount: MinorUnit,
    pub description: Option<String>,
    pub refund_order_id: Option<String>,
    pub checkout_data: Option<ShoppingCart>,
}

impl<F> TryFrom<&MultisafepayRouterData<&types::RefundsRouterData<F>>>
    for MultisafepayRefundRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &MultisafepayRouterData<&types::RefundsRouterData<F>>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            currency: item.router_data.request.currency,
            amount: item.amount,
            description: item.router_data.description.clone(),
            refund_order_id: Some(item.router_data.request.refund_id.clone()),
            checkout_data: None,
        })
    }
}

// Type definition for Refund Response

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

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

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct RefundData {
    pub transaction_id: i64,
    pub refund_id: i64,
    pub order_id: Option<String>,
    pub error_code: Option<i32>,
    pub error_info: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefundResponse {
    pub success: bool,
    pub data: RefundData,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MultisafepayRefundResponse {
    ErrorResponse(MultisafepayErrorResponse),
    RefundResponse(RefundResponse),
}

impl TryFrom<RefundsResponseRouterData<Execute, MultisafepayRefundResponse>>
    for types::RefundsRouterData<Execute>
{
    type Error = error_stack::Report<ParsingError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, MultisafepayRefundResponse>,
    ) -> Result<Self, Self::Error> {
        match item.response {
            MultisafepayRefundResponse::RefundResponse(refund_data) => {
                let refund_status = if refund_data.success {
                    RefundStatus::Succeeded
                } else {
                    RefundStatus::Failed
                };

                Ok(Self {
                    response: Ok(RefundsResponseData {
                        connector_refund_id: refund_data.data.refund_id.to_string(),
                        refund_status: enums::RefundStatus::from(refund_status),
                    }),
                    ..item.data
                })
            }
            MultisafepayRefundResponse::ErrorResponse(error_response) => {
                let attempt_status = Option::<AttemptStatus>::from(error_response.clone());
                Ok(Self {
                    response: Err(ErrorResponse {
                        code: error_response.error_code.to_string(),
                        message: error_response.error_info.clone(),
                        reason: Some(error_response.error_info),
                        status_code: item.http_code,
                        attempt_status,
                        connector_transaction_id: None,
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    }),
                    ..item.data
                })
            }
        }
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, MultisafepayRefundResponse>>
    for types::RefundsRouterData<RSync>
{
    type Error = error_stack::Report<ParsingError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, MultisafepayRefundResponse>,
    ) -> Result<Self, Self::Error> {
        match item.response {
            MultisafepayRefundResponse::RefundResponse(refund_data) => {
                let refund_status = if refund_data.success {
                    RefundStatus::Succeeded
                } else {
                    RefundStatus::Failed
                };

                Ok(Self {
                    response: Ok(RefundsResponseData {
                        connector_refund_id: refund_data.data.refund_id.to_string(),
                        refund_status: enums::RefundStatus::from(refund_status),
                    }),
                    ..item.data
                })
            }
            MultisafepayRefundResponse::ErrorResponse(error_response) => Ok(Self {
                response: Err(populate_error_reason(
                    Some(error_response.error_code.to_string()),
                    Some(error_response.error_info.clone()),
                    Some(error_response.error_info),
                    item.http_code,
                    None,
                    None,
                )),
                ..item.data
            }),
        }
    }
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct MultisafepayErrorResponse {
    pub error_code: i32,
    pub error_info: String,
}

impl From<MultisafepayErrorResponse> for Option<AttemptStatus> {
    fn from(error_data: MultisafepayErrorResponse) -> Self {
        match error_data.error_code {
            10001 // InvalidAmount
            | 1002 // InvalidCurrency
            | 1003  // InvalidAccountID
            | 1004 // InvalidSiteID
            | 1005 // InvalidSecurityCode
            | 1006 // InvalidTransactionID
            | 1007 // InvalidIPAddress
            | 1008 // InvalidDescription
            | 1010 // InvalidVariable
            | 1011 // InvalidCustomerAccountID
            | 1012 // InvalidCustomerSecurityCode
            | 1013 // InvalidSignature
            | 1015 //UnknownAccountID
            | 1016 // MissingData
            | 1018 // InvalidCountryCode
            | 1025 // MultisafepayErrorCodes::IncorrectCustomerIPAddress
            | 1026 // MultisafepayErrorCodes::MultipleCurrenciesInCart
            | 1027 // MultisafepayErrorCodes::CartCurrencyDifferentToOrderCurrency
            | 1028 // IncorrectCustomTaxRate
            | 1029 // IncorrectItemTaxRate
            | 1030 // IncorrectItemCurrency
            | 1031 // IncorrectItemPrice
            | 1035 // InvalidSignatureRefund
            | 1036 // InvalidIdealIssuerID
            | 5001 // CartDataNotValidated
            | 1032 // InvalidAPIKey
            => {
                Some(AttemptStatus::AuthenticationFailed)
            }

            1034 // CannotRefundTransaction
            | 1022 // CannotInitiateTransaction
            | 1024 //TransactionDeclined
            => Some(AttemptStatus::Failure),
            1017 // InsufficientFunds
            => Some(AttemptStatus::AuthorizationFailed),
            _ => None,
        }
    }
}