Documentation: docs/try_sandbox.md
# Type: Doc File

# Try out hyperswitch sandbox environment

**Table Of Contents:**

- [Set up your accounts](#set-up-your-accounts)
- [Try out our APIs](#try-out-our-apis)
  - [Create a payment](#create-a-payment)
  - [Create a refund](#create-a-refund)

## Set up your accounts

1. Sign up on the payment connector's (say Stripe, Adyen, etc.) dashboard and
   store your connector API key (and any other necessary secrets) securely.
2. Sign up on our [dashboard][dashboard].
3. Create a merchant account on our dashboard and generate your API keys.
   Ensure to save the merchant ID, API key and publishable key displayed on the
   dashboard securely.
4. Configure the merchant return URL and the webhooks URL, which will be used
   on completion of payments and for sending webhooks, respectively.
5. Create a payments connector account by selecting a payment connector among
   the options displayed and fill in the connector credentials you obtained in
   Step 1.
6. Sign up or sign in to [Postman][postman].
7. Open our [Postman collection][postman-collection] and switch to the
   ["Variables" tab][variables].
   Add the API key received in Step 3 under the "current value" column for the
   `api_key` variable.

## Try out our APIs

### Create a payment

1. Open the ["Quick Start" folder][quick-start] in the collection.
2. Open the ["Payments - Create"][payments-create] request, switch to the "Body"
   tab and update any request parameters as required.
   Click on the "Send" button to create a payment.
   If all goes well and you had provided the correct connector credentials, the
   payment should be created successfully.
   You should see the `status` field of the response body having a value of
   `succeeded` in this case.

   - If the `status` of the payment created was `requires_confirmation`, set
     `confirm` to `true` in the request body and send the request again.

3. Open the ["Payments - Retrieve"][payments-retrieve] request and click on the
   "Send" button (without modifying anything).
   This should return the payment object for the payment created in Step 2.

### Create a refund

1. Open the ["Refunds - Create"][refunds-create] request in the
   ["Quick Start" folder][quick-start] folder and switch to the "Body" tab.
   Update the amount to be refunded, if required, and click on the "Send" button.
   This should create a refund against the last payment made for the specified
   amount.
   Check the `status` field of the response body to verify that the refund
   hasn't failed.
2. Open the ["Refunds - Retrieve"][refunds-retrieve] request and switch to the
   "Params" tab.
   Set the `id` path variable in the "Path Variables" table to the `refund_id`
   value returned in the response during the previous step.
   This should return the refund object for the refund created in the previous
   step.

That's it!
Hope you got a hang of our APIs.
To explore more of our APIs, please check the remaining folders in the
[Postman collection][postman-collection].

[dashboard]: https://app.hyperswitch.io
[postman]: https://www.postman.com
[postman-collection]: https://www.postman.com/hyperswitch/workspace/hyperswitch/collection/25176183-e36f8e3d-078c-4067-a273-f456b6b724ed
[variables]: https://www.postman.com/hyperswitch/workspace/hyperswitch/collection/25176183-e36f8e3d-078c-4067-a273-f456b6b724ed?tab=variables
[quick-start]: https://www.postman.com/hyperswitch/workspace/hyperswitch/folder/25176183-0103918c-6611-459b-9faf-354dee8e4437
[payments-create]: https://www.postman.com/hyperswitch/workspace/hyperswitch/request/25176183-9b4ad6a8-fbdd-4919-8505-c75c83bdf9d6
[payments-retrieve]: https://www.postman.com/hyperswitch/workspace/hyperswitch/request/25176183-11995c9b-8a34-4afd-a6ce-e8645693929b
[refunds-create]: https://www.postman.com/hyperswitch/workspace/hyperswitch/request/25176183-5b15d068-db9e-48a5-9ee9-3a70c0aac944
[refunds-retrieve]: https://www.postman.com/hyperswitch/workspace/hyperswitch/request/25176183-c50c32af-5ceb-4ab6-aca7-85f6b32df9d3


pub fn set_ip_address_if_required(
    request: &mut SetMetaDataRequest,
    headers: &HeaderMap,
) -> UserResult<()> {
    if let SetMetaDataRequest::ProductionAgreement(req) = request {
        let ip_address_from_request: Secret<String, common_utils::pii::IpAddress> = headers
            .get(headers::X_FORWARDED_FOR)
            .ok_or(report!(UserErrors::IpAddressParsingFailed))
            .attach_printable("X-Forwarded-For header not found")?
            .to_str()
            .change_context(UserErrors::IpAddressParsingFailed)
            .attach_printable("Error converting Header Value to Str")?
            .split(',')
            .next()
            .and_then(|ip| {
                let ip_addr: Result<IpAddr, _> = ip.parse();
                ip_addr.ok()
            })
            .ok_or(report!(UserErrors::IpAddressParsingFailed))
            .attach_printable("Error Parsing header value to ip")?
            .to_string()
            .into();
        req.ip_address = Some(ip_address_from_request)
    }
    Ok(())
}


OpenAPI Block Path: components.schemas.PayoutCreatePayoutLinkConfig
{
  "allOf": [
    {
      "allOf": [
        {
          "$ref": "#/components/schemas/GenericLinkUiConfig"
        }
      ],
      "nullable": true
    },
    {
      "type": "object",
      "properties": {
        "payout_link_id": {
          "type": "string",
          "description": "The unique identifier for the collect link.",
          "example": "pm_collect_link_2bdacf398vwzq5n422S1",
          "nullable": true
        },
        "enabled_payment_methods": {
          "type": "array",
          "items": {
            "$ref": "#/components/schemas/EnabledPaymentMethod"
          },
          "description": "List of payout methods shown on collect UI",
          "example": "[{\"payment_method\": \"bank_transfer\", \"payment_method_types\": [\"ach\", \"bacs\"]}]",
          "nullable": true
        },
        "form_layout": {
          "allOf": [
            {
              "$ref": "#/components/schemas/UIWidgetFormLayout"
            }
          ],
          "nullable": true
        },
        "test_mode": {
          "type": "boolean",
          "description": "`test_mode` allows for opening payout links without any restrictions. This removes\n- domain name validations\n- check for making sure link is accessed within an iframe",
          "example": false,
          "nullable": true
        }
      }
    }
  ],
  "description": "Custom payout link config for the particular payout, if payout link is to be generated."
}


pub async fn list_merchant_account(
    state: SessionState,
    req: api_models::admin::MerchantAccountListRequest,
    org_data_from_auth: Option<authentication::AuthenticationDataWithOrg>,
) -> RouterResponse<Vec<api::MerchantAccountResponse>> {
    if let Some(auth) = org_data_from_auth {
        if auth.organization_id != req.organization_id {
            return Err(errors::ApiErrorResponse::InvalidRequestData {
                message: "Organization ID in request and authentication do not match".to_string(),
            }
            .into());
        }
    }
    let merchant_accounts = state
        .store
        .list_merchant_accounts_by_organization_id(&(&state).into(), &req.organization_id)
        .await
        .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;

    let merchant_accounts = merchant_accounts
        .into_iter()
        .map(|merchant_account| {
            api::MerchantAccountResponse::foreign_try_from(merchant_account).change_context(
                errors::ApiErrorResponse::InvalidDataValue {
                    field_name: "merchant_account",
                },
            )
        })
        .collect::<Result<Vec<_>, _>>()?;

    Ok(services::ApplicationResponse::Json(merchant_accounts))
}


pub struct CoinbasePaymentsResponse {
    // status: CoinbasePaymentStatus,
    // id: String,
    data: CoinbasePaymentResponseData,
}


pub struct DeutschebankMeansOfPayment {
    bank_account: DeutschebankBankAccount,
}


pub async fn pg_connection_write<T: DatabaseStore>(
    store: &T,
) -> error_stack::Result<
    PooledConnection<'_, async_bb8_diesel::ConnectionManager<PgConnection>>,
    StorageError,
> {
    // Since all writes should happen to master DB only choose master DB.
    let pool = store.get_master_pool();

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


impl api::PaymentCapture for Payu {}


pub async fn success_based_routing_update_configs() {}


pub struct WorldlineRouterData<T> {
    amount: i64,
    router_data: T,
}


impl api::Payment for Moneris {}


File: crates/diesel_models/src/user/dashboard_metadata.rs
 Public structs: 3

use common_utils::id_type;
use diesel::{query_builder::AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use masking::Secret;
use time::PrimitiveDateTime;

use crate::{enums, schema::dashboard_metadata};

#[derive(Clone, Debug, Identifiable, Queryable, Selectable)]
#[diesel(table_name = dashboard_metadata, check_for_backend(diesel::pg::Pg))]
pub struct DashboardMetadata {
    pub id: i32,
    pub user_id: Option<String>,
    pub merchant_id: id_type::MerchantId,
    pub org_id: id_type::OrganizationId,
    pub data_key: enums::DashboardMetadata,
    pub data_value: Secret<serde_json::Value>,
    pub created_by: String,
    pub created_at: PrimitiveDateTime,
    pub last_modified_by: String,
    pub last_modified_at: PrimitiveDateTime,
}

#[derive(
    router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay, AsChangeset,
)]
#[diesel(table_name = dashboard_metadata)]
pub struct DashboardMetadataNew {
    pub user_id: Option<String>,
    pub merchant_id: id_type::MerchantId,
    pub org_id: id_type::OrganizationId,
    pub data_key: enums::DashboardMetadata,
    pub data_value: Secret<serde_json::Value>,
    pub created_by: String,
    pub created_at: PrimitiveDateTime,
    pub last_modified_by: String,
    pub last_modified_at: PrimitiveDateTime,
}

#[derive(
    router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay, AsChangeset,
)]
#[diesel(table_name = dashboard_metadata)]
pub struct DashboardMetadataUpdateInternal {
    pub data_key: enums::DashboardMetadata,
    pub data_value: Secret<serde_json::Value>,
    pub last_modified_by: String,
    pub last_modified_at: PrimitiveDateTime,
}

#[derive(Debug)]
pub enum DashboardMetadataUpdate {
    UpdateData {
        data_key: enums::DashboardMetadata,
        data_value: Secret<serde_json::Value>,
        last_modified_by: String,
    },
}

impl From<DashboardMetadataUpdate> for DashboardMetadataUpdateInternal {
    fn from(metadata_update: DashboardMetadataUpdate) -> Self {
        let last_modified_at = common_utils::date_time::now();
        match metadata_update {
            DashboardMetadataUpdate::UpdateData {
                data_key,
                data_value,
                last_modified_by,
            } => Self {
                data_key,
                data_value,
                last_modified_by,
                last_modified_at,
            },
        }
    }
}


pub async fn update_resource<D, R, M>(
        &self,
        state: &KeyManagerState,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
        update_resource_fut: R,
        updated_resource: M,
        UpdateResourceParams {
            updateable,
            operation,
        }: UpdateResourceParams<'_>,
    ) -> error_stack::Result<D, errors::StorageError>
    where
        D: Debug + Sync + Conversion,
        M: StorageModel<D>,
        R: futures::Future<Output = error_stack::Result<M, DatabaseError>> + Send,
    {
        match operation {
            Op::Update(key, field, updated_by) => {
                let storage_scheme = Box::pin(decide_storage_scheme::<_, M>(
                    self,
                    storage_scheme,
                    Op::Update(key.clone(), field, updated_by),
                ))
                .await;
                match storage_scheme {
                    MerchantStorageScheme::PostgresOnly => {
                        update_resource_fut.await.map_err(|error| {
                            let new_err = diesel_error_to_data_error(*error.current_context());
                            error.change_context(new_err)
                        })
                    }
                    MerchantStorageScheme::RedisKv => {
                        let key_str = key.to_string();
                        let redis_value = serde_json::to_string(&updated_resource)
                            .change_context(errors::StorageError::SerializationFailed)?;

                        let redis_entry = kv::TypedSql {
                            op: kv::DBOperation::Update {
                                updatable: Box::new(updateable),
                            },
                        };
                        Box::pin(kv_wrapper::<(), _, _>(
                            self,
                            KvOperation::<M>::Hset((field, redis_value), redis_entry),
                            key,
                        ))
                        .await
                        .map_err(|err| err.to_redis_failed_response(&key_str))?
                        .try_into_hset()
                        .change_context(errors::StorageError::KVError)?;
                        Ok(updated_resource)
                    }
                }
            }
            _ => Err(errors::StorageError::KVError.into()),
        }?
        .convert(
            state,
            key_store.key.get_inner(),
            key_store.merchant_id.clone().into(),
        )
        .await
        .change_context(errors::StorageError::DecryptionError)
    }


pub struct ItaubankPixResponse {
    #[serde(rename = "endToEndId")]
    pix_id: Option<String>,
}


File: crates/router/src/workflows.rs

#[cfg(feature = "email")]
pub mod api_key_expiry;
#[cfg(feature = "payouts")]
pub mod attach_payout_account_workflow;
pub mod outgoing_webhook_retry;
pub mod payment_method_status_update;
pub mod payment_sync;

pub mod refund_router;

pub mod tokenized_data;

pub mod revenue_recovery;

pub mod process_dispute;

pub mod dispute_list;


impl api::PaymentAuthorize for Billwerk {}

impl api::PaymentAuthorize for for Billwerk


pub trait GroupByClause<T>
where
    T: AnalyticsDataSource,
    AnalyticsCollection: ToSql<T>,
{
    fn set_group_by_clause(&self, builder: &mut QueryBuilder<T>) -> QueryResult<()>;
}


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


impl webhooks::IncomingWebhook for Cybersource {
    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> {
        Ok(api_models::webhooks::IncomingWebhookEvent::EventNotSupported)
    }

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

impl webhooks::IncomingWebhook for for Cybersource


pub struct Log {
    /// Logging to a file.
    pub file: LogFile,
    /// Logging to a console.
    pub console: LogConsole,
    /// Telemetry / tracing.
    pub telemetry: LogTelemetry,
}


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

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

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

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

    fn get_auth_header(
        &self,
        _auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        Ok(vec![])
    }

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

        match response {
            Ok(response_data) => {
                event_builder.map(|i| i.set_error_response_body(&response_data));
                router_env::logger::info!(connector_response=?response_data);
                Ok(ErrorResponse {
                    status_code: res.status_code,
                    code: response_data.status.error_code,
                    message: response_data.status.status.unwrap_or_default(),
                    reason: response_data.status.message,
                    attempt_status: None,
                    connector_transaction_id: None,
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                })
            }
            Err(error_msg) => {
                event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code})));
                logger::error!(deserialization_error =? error_msg);
                utils::handle_json_response_deserialization_failure(res, "rapyd")
            }
        }
    }
}


impl api::Payment for Hipay {}


impl api::RefundExecute for Trustpay {}

impl api::RefundExecute for for Trustpay


pub struct SupplementaryVaultData {
    pub customer_id: Option<id_type::CustomerId>,
    pub payment_method_id: Option<String>,
}


File: crates/api_models/src/authentication.rs
 Public functions: 4
 Public structs: 12

use common_enums::{enums, AuthenticationConnectors};
#[cfg(feature = "v1")]
use common_utils::errors::{self, CustomResult};
use common_utils::{
    events::{ApiEventMetric, ApiEventsType},
    id_type,
};
#[cfg(feature = "v1")]
use error_stack::ResultExt;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use utoipa::ToSchema;

#[cfg(feature = "v1")]
use crate::payments::{Address, BrowserInformation, PaymentMethodData};
use crate::payments::{CustomerDetails, DeviceChannel, SdkInformation, ThreeDsCompletionIndicator};

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationCreateRequest {
    /// The unique identifier for this authentication.
    #[schema(value_type = Option<String>, example = "auth_mbabizu24mvu3mela5njyhpit4")]
    pub authentication_id: Option<id_type::AuthenticationId>,

    /// The business profile that is associated with this authentication
    #[schema(value_type = Option<String>)]
    pub profile_id: Option<id_type::ProfileId>,

    /// Customer details.
    #[schema(value_type = Option<CustomerDetails>)]
    pub customer: Option<CustomerDetails>,

    /// The amount for the transaction, required.
    #[schema(value_type = MinorUnit, example = 1000)]
    pub amount: common_utils::types::MinorUnit,

    /// The connector to be used for authentication, if known.
    #[schema(value_type = Option<AuthenticationConnectors>, example = "netcetera")]
    pub authentication_connector: Option<AuthenticationConnectors>,

    /// The currency for the transaction, required.
    #[schema(value_type = Currency)]
    pub currency: common_enums::Currency,

    /// The URL to which the user should be redirected after authentication.
    #[schema(value_type = Option<String>, example = "https://example.com/redirect")]
    pub return_url: Option<String>,

    /// Force 3DS challenge.
    #[serde(default)]
    pub force_3ds_challenge: Option<bool>,

    /// Choose what kind of sca exemption is required for this payment
    #[schema(value_type = Option<ScaExemptionType>)]
    pub psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,

    /// Profile Acquirer ID get from profile acquirer configuration
    #[schema(value_type = Option<String>)]
    pub profile_acquirer_id: Option<id_type::ProfileAcquirerId>,

    /// Acquirer details information
    #[schema(value_type = Option<AcquirerDetails>)]
    pub acquirer_details: Option<AcquirerDetails>,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AcquirerDetails {
    /// The bin of the card.
    #[schema(value_type = Option<String>, example = "123456")]
    pub acquirer_bin: Option<String>,
    /// The merchant id of the card.
    #[schema(value_type = Option<String>, example = "merchant_abc")]
    pub acquirer_merchant_id: Option<String>,
    /// The country code of the card.
    #[schema(value_type = Option<String>, example = "US/34456")]
    pub merchant_country_code: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationResponse {
    /// The unique identifier for this authentication.
    #[schema(value_type = String, example = "auth_mbabizu24mvu3mela5njyhpit4")]
    pub authentication_id: id_type::AuthenticationId,

    /// This is an identifier for the merchant account. This is inferred from the API key
    /// provided during the request
    #[schema(value_type = String, example = "merchant_abc")]
    pub merchant_id: id_type::MerchantId,

    /// The current status of the authentication (e.g., Started).
    #[schema(value_type = AuthenticationStatus)]
    pub status: common_enums::AuthenticationStatus,

    /// The client secret for this authentication, to be used for client-side operations.
    #[schema(value_type = Option<String>, example = "auth_mbabizu24mvu3mela5njyhpit4_secret_el9ksDkiB8hi6j9N78yo")]
    pub client_secret: Option<masking::Secret<String>>,

    /// The amount for the transaction.
    #[schema(value_type = MinorUnit, example = 1000)]
    pub amount: common_utils::types::MinorUnit,

    /// The currency for the transaction.
    #[schema(value_type = Currency)]
    pub currency: enums::Currency,

    /// The connector to be used for authentication, if known.
    #[schema(value_type = Option<AuthenticationConnectors>, example = "netcetera")]
    pub authentication_connector: Option<AuthenticationConnectors>,

    /// Whether 3DS challenge was forced.
    pub force_3ds_challenge: Option<bool>,

    /// The URL to which the user should be redirected after authentication, if provided.
    pub return_url: Option<String>,

    #[schema(example = "2022-09-10T10:11:12Z")]
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub created_at: Option<PrimitiveDateTime>,

    #[schema(example = "E0001")]
    pub error_code: Option<String>,

    /// If there was an error while calling the connector the error message is received here
    #[schema(example = "Failed while verifying the card")]
    pub error_message: Option<String>,

    /// The business profile that is associated with this payment
    #[schema(value_type = Option<String>)]
    pub profile_id: Option<id_type::ProfileId>,

    /// Choose what kind of sca exemption is required for this payment
    #[schema(value_type = Option<ScaExemptionType>)]
    pub psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,

    /// Acquirer details information
    #[schema(value_type = Option<AcquirerDetails>)]
    pub acquirer_details: Option<AcquirerDetails>,

    /// Profile Acquirer ID get from profile acquirer configuration
    #[schema(value_type = Option<String>)]
    pub profile_acquirer_id: Option<id_type::ProfileAcquirerId>,
}

impl ApiEventMetric for AuthenticationCreateRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        self.authentication_id
            .as_ref()
            .map(|id| ApiEventsType::Authentication {
                authentication_id: id.clone(),
            })
    }
}

impl ApiEventMetric for AuthenticationResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationEligibilityRequest {
    /// Payment method-specific data such as card details, wallet info, etc.
    /// This holds the raw information required to process the payment method.
    #[schema(value_type = PaymentMethodData)]
    pub payment_method_data: PaymentMethodData,

    /// Enum representing the type of payment method being used
    /// (e.g., Card, Wallet, UPI, BankTransfer, etc.).
    #[schema(value_type = PaymentMethod)]
    pub payment_method: common_enums::PaymentMethod,

    /// Optional secret value used to identify and authorize the client making the request.
    /// This can help ensure that the payment session is secure and valid.
    #[schema(value_type = Option<String>)]
    pub client_secret: Option<masking::Secret<String>>,

    /// Optional identifier for the business profile associated with the payment.
    /// This determines which configurations, rules, and branding are applied to the transaction.
    #[schema(value_type = Option<String>)]
    pub profile_id: Option<id_type::ProfileId>,

    /// Optional billing address of the customer.
    /// This can be used for fraud detection, authentication, or compliance purposes.
    #[schema(value_type = Option<Address>)]
    pub billing: Option<Address>,

    /// Optional shipping address of the customer.
    /// This can be useful for logistics, verification, or additional risk checks.
    #[schema(value_type = Option<Address>)]
    pub shipping: Option<Address>,

    /// Optional information about the customer's browser (user-agent, language, etc.).
    /// This is typically used to support 3DS authentication flows and improve risk assessment.
    #[schema(value_type = Option<BrowserInformation>)]
    pub browser_information: Option<BrowserInformation>,

    /// Optional email address of the customer.
    /// Used for customer identification, communication, and possibly for 3DS or fraud checks.
    #[schema(value_type = Option<String>)]
    pub email: Option<common_utils::pii::Email>,
}

#[cfg(feature = "v1")]
impl AuthenticationEligibilityRequest {
    pub fn get_next_action_api(
        &self,
        base_url: String,
        authentication_id: String,
    ) -> CustomResult<NextAction, errors::ParsingError> {
        let url = format!("{base_url}/authentication/{authentication_id}/authenticate");
        Ok(NextAction {
            url: url::Url::parse(&url).change_context(errors::ParsingError::UrlParsingError)?,
            http_method: common_utils::request::Method::Post,
        })
    }

    pub fn get_billing_address(&self) -> Option<Address> {
        self.billing.clone()
    }

    pub fn get_shipping_address(&self) -> Option<Address> {
        self.shipping.clone()
    }

    pub fn get_browser_information(&self) -> Option<BrowserInformation> {
        self.browser_information.clone()
    }
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, ToSchema)]
pub struct AuthenticationEligibilityResponse {
    /// The unique identifier for this authentication.
    #[schema(value_type = String, example = "auth_mbabizu24mvu3mela5njyhpit4")]
    pub authentication_id: id_type::AuthenticationId,
    /// The URL to which the user should be redirected after authentication.
    #[schema(value_type = NextAction)]
    pub next_action: NextAction,
    /// The current status of the authentication (e.g., Started).
    #[schema(value_type = AuthenticationStatus)]
    pub status: common_enums::AuthenticationStatus,
    /// The 3DS data for this authentication.
    #[schema(value_type = Option<EligibilityResponseParams>)]
    pub eligibility_response_params: Option<EligibilityResponseParams>,
    /// The metadata for this authentication.
    #[schema(value_type = serde_json::Value)]
    pub connector_metadata: Option<serde_json::Value>,
    /// The unique identifier for this authentication.
    #[schema(value_type = String)]
    pub profile_id: id_type::ProfileId,
    /// The error message for this authentication.
    #[schema(value_type = Option<String>)]
    pub error_message: Option<String>,
    /// The error code for this authentication.
    #[schema(value_type = Option<String>)]
    pub error_code: Option<String>,
    /// The connector used for this authentication.
    #[schema(value_type = Option<AuthenticationConnectors>)]
    pub authentication_connector: Option<AuthenticationConnectors>,
    /// Billing address
    #[schema(value_type = Option<Address>)]
    pub billing: Option<Address>,
    /// Shipping address
    #[schema(value_type = Option<Address>)]
    pub shipping: Option<Address>,
    /// Browser information
    #[schema(value_type = Option<BrowserInformation>)]
    pub browser_information: Option<BrowserInformation>,
    /// Email
    #[schema(value_type = Option<String>)]
    pub email: common_utils::crypto::OptionalEncryptableEmail,
    /// Acquirer details information.
    #[schema(value_type = Option<AcquirerDetails>)]
    pub acquirer_details: Option<AcquirerDetails>,
}

#[derive(Debug, Serialize, ToSchema)]
pub enum EligibilityResponseParams {
    ThreeDsData(ThreeDsData),
}

#[derive(Debug, Serialize, ToSchema)]
pub struct ThreeDsData {
    /// The unique identifier for this authentication from the 3DS server.
    #[schema(value_type = String)]
    pub three_ds_server_transaction_id: Option<String>,
    /// The maximum supported 3DS version.
    #[schema(value_type = String)]
    pub maximum_supported_3ds_version: Option<common_utils::types::SemanticVersion>,
    /// The unique identifier for this authentication from the connector.
    #[schema(value_type = String)]
    pub connector_authentication_id: Option<String>,
    /// The data required to perform the 3DS method.
    #[schema(value_type = String)]
    pub three_ds_method_data: Option<String>,
    /// The URL to which the user should be redirected after authentication.
    #[schema(value_type = String, example = "https://example.com/redirect")]
    pub three_ds_method_url: Option<url::Url>,
    /// The version of the message.
    #[schema(value_type = String)]
    pub message_version: Option<common_utils::types::SemanticVersion>,
    /// The unique identifier for this authentication.
    #[schema(value_type = String)]
    pub directory_server_id: Option<String>,
}

#[derive(Debug, Serialize, ToSchema)]
pub struct NextAction {
    /// The URL for authenticatating the user.
    #[schema(value_type = String)]
    pub url: url::Url,
    /// The HTTP method to use for the request.
    #[schema(value_type = Method)]
    pub http_method: common_utils::request::Method,
}

#[cfg(feature = "v1")]
impl ApiEventMetric for AuthenticationEligibilityRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        None
    }
}

#[cfg(feature = "v1")]
impl ApiEventMetric for AuthenticationEligibilityResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationAuthenticateRequest {
    /// Authentication ID for the authentication
    #[serde(skip_deserializing)]
    pub authentication_id: id_type::AuthenticationId,
    /// Client secret for the authentication
    #[schema(value_type = String)]
    pub client_secret: Option<masking::Secret<String>>,
    /// SDK Information if request is from SDK
    pub sdk_information: Option<SdkInformation>,
    /// Device Channel indicating whether request is coming from App or Browser
    pub device_channel: DeviceChannel,
    /// Indicates if 3DS method data was successfully completed or not
    pub threeds_method_comp_ind: ThreeDsCompletionIndicator,
}

impl ApiEventMetric for AuthenticationAuthenticateRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationAuthenticateResponse {
    /// Indicates the transaction status
    #[serde(rename = "trans_status")]
    #[schema(value_type = Option<TransactionStatus>)]
    pub transaction_status: Option<common_enums::TransactionStatus>,
    /// Access Server URL to be used for challenge submission
    pub acs_url: Option<url::Url>,
    /// Challenge request which should be sent to acs_url
    pub challenge_request: Option<String>,
    /// Unique identifier assigned by the EMVCo(Europay, Mastercard and Visa)
    pub acs_reference_number: Option<String>,
    /// Unique identifier assigned by the ACS to identify a single transaction
    pub acs_trans_id: Option<String>,
    /// Unique identifier assigned by the 3DS Server to identify a single transaction
    pub three_ds_server_transaction_id: Option<String>,
    /// Contains the JWS object created by the ACS for the ARes(Authentication Response) message
    pub acs_signed_content: Option<String>,
    /// Three DS Requestor URL
    pub three_ds_requestor_url: String,
    /// Merchant app declaring their URL within the CReq message so that the Authentication app can call the Merchant app after OOB authentication has occurred
    pub three_ds_requestor_app_url: Option<String>,

    /// The error message for this authentication.
    #[schema(value_type = String)]
    pub error_message: Option<String>,
    /// The error code for this authentication.
    #[schema(value_type = String)]
    pub error_code: Option<String>,
    /// The authentication value for this authentication, only available in case of server to server request. Unavailable in case of client request due to security concern.
    #[schema(value_type = String)]
    pub authentication_value: Option<masking::Secret<String>>,

    /// The current status of the authentication (e.g., Started).
    #[schema(value_type = AuthenticationStatus)]
    pub status: common_enums::AuthenticationStatus,

    /// The connector to be used for authentication, if known.
    #[schema(value_type = Option<AuthenticationConnectors>, example = "netcetera")]
    pub authentication_connector: Option<AuthenticationConnectors>,

    /// The unique identifier for this authentication.
    #[schema(value_type = AuthenticationId, example = "auth_mbabizu24mvu3mela5njyhpit4")]
    pub authentication_id: id_type::AuthenticationId,

    /// The ECI value for this authentication.
    #[schema(value_type = String)]
    pub eci: Option<String>,

    /// Acquirer details information.
    #[schema(value_type = Option<AcquirerDetails>)]
    pub acquirer_details: Option<AcquirerDetails>,
}

impl ApiEventMetric for AuthenticationAuthenticateResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[cfg(feature = "v1")]
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct AuthenticationSyncResponse {
    // Core Authentication Fields (from AuthenticationResponse)
    /// The unique identifier for this authentication.
    #[schema(value_type = String, example = "auth_mbabizu24mvu3mela5njyhpit4")]
    pub authentication_id: id_type::AuthenticationId,

    /// This is an identifier for the merchant account.
    #[schema(value_type = String, example = "merchant_abc")]
    pub merchant_id: id_type::MerchantId,

    /// The current status of the authentication.
    #[schema(value_type = AuthenticationStatus)]
    pub status: common_enums::AuthenticationStatus,

    /// The client secret for this authentication.
    #[schema(value_type = Option<String>)]
    pub client_secret: Option<masking::Secret<String>>,

    /// The amount for the transaction.
    #[schema(value_type = MinorUnit, example = 1000)]
    pub amount: common_utils::types::MinorUnit,

    /// The currency for the transaction.
    #[schema(value_type = Currency)]
    pub currency: enums::Currency,

    /// The connector used for authentication.
    #[schema(value_type = Option<AuthenticationConnectors>)]
    pub authentication_connector: Option<AuthenticationConnectors>,

    /// Whether 3DS challenge was forced.
    pub force_3ds_challenge: Option<bool>,

    /// The URL to which the user should be redirected after authentication.
    pub return_url: Option<String>,

    #[schema(example = "2022-09-10T10:11:12Z")]
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,

    /// The business profile that is associated with this authentication.
    #[schema(value_type = String)]
    pub profile_id: id_type::ProfileId,

    /// SCA exemption type for this authentication.
    #[schema(value_type = Option<ScaExemptionType>)]
    pub psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,

    /// Acquirer details information.
    #[schema(value_type = Option<AcquirerDetails>)]
    pub acquirer_details: Option<AcquirerDetails>,

    /// The unique identifier from the 3DS server.
    #[schema(value_type = Option<String>)]
    pub threeds_server_transaction_id: Option<String>,

    /// The maximum supported 3DS version.
    #[schema(value_type = Option<String>)]
    pub maximum_supported_3ds_version: Option<common_utils::types::SemanticVersion>,

    /// The unique identifier from the connector.
    #[schema(value_type = Option<String>)]
    pub connector_authentication_id: Option<String>,

    /// The data required to perform the 3DS method.
    #[schema(value_type = Option<String>)]
    pub three_ds_method_data: Option<String>,

    /// The URL for the 3DS method.
    #[schema(value_type = Option<String>)]
    pub three_ds_method_url: Option<String>,

    /// The version of the message.
    #[schema(value_type = Option<String>)]
    pub message_version: Option<common_utils::types::SemanticVersion>,

    /// The metadata for this authentication.
    #[schema(value_type = Option<serde_json::Value>)]
    pub connector_metadata: Option<serde_json::Value>,

    /// The unique identifier for the directory server.
    #[schema(value_type = Option<String>)]
    pub directory_server_id: Option<String>,

    /// Billing address.
    #[schema(value_type = Option<Address>)]
    pub billing: Option<Address>,

    /// Shipping address.
    #[schema(value_type = Option<Address>)]
    pub shipping: Option<Address>,

    /// Browser information.
    #[schema(value_type = Option<BrowserInformation>)]
    pub browser_information: Option<BrowserInformation>,

    /// Email.
    #[schema(value_type = Option<String>)]
    pub email: common_utils::crypto::OptionalEncryptableEmail,

    /// Indicates the transaction status.
    #[serde(rename = "trans_status")]
    #[schema(value_type = Option<TransactionStatus>)]
    pub transaction_status: Option<common_enums::TransactionStatus>,

    /// Access Server URL for challenge submission.
    pub acs_url: Option<String>,

    /// Challenge request to be sent to acs_url.
    pub challenge_request: Option<String>,

    /// Unique identifier assigned by EMVCo.
    pub acs_reference_number: Option<String>,

    /// Unique identifier assigned by the ACS.
    pub acs_trans_id: Option<String>,

    /// JWS object created by the ACS for the ARes message.
    pub acs_signed_content: Option<String>,

    /// Three DS Requestor URL.
    pub three_ds_requestor_url: Option<String>,

    /// Merchant app URL for OOB authentication.
    pub three_ds_requestor_app_url: Option<String>,

    /// The authentication value for this authentication, only available in case of server to server request. Unavailable in case of client request due to security concern.
    #[schema(value_type = Option<String>)]
    pub authentication_value: Option<masking::Secret<String>>,

    /// ECI value for this authentication, only available in case of server to server request. Unavailable in case of client request due to security concern.
    pub eci: Option<String>,

    // Common Error Fields (present in multiple responses)
    /// Error message if any.
    #[schema(value_type = Option<String>)]
    pub error_message: Option<String>,

    /// Error code if any.
    #[schema(value_type = Option<String>)]
    pub error_code: Option<String>,

    /// Profile Acquirer ID
    #[schema(value_type = Option<String>)]
    pub profile_acquirer_id: Option<id_type::ProfileAcquirerId>,
}

#[cfg(feature = "v1")]
impl ApiEventMetric for AuthenticationSyncResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationSyncRequest {
    /// The client secret for this authentication.
    #[schema(value_type = String)]
    pub client_secret: Option<masking::Secret<String>>,
    /// Authentication ID for the authentication
    #[serde(skip_deserializing)]
    pub authentication_id: id_type::AuthenticationId,
}

impl ApiEventMetric for AuthenticationSyncRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct AuthenticationSyncPostUpdateRequest {
    /// Authentication ID for the authentication
    #[serde(skip_deserializing)]
    pub authentication_id: id_type::AuthenticationId,
}

impl ApiEventMetric for AuthenticationSyncPostUpdateRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}


/// get_max_auto_single_connector_payout_retries_enabled_
    pub fn get_max_auto_single_connector_payout_retries_enabled(
        &self,
        payout_retry_type: common_enums::PayoutRetryType,
    ) -> String {
        match payout_retry_type {
            common_enums::PayoutRetryType::SingleConnector => format!(
                "max_auto_single_connector_payout_retries_enabled_{}",
                self.get_string_repr()
            ),
            common_enums::PayoutRetryType::MultiConnector => format!(
                "max_auto_multiple_connector_payout_retries_enabled_{}",
                self.get_string_repr()
            ),
        }
    }


pub struct CashtocodeAuth {
    pub password_classic: Option<Secret<String>>,
    pub password_evoucher: Option<Secret<String>>,
    pub username_classic: Option<Secret<String>>,
    pub username_evoucher: Option<Secret<String>>,
    pub merchant_id_classic: Option<Secret<String>>,
    pub merchant_id_evoucher: Option<Secret<String>>,
}


pub async fn skip_locker_call_and_migrate_payment_method(
    _state: state::PaymentMethodsState,
    _req: &pm_api::PaymentMethodMigrate,
    _merchant_id: id_type::MerchantId,
    _merchant_context: &merchant_context::MerchantContext,
    _card: pm_api::CardDetailFromLocker,
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodResponse>, errors::ApiErrorResponse> {
    todo!()
}


pub struct AirwallexPLShippingAddress {
    country_code: Option<enums::CountryAlpha2>,
    city: Option<String>,
    street: Option<Secret<String>>,
    postcode: Option<Secret<String>>,
}


impl api::Payment for Tsys {}


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

use hyperswitch_domain_models::address::{Address, AddressDetails, PhoneDetails};
use masking::Secret;
use router::types::{self, api, domain, storage::enums};

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

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

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

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

static CONNECTOR: OpennodeTest = OpennodeTest {};

fn get_default_payment_info() -> Option<utils::PaymentInfo> {
    Some(utils::PaymentInfo {
        address: Some(types::PaymentAddress::new(
            None,
            Some(Address {
                address: Some(AddressDetails {
                    first_name: Some(Secret::new("first".to_string())),
                    last_name: Some(Secret::new("last".to_string())),
                    line1: Some(Secret::new("line1".to_string())),
                    line2: Some(Secret::new("line2".to_string())),
                    city: Some("city".to_string()),
                    zip: Some(Secret::new("zip".to_string())),
                    country: Some(api_models::enums::CountryAlpha2::IN),
                    ..Default::default()
                }),
                phone: Some(PhoneDetails {
                    number: Some(Secret::new("9123456789".to_string())),
                    country_code: Some("+91".to_string()),
                }),
                email: None,
            }),
            None,
            None,
        )),
        ..Default::default()
    })
}

fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
    Some(types::PaymentsAuthorizeData {
        amount: 1,
        currency: enums::Currency::USD,
        payment_method_data: domain::PaymentMethodData::Crypto(domain::CryptoData {
            pay_currency: None,
            network: None,
        }),
        confirm: true,
        statement_descriptor_suffix: None,
        statement_descriptor: None,
        setup_future_usage: None,
        mandate_id: None,
        off_session: None,
        setup_mandate_details: None,
        // capture_method: Some(capture_method),
        browser_info: None,
        order_details: None,
        order_category: None,
        email: None,
        customer_name: None,
        payment_experience: None,
        payment_method_type: None,
        session_token: None,
        enrolled_for_3ds: false,
        related_transaction_id: None,
        router_return_url: Some(String::from("https://google.com/")),
        webhook_url: Some(String::from("https://google.com/")),
        complete_authorize_url: None,
        capture_method: None,
        customer_id: None,
        surcharge_details: None,
        request_incremental_authorization: false,
        metadata: None,
        authentication_data: None,
        customer_acceptance: None,
        ..utils::PaymentAuthorizeType::default().0
    })
}

// Creates a payment using the manual capture flow
#[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::AuthenticationPending);
    let resp = response.response.ok().unwrap();
    let endpoint = match resp {
        types::PaymentsResponseData::TransactionResponse {
            redirection_data, ..
        } => Some(redirection_data),
        _ => None,
    };
    assert!(endpoint.is_some())
}

// Synchronizes a successful transaction.
#[actix_web::test]
async fn should_sync_authorized_payment() {
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Authorized,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    "5adebfb1-802e-432b-8b42-5db4b754b2eb".to_string(),
                ),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .expect("PSync response");
    assert_eq!(response.status, enums::AttemptStatus::Charged);
}

// Synchronizes a unresolved(underpaid) transaction.
#[actix_web::test]
async fn should_sync_unresolved_payment() {
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Authorized,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    "4cf63e6b-5135-49cb-997f-6e0b30fecebc".to_string(),
                ),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .expect("PSync response");
    assert_eq!(response.status, enums::AttemptStatus::Unresolved);
}

// Synchronizes a expired transaction.
#[actix_web::test]
async fn should_sync_expired_payment() {
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Authorized,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    "c36a097a-5091-4317-8749-80343a71c1c4".to_string(),
                ),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .expect("PSync response");
    assert_eq!(response.status, enums::AttemptStatus::Failure);
}


pub fn from_string(value: String) -> Self {
        Self {
            inner: FredRedisValue::String(value.into()),
        }
    }


impl ConnectorPreAuthentication for Netcetera {}

impl ConnectorPreAuthentication for for Netcetera


                            }),
                            &None,
                            &None,
                            None,
                            item.http_code,
                            item.response.id.clone(),
                        ))
                    } else {
                        Err(get_error_response(
                            &item.response.error_information,
                            &None,
                            &None,
                            None,
                            item.http_code,
                            item.response.id.clone(),
                        ))
                    }
                } else {
                    Ok(RefundsResponseData {
                        connector_refund_id: item.response.id,
                        refund_status,
                    })
                }
            }

            None => Ok(RefundsResponseData {
                connector_refund_id: item.response.id.clone(),
                refund_status: match item.data.response {
                    Ok(response) => response.refund_status,
                    Err(_) => common_enums::RefundStatus::Pending,
                },
            }),
        };

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

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaStandardErrorResponse {
    pub error_information: Option<ErrorInformation>,
    pub status: Option<String>,
    pub message: Option<String>,
    pub reason: Option<String>,
    pub details: Option<Vec<Details>>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaServerErrorResponse {
    pub status: Option<String>,
    pub message: Option<String>,
    pub reason: Option<Reason>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Reason {
    SystemError,
    ServerTimeout,
    ServiceTimeout,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct BankOfAmericaAuthenticationErrorResponse {
    pub response: AuthenticationErrorInformation,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum BankOfAmericaErrorResponse {
    AuthenticationError(BankOfAmericaAuthenticationErrorResponse),
    StandardError(BankOfAmericaStandardErrorResponse),
}

#[derive(Debug, Deserialize, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Details {
    pub field: String,
    pub reason: String,
}

#[derive(Debug, Default, Deserialize, Serialize)]
pub struct ErrorInformation {
    pub message: String,
    pub reason: String,
    pub details: Option<Vec<Details>>,
}

#[derive(Debug, Default, Deserialize, Serialize)]
pub struct AuthenticationErrorInformation {
    pub rmsg: String,
}

fn get_error_response(
    error_data: &Option<BankOfAmericaErrorInformation>,
    processor_information: &Option<ClientProcessorInformation>,
    risk_information: &Option<ClientRiskInformation>,
    attempt_status: Option<enums::AttemptStatus>,
    status_code: u16,
    transaction_id: String,
) -> ErrorResponse {
    let avs_message = risk_information
        .clone()
        .map(|client_risk_information| {
            client_risk_information.rules.map(|rules| {
                rules
                    .iter()
                    .map(|risk_info| {
                        risk_info.name.clone().map_or("".to_string(), |name| {
                            format!(" , {}", name.clone().expose())
                        })
                    })
                    .collect::<Vec<String>>()
                    .join("")
            })
        })
        .unwrap_or(Some("".to_string()));

    let detailed_error_info = error_data.to_owned().and_then(|error_info| {
        error_info.details.map(|error_details| {
            error_details
                .iter()
                .map(|details| format!("{} : {}", details.field, details.reason))
                .collect::<Vec<_>>()
                .join(", ")
        })
    });
    let network_decline_code = processor_information
        .as_ref()
        .and_then(|info| info.response_code.clone());
    let network_advice_code = processor_information.as_ref().and_then(|info| {
        info.merchant_advice
            .as_ref()
            .and_then(|merchant_advice| merchant_advice.code_raw.clone())
    });

    let reason = get_error_reason(
        error_data
            .clone()
            .and_then(|error_details| error_details.message),
        detailed_error_info,
        avs_message,
    );
    let error_message = error_data
        .clone()
        .and_then(|error_details| error_details.reason);

    ErrorResponse {
        code: error_message
            .clone()
            .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()),
        message: error_message
            .clone()
            .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()),
        reason,
        status_code,
        attempt_status,
        connector_transaction_id: Some(transaction_id.clone()),
        network_advice_code,
        network_decline_code,
        network_error_message: None,
        connector_metadata: None,
    }
}

impl
    TryFrom<(
        &SetupMandateRouterData,
        hyperswitch_domain_models::payment_method_data::Card,
    )> for BankOfAmericaPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (item, ccard): (
            &SetupMandateRouterData,
            hyperswitch_domain_models::payment_method_data::Card,
        ),
    ) -> Result<Self, Self::Error> {
        if item.is_three_ds() {
            Err(errors::ConnectorError::NotSupported {
                message: "Card 3DS".to_string(),
                connector: "BankOfAmerica",
            })?
        };

        let order_information = OrderInformationWithBill::try_from(item)?;
        let client_reference_information = ClientReferenceInformation::from(item);
        let merchant_defined_information =
            item.request.metadata.clone().map(|metadata| {
                convert_metadata_to_merchant_defined_info(metadata.peek().to_owned())
            });
        let payment_information = PaymentInformation::try_from(&ccard)?;
        let processing_information = ProcessingInformation::try_from((None, None))?;
        Ok(Self {
            processing_information,
            payment_information,
            order_information,
            client_reference_information,
            consumer_authentication_information: None,
            merchant_defined_information,
        })
    }
}

impl TryFrom<(&SetupMandateRouterData, ApplePayWalletData)> for BankOfAmericaPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (item, apple_pay_data): (&SetupMandateRouterData, ApplePayWalletData),
    ) -> Result<Self, Self::Error> {
        let order_information = OrderInformationWithBill::try_from(item)?;
        let client_reference_information = ClientReferenceInformation::from(item);
        let merchant_defined_information =
            item.request.metadata.clone().map(|metadata| {
                convert_metadata_to_merchant_defined_info(metadata.peek().to_owned())
            });
        let payment_information = match item.payment_method_token.clone() {
            Some(payment_method_token) => match payment_method_token {
                PaymentMethodToken::ApplePayDecrypt(decrypt_data) => {
                    PaymentInformation::try_from(&decrypt_data)?
                }
                PaymentMethodToken::Token(_) => Err(unimplemented_payment_method!(
                    "Apple Pay",
                    "Manual",
                    "Bank Of America"
                ))?,
                PaymentMethodToken::PazeDecrypt(_) => {
                    Err(unimplemented_payment_method!("Paze", "Bank Of America"))?
                }
                PaymentMethodToken::GooglePayDecrypt(_) => Err(unimplemented_payment_method!(
                    "Google Pay",
                    "Bank Of America"
                ))?,
            },
            None => PaymentInformation::try_from(&apple_pay_data)?,
        };
        let processing_information = ProcessingInformation::try_from((
            Some(PaymentSolution::ApplePay),
            Some(apple_pay_data.payment_method.network.clone()),
        ))?;
        let ucaf_collection_indicator = match apple_pay_data
            .payment_method
            .network
            .to_lowercase()
            .as_str()
        {
            "mastercard" => Some("2".to_string()),
            _ => None,
        };
        let consumer_authentication_information = Some(BankOfAmericaConsumerAuthInformation {
            ucaf_collection_indicator,
            cavv: None,
            ucaf_authentication_data: None,
            xid: None,
            directory_server_transaction_id: None,
            specification_version: None,
        });

        Ok(Self {
            processing_information,
            payment_information,
            order_information,
            client_reference_information,
            merchant_defined_information,
            consumer_authentication_information,
        })
    }
}

impl TryFrom<(&SetupMandateRouterData, GooglePayWalletData)> for BankOfAmericaPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (item, google_pay_data): (&SetupMandateRouterData, GooglePayWalletData),
    ) -> Result<Self, Self::Error> {
        let order_information = OrderInformationWithBill::try_from(item)?;
        let client_reference_information = ClientReferenceInformation::from(item);
        let merchant_defined_information =
            item.request.metadata.clone().map(|metadata| {
                convert_metadata_to_merchant_defined_info(metadata.peek().to_owned())
            });
        let payment_information = PaymentInformation::try_from(&google_pay_data)?;
        let processing_information =
            ProcessingInformation::try_from((Some(PaymentSolution::GooglePay), None))?;

        Ok(Self {
            processing_information,
            payment_information,
            order_information,
            client_reference_information,
            merchant_defined_information,
            consumer_authentication_information: None,
        })
    }
}

// specific for setupMandate flow
impl TryFrom<(Option<PaymentSolution>, Option<String>)> for ProcessingInformation {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (solution, network): (Option<PaymentSolution>, Option<String>),
    ) -> Result<Self, Self::Error> {
        let (action_list, action_token_types, authorization_options) =
            get_boa_mandate_action_details();
        let commerce_indicator = get_commerce_indicator(network);

        Ok(Self {
            capture: Some(false),
            capture_options: None,
            action_list,
            action_token_types,
            authorization_options,
            commerce_indicator,
            payment_solution: solution.map(String::from),
        })
    }
}

impl TryFrom<&SetupMandateRouterData> for OrderInformationWithBill {
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(item: &SetupMandateRouterData) -> Result<Self, Self::Error> {
        let email = item.request.get_email()?;
        let bill_to = build_bill_to(item.get_optional_billing(), email)?;

        Ok(Self {
            amount_details: Amount {
                total_amount: StringMajorUnit::zero(),
                currency: item.request.currency,
            },
            bill_to: Some(bill_to),
        })
    }
}

impl TryFrom<&hyperswitch_domain_models::payment_method_data::Card> for PaymentInformation {
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        ccard: &hyperswitch_domain_models::payment_method_data::Card,
    ) -> Result<Self, Self::Error> {
        let card_type = match ccard.card_network.clone().and_then(get_boa_card_type) {
            Some(card_network) => Some(card_network.to_string()),
            None => ccard.get_card_issuer().ok().map(String::from),
        };
        Ok(Self::Cards(Box::new(CardPaymentInformation {
            card: Card {
                number: ccard.card_number.clone(),
                expiration_month: ccard.card_exp_month.clone(),
                expiration_year: ccard.card_exp_year.clone(),
                security_code: ccard.card_cvc.clone(),
                card_type,
            },
        })))
    }
}

impl TryFrom<&Box<ApplePayPredecryptData>> for PaymentInformation {
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(apple_pay_data: &Box<ApplePayPredecryptData>) -> Result<Self, Self::Error> {
        let expiration_month = apple_pay_data.get_expiry_month().change_context(
            errors::ConnectorError::InvalidDataFormat {
                field_name: "expiration_month",
            },
        )?;
        let expiration_year = apple_pay_data.get_four_digit_expiry_year();

        Ok(Self::ApplePay(Box::new(ApplePayPaymentInformation {
            tokenized_card: TokenizedCard {
                number: apple_pay_data.application_primary_account_number.clone(),
                cryptogram: apple_pay_data
                    .payment_data
                    .online_payment_cryptogram
                    .clone(),
                transaction_type: TransactionType::ApplePay,
                expiration_year,
                expiration_month,
            },
        })))
    }
}

impl TryFrom<&ApplePayWalletData> for PaymentInformation {
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(apple_pay_data: &ApplePayWalletData) -> Result<Self, Self::Error> {
        let apple_pay_encrypted_data = apple_pay_data
            .payment_data
            .get_encrypted_apple_pay_payment_data_mandatory()
            .change_context(errors::ConnectorError::MissingRequiredField {
                field_name: "Apple pay encrypted data",
            })?;

        Ok(Self::ApplePayToken(Box::new(
            ApplePayTokenPaymentInformation {
                fluid_data: FluidData {
                    value: Secret::from(apple_pay_encrypted_data.clone()),
                    descriptor: None,
                },
                tokenized_card: ApplePayTokenizedCard {
                    transaction_type: TransactionType::ApplePay,
                },
            },
        )))
    }
}

impl TryFrom<&GooglePayWalletData> for PaymentInformation {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(google_pay_data: &GooglePayWalletData) -> Result<Self, Self::Error> {
        Ok(Self::GooglePay(Box::new(GooglePayPaymentInformation {
            fluid_data: FluidData {
                value: Secret::from(
                    consts::BASE64_ENGINE.encode(
                        google_pay_data
                            .tokenization_data
                            .get_encrypted_google_pay_token()
                            .change_context(errors::ConnectorError::MissingRequiredField {
                                field_name: "gpay wallet_token",
                            })?
                            .clone(),
                    ),
                ),
                descriptor: None,
            },
        })))
    }
}

fn convert_to_error_response_from_error_info(
    error_response: &BankOfAmericaErrorInformationResponse,
    status_code: u16,
) -> ErrorResponse {
    let detailed_error_info =
        error_response
            .error_information
            .to_owned()
            .details
            .map(|error_details| {
                error_details
                    .iter()
                    .map(|details| format!("{} : {}", details.field, details.reason))
                    .collect::<Vec<_>>()
                    .join(", ")
            });

    let reason = get_error_reason(
        error_response.error_information.message.to_owned(),
        detailed_error_info,
        None,
    );
    ErrorResponse {
        code: error_response
            .error_information
            .reason
            .clone()
            .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()),
        message: error_response
            .error_information
            .reason
            .clone()
            .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()),
        reason,
        status_code,
        attempt_status: None,
        connector_transaction_id: Some(error_response.id.clone()),
        network_advice_code: None,
        network_decline_code: None,
        network_error_message: None,
        connector_metadata: None,
    }
}

fn get_boa_mandate_action_details() -> (
    Option<Vec<BankOfAmericaActionsList>>,
    Option<Vec<BankOfAmericaActionsTokenType>>,
    Option<BankOfAmericaAuthorizationOptions>,
) {
    (
        Some(vec![BankOfAmericaActionsList::TokenCreate]),
        Some(vec![
            BankOfAmericaActionsTokenType::PaymentInstrument,
            BankOfAmericaActionsTokenType::Customer,
        ]),
        Some(BankOfAmericaAuthorizationOptions {
            initiator: Some(BankOfAmericaPaymentInitiator {
                initiator_type: Some(BankOfAmericaPaymentInitiatorTypes::Customer),
                credential_stored_on_file: Some(true),
                stored_credential_used: None,
            }),
            merchant_intitiated_transaction: None,
        }),
    )
}

fn get_commerce_indicator(network: Option<String>) -> String {
    match network {
        Some(card_network) => match card_network.to_lowercase().as_str() {
            "amex" => "aesk",
            "discover" => "dipb",
            "mastercard" => "spa",
            "visa" => "internet",
            _ => "internet",
        },
        None => "internet",
    }
    .to_string()
}

pub fn get_error_reason(
    error_info: Option<String>,
    detailed_error_info: Option<String>,
    avs_error_info: Option<String>,
) -> Option<String> {
    match (error_info, detailed_error_info, avs_error_info) {
        (Some(message), Some(details), Some(avs_message)) => Some(format!(
            "{message}, detailed_error_information: {details}, avs_message: {avs_message}",
        )),
        (Some(message), Some(details), None) => {
            Some(format!("{message}, detailed_error_information: {details}"))
        }
        (Some(message), None, Some(avs_message)) => {
            Some(format!("{message}, avs_message: {avs_message}"))
        }
        (None, Some(details), Some(avs_message)) => {
            Some(format!("{details}, avs_message: {avs_message}"))
        }
        (Some(message), None, None) => Some(message),
        (None, Some(details), None) => Some(details),
        (None, None, Some(avs_message)) => Some(avs_message),
        (None, None, None) => None,
    }
}


impl api::RefundSync for Itaubank {}


pub struct ReturnLink {
    pub rel: LinkType,
    pub href: String,
    pub method: String,
}


pub struct CustomerAcceptance {
    /// Type of acceptance provided by the
    #[schema(example = "online")]
    pub acceptance_type: AcceptanceType,
    /// Specifying when the customer acceptance was provided
    #[schema(example = "2022-09-10T10:11:12Z")]
    #[serde(default, with = "common_utils::custom_serde::iso8601::option")]
    pub accepted_at: Option<PrimitiveDateTime>,
    /// Information required for online mandate generation
    pub online: Option<OnlineMandate>,
}


pub struct ApiModelMetaData {
    pub merchant_config_currency: Option<api_models::enums::Currency>,
    pub merchant_account_id: Option<String>,
    pub account_name: Option<String>,
    pub terminal_id: Option<String>,
    pub merchant_id: Option<String>,
    pub google_pay: Option<GoogleApiModelData>,
    pub paypal_sdk: Option<PaypalSdkData>,
    pub apple_pay: Option<ApplePayData>,
    pub apple_pay_combined: Option<ApplePayData>,
    pub endpoint_prefix: Option<String>,
    pub mcc: Option<String>,
    pub merchant_country_code: Option<String>,
    pub merchant_name: Option<String>,
    pub acquirer_bin: Option<String>,
    pub acquirer_merchant_id: Option<String>,
    pub acquirer_country_code: Option<String>,
    pub three_ds_requestor_name: Option<String>,
    pub three_ds_requestor_id: Option<String>,
    pub pull_mechanism_for_external_3ds_enabled: Option<bool>,
    pub klarna_region: Option<KlarnaEndpoint>,
    pub source_balance_account: Option<String>,
    pub brand_id: Option<String>,
    pub destination_account_number: Option<String>,
    pub dpa_id: Option<String>,
    pub dpa_name: Option<String>,
    pub locale: Option<String>,
    pub card_brands: Option<Vec<String>>,
    pub merchant_category_code: Option<String>,
    pub merchant_configuration_id: Option<String>,
    pub tenant_id: Option<String>,
    pub platform_url: Option<String>,
    pub account_id: Option<String>,
}


pub struct ConnectorNetworkTxnId(masking::Secret<String>);


pub fn from_storage(refund: &'a Refund) -> Self {
        let Refund {
            payment_id,
            merchant_id,
            connector_transaction_id,
            connector,
            connector_refund_id,
            external_reference_id,
            refund_type,
            total_amount,
            currency,
            refund_amount,
            refund_status,
            sent_to_gateway,
            refund_error_message,
            metadata,
            refund_arn,
            created_at,
            modified_at,
            description,
            attempt_id,
            refund_reason,
            refund_error_code,
            profile_id,
            updated_by,
            charges,
            organization_id,
            split_refunds,
            unified_code,
            unified_message,
            processor_refund_data,
            processor_transaction_data,
            id,
            merchant_reference_id,
            connector_id,
        } = refund;

        Self {
            refund_id: id,
            merchant_reference_id,
            payment_id,
            merchant_id,
            connector_transaction_id,
            connector,
            connector_refund_id: connector_refund_id.as_ref(),
            external_reference_id: external_reference_id.as_ref(),
            refund_type,
            total_amount,
            currency,
            refund_amount,
            refund_status,
            sent_to_gateway,
            refund_error_message: refund_error_message.as_ref(),
            refund_arn: refund_arn.as_ref(),
            created_at: created_at.assume_utc(),
            modified_at: modified_at.assume_utc(),
            description: description.as_ref(),
            attempt_id,
            refund_reason: refund_reason.as_ref(),
            refund_error_code: refund_error_code.as_ref(),
            profile_id: profile_id.as_ref(),
            organization_id,
            metadata: metadata.as_ref(),
            updated_by,
            merchant_connector_id: connector_id.as_ref(),
            charges: charges.as_ref(),
            connector_refund_data: processor_refund_data.as_ref(),
            connector_transaction_data: processor_transaction_data.as_ref(),
            split_refunds: split_refunds.as_ref(),
            unified_code: unified_code.as_ref(),
            unified_message: unified_message.as_ref(),
            processor_refund_data: processor_refund_data.as_ref(),
            processor_transaction_data: processor_transaction_data.as_ref(),
        }
    }