Bug: [FEATURE] allow testing payout links in test_mode



### Feature Description

Payout links is secure by nature, meaning it can be consumed only when below pre-requisites are met
- `allowed_domains` are configured in profile - these are later used for validating the render request, and blow embedding these links using CSP
- render request was originated from an `iframe` (checks `Sec-Fetch-Dest` request header)

Testing these requires a little configuration overhead, and is not easily accessible, introducing a `test_mode` flag which bypasses these checks in non `Production` env

### Possible Implementation

Allow setting `test_mode` against payout_link_config in profile or to be directly sent the payout create request (`test_mode` in create request takes precedence over `test_mode` in profile).

Based on the value, perform below operations
- validate client's Host / Origin headers
- validate `Sec-Fetch-Dest`
- set `Content-Security-Policy` in response headers
- perform top level checks on client (window.top.location !== window.location)

### Have you spent some time checking if this feature request has been raised before?

- [X] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [X] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json
index af1186ac9ed..cc1b2f3137e 100644
--- a/api-reference-v2/openapi_spec.json
+++ b/api-reference-v2/openapi_spec.json
@@ -2953,7 +2953,15 @@
             "$ref": "#/components/schemas/BusinessGenericLinkConfig"
           },
           {
-            "type": "object"
+            "type": "object",
+            "properties": {
+              "payout_test_mode": {
+                "type": "boolean",
+                "description": "Allows for removing any validations / pre-requisites which are necessary in a production environment",
+                "default": false,
+                "nullable": true
+              }
+            }
           }
         ]
       },
@@ -13927,6 +13935,12 @@
                 "description": "List of payout methods shown on collect UI",
                 "example": "[{\"payment_method\": \"bank_transfer\", \"payment_method_types\": [\"ach\", \"bacs\"]}]",
                 "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
               }
             }
           }
diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs
index 18ad5dbf844..41378def83a 100644
--- a/crates/api_models/src/admin.rs
+++ b/crates/api_models/src/admin.rs
@@ -2516,6 +2516,10 @@ pub struct BusinessCollectLinkConfig {
 pub struct BusinessPayoutLinkConfig {
     #[serde(flatten)]
     pub config: BusinessGenericLinkConfig,
+
+    /// Allows for removing any validations / pre-requisites which are necessary in a production environment
+    #[schema(value_type = Option<bool>, default = false)]
+    pub payout_test_mode: Option<bool>,
 }
 
 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
diff --git a/crates/api_models/src/payouts.rs b/crates/api_models/src/payouts.rs
index 0010c2988e1..fc3bd5fcda7 100644
--- a/crates/api_models/src/payouts.rs
+++ b/crates/api_models/src/payouts.rs
@@ -202,6 +202,12 @@ pub struct PayoutCreatePayoutLinkConfig {
     /// List of payout methods shown on collect UI
     #[schema(value_type = Option<Vec<EnabledPaymentMethod>>, example = r#"[{"payment_method": "bank_transfer", "payment_method_types": ["ach", "bacs"]}]"#)]
     pub enabled_payment_methods: Option<Vec<link_utils::EnabledPaymentMethod>>,
+
+    /// `test_mode` allows for opening payout links without any restrictions. This removes
+    /// - domain name validations
+    /// - check for making sure link is accessed within an iframe
+    #[schema(value_type = Option<bool>, example = false)]
+    pub test_mode: Option<bool>,
 }
 
 /// The payout method information required for carrying out a payout
@@ -772,6 +778,7 @@ pub struct PayoutLinkDetails {
     pub amount: common_utils::types::StringMajorUnit,
     pub currency: common_enums::Currency,
     pub locale: String,
+    pub test_mode: bool,
 }
 
 #[derive(Clone, Debug, serde::Serialize)]
@@ -787,4 +794,5 @@ pub struct PayoutLinkStatusDetails {
     pub error_message: Option<String>,
     #[serde(flatten)]
     pub ui_config: link_utils::GenericLinkUiConfigFormData,
+    pub test_mode: bool,
 }
diff --git a/crates/common_utils/src/link_utils.rs b/crates/common_utils/src/link_utils.rs
index e95832eeba9..dc7153f2c5b 100644
--- a/crates/common_utils/src/link_utils.rs
+++ b/crates/common_utils/src/link_utils.rs
@@ -167,6 +167,8 @@ pub struct PayoutLinkData {
     pub currency: enums::Currency,
     /// A list of allowed domains (glob patterns) where this link can be embedded / opened from
     pub allowed_domains: HashSet<String>,
+    /// `test_mode` can be used for testing payout links without any restrictions
+    pub test_mode: Option<bool>,
 }
 
 crate::impl_to_sql_from_sql_json!(PayoutLinkData);
diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs
index cab1aec410f..6c9b839e764 100644
--- a/crates/diesel_models/src/business_profile.rs
+++ b/crates/diesel_models/src/business_profile.rs
@@ -552,6 +552,7 @@ common_utils::impl_to_sql_from_sql_json!(BusinessPaymentLinkConfig);
 pub struct BusinessPayoutLinkConfig {
     #[serde(flatten)]
     pub config: BusinessGenericLinkConfig,
+    pub payout_test_mode: Option<bool>,
 }
 
 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
diff --git a/crates/router/src/core/generic_link/payout_link/initiate/script.js b/crates/router/src/core/generic_link/payout_link/initiate/script.js
index 2327bfba13c..276ef644948 100644
--- a/crates/router/src/core/generic_link/payout_link/initiate/script.js
+++ b/crates/router/src/core/generic_link/payout_link/initiate/script.js
@@ -1,6 +1,10 @@
 // @ts-check
 
 // Top level checks
+// @ts-ignore
+var payoutDetails = window.__PAYOUT_DETAILS;
+var isTestMode = payoutDetails.test_mode;
+
 var isFramed = false;
 try {
   isFramed = window.parent.location !== window.location;
@@ -12,7 +16,7 @@ try {
 }
 
 // Remove the script from DOM incase it's not iframed
-if (!isFramed) {
+if (!isTestMode && !isFramed) {
   function initializePayoutSDK() {
     var errMsg = "{{i18n_not_allowed}}";
     var contentElement = document.getElementById("payout-link");
diff --git a/crates/router/src/core/payout_link.rs b/crates/router/src/core/payout_link.rs
index d18afe02056..7ca564426e8 100644
--- a/crates/router/src/core/payout_link.rs
+++ b/crates/router/src/core/payout_link.rs
@@ -79,7 +79,10 @@ pub async fn initiate_payout_link(
             message: "payout link not found".to_string(),
         })?;
 
-    validator::validate_payout_link_render_request(request_headers, &payout_link)?;
+    let allowed_domains = validator::validate_payout_link_render_request_and_get_allowed_domains(
+        request_headers,
+        &payout_link,
+    )?;
 
     // Check status and return form data accordingly
     let has_expired = common_utils::date_time::now() > payout_link.expiry;
@@ -120,7 +123,7 @@ pub async fn initiate_payout_link(
 
             Ok(services::ApplicationResponse::GenericLinkForm(Box::new(
                 GenericLinks {
-                    allowed_domains: (link_data.allowed_domains),
+                    allowed_domains,
                     data: GenericLinksData::ExpiredLink(expired_link_data),
                     locale,
                 },
@@ -204,6 +207,7 @@ pub async fn initiate_payout_link(
                 amount,
                 currency: payout.destination_currency,
                 locale: locale.clone(),
+                test_mode: link_data.test_mode.unwrap_or(false),
             };
 
             let serialized_css_content = String::new();
@@ -224,7 +228,7 @@ pub async fn initiate_payout_link(
             };
             Ok(services::ApplicationResponse::GenericLinkForm(Box::new(
                 GenericLinks {
-                    allowed_domains: (link_data.allowed_domains),
+                    allowed_domains,
                     data: GenericLinksData::PayoutLink(generic_form_data),
                     locale,
                 },
@@ -249,6 +253,7 @@ pub async fn initiate_payout_link(
                 error_code: payout_attempt.error_code,
                 error_message: payout_attempt.error_message,
                 ui_config: ui_config_data,
+                test_mode: link_data.test_mode.unwrap_or(false),
             };
 
             let serialized_css_content = String::new();
@@ -267,7 +272,7 @@ pub async fn initiate_payout_link(
             };
             Ok(services::ApplicationResponse::GenericLinkForm(Box::new(
                 GenericLinks {
-                    allowed_domains: (link_data.allowed_domains),
+                    allowed_domains,
                     data: GenericLinksData::PayoutLinkStatus(generic_status_data),
                     locale,
                 },
diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs
index a54f00d6aa0..e0bd37aef0d 100644
--- a/crates/router/src/core/payouts.rs
+++ b/crates/router/src/core/payouts.rs
@@ -4,7 +4,7 @@ pub mod helpers;
 pub mod retry;
 pub mod transformers;
 pub mod validator;
-use std::vec::IntoIter;
+use std::{collections::HashSet, vec::IntoIter};
 
 use api_models::{self, enums as api_enums, payouts::PayoutLinkResponse};
 #[cfg(feature = "payout_retry")]
@@ -28,7 +28,7 @@ use futures::future::join_all;
 use masking::{PeekInterface, Secret};
 #[cfg(feature = "payout_retry")]
 use retry::GsmValidation;
-use router_env::{instrument, logger, tracing};
+use router_env::{instrument, logger, tracing, Env};
 use scheduler::utils as pt_utils;
 use serde_json;
 use time::Duration;
@@ -2614,15 +2614,34 @@ pub async fn create_payout_link(
         .and_then(|config| config.ui_config.clone())
         .or(profile_ui_config);
 
-    // Validate allowed_domains presence
-    let allowed_domains = profile_config
+    let test_mode_in_config = payout_link_config_req
         .as_ref()
-        .map(|config| config.config.allowed_domains.to_owned())
-        .get_required_value("allowed_domains")
-        .change_context(errors::ApiErrorResponse::LinkConfigurationError {
-            message: "Payout links cannot be used without setting allowed_domains in profile"
-                .to_string(),
-        })?;
+        .and_then(|config| config.test_mode)
+        .or_else(|| profile_config.as_ref().and_then(|c| c.payout_test_mode));
+    let is_test_mode_enabled = test_mode_in_config.unwrap_or(false);
+
+    let allowed_domains = match (router_env::which(), is_test_mode_enabled) {
+        // Throw error in case test_mode was enabled in production
+        (Env::Production, true) => Err(report!(errors::ApiErrorResponse::LinkConfigurationError {
+            message: "test_mode cannot be true for creating payout_links in production".to_string()
+        })),
+        // Send empty set of whitelisted domains
+        (_, true) => {
+            Ok(HashSet::new())
+        },
+        // Otherwise, fetch and use allowed domains from profile config
+        (_, false) => {
+            profile_config
+            .as_ref()
+            .map(|config| config.config.allowed_domains.to_owned())
+            .get_required_value("allowed_domains")
+            .change_context(errors::ApiErrorResponse::LinkConfigurationError {
+                message:
+                    "Payout links cannot be used without setting allowed_domains in profile. If you're using a non-production environment, you can set test_mode to true while in payout_link_config"
+                        .to_string(),
+            })
+        }
+    }?;
 
     // Form data to be injected in the link
     let (logo, merchant_name, theme) = match ui_config {
@@ -2685,6 +2704,7 @@ pub async fn create_payout_link(
         amount: MinorUnit::from(*amount),
         currency: *currency,
         allowed_domains,
+        test_mode: test_mode_in_config,
     };
 
     create_payout_link_db_entry(state, merchant_id, &data, req.return_url.clone()).await
diff --git a/crates/router/src/core/payouts/validator.rs b/crates/router/src/core/payouts/validator.rs
index d425725645b..2d47a60f23d 100644
--- a/crates/router/src/core/payouts/validator.rs
+++ b/crates/router/src/core/payouts/validator.rs
@@ -1,3 +1,5 @@
+use std::collections::HashSet;
+
 use actix_web::http::header;
 #[cfg(feature = "olap")]
 use common_utils::errors::CustomResult;
@@ -5,7 +7,7 @@ use common_utils::validation::validate_domain_against_allowed_domains;
 use diesel_models::generic_link::PayoutLink;
 use error_stack::{report, ResultExt};
 pub use hyperswitch_domain_models::errors::StorageError;
-use router_env::{instrument, tracing};
+use router_env::{instrument, tracing, which as router_env_which, Env};
 use url::Url;
 
 use super::helpers;
@@ -225,88 +227,105 @@ pub(super) fn validate_payout_list_request_for_joins(
     Ok(())
 }
 
-pub fn validate_payout_link_render_request(
+pub fn validate_payout_link_render_request_and_get_allowed_domains(
     request_headers: &header::HeaderMap,
     payout_link: &PayoutLink,
-) -> RouterResult<()> {
+) -> RouterResult<HashSet<String>> {
     let link_id = payout_link.link_id.to_owned();
     let link_data = payout_link.link_data.to_owned();
 
-    // Fetch destination is "iframe"
-    match request_headers.get("sec-fetch-dest").and_then(|v| v.to_str().ok()) {
-        Some("iframe") => Ok(()),
-        Some(requestor) => Err(report!(errors::ApiErrorResponse::AccessForbidden {
-            resource: "payout_link".to_string(),
-        }))
-        .attach_printable_lazy(|| {
-            format!(
-                "Access to payout_link [{}] is forbidden when requested through {}",
-                link_id, requestor
-            )
-        }),
-        None => Err(report!(errors::ApiErrorResponse::AccessForbidden {
-            resource: "payout_link".to_string(),
-        }))
-        .attach_printable_lazy(|| {
-            format!(
-                "Access to payout_link [{}] is forbidden when sec-fetch-dest is not present in request headers",
-                link_id
-            )
-        }),
-    }?;
+    let is_test_mode_enabled = link_data.test_mode.unwrap_or(false);
 
-    // Validate origin / referer
-    let domain_in_req = {
-        let origin_or_referer = request_headers
-            .get("origin")
-            .or_else(|| request_headers.get("referer"))
-            .and_then(|v| v.to_str().ok())
-            .ok_or_else(|| {
-                report!(errors::ApiErrorResponse::AccessForbidden {
+    match (router_env_which(), is_test_mode_enabled) {
+        // Throw error in case test_mode was enabled in production
+        (Env::Production, true) => Err(report!(errors::ApiErrorResponse::LinkConfigurationError {
+            message: "test_mode cannot be true for rendering payout_links in production"
+                .to_string()
+        })),
+        // Skip all validations when test mode is enabled in non prod env
+        (_, true) => Ok(HashSet::new()),
+        // Otherwise, perform validations
+        (_, false) => {
+            // Fetch destination is "iframe"
+            match request_headers.get("sec-fetch-dest").and_then(|v| v.to_str().ok()) {
+                Some("iframe") => Ok(()),
+                Some(requestor) => Err(report!(errors::ApiErrorResponse::AccessForbidden {
                     resource: "payout_link".to_string(),
-                })
-            })
-            .attach_printable_lazy(|| {
-                format!(
-                    "Access to payout_link [{}] is forbidden when origin or referer is not present in request headers",
-                    link_id
-                )
-            })?;
-
-        let url = Url::parse(origin_or_referer)
-            .map_err(|_| {
-                report!(errors::ApiErrorResponse::AccessForbidden {
+                }))
+                .attach_printable_lazy(|| {
+                    format!(
+                        "Access to payout_link [{}] is forbidden when requested through {}",
+                        link_id, requestor
+                    )
+                }),
+                None => Err(report!(errors::ApiErrorResponse::AccessForbidden {
                     resource: "payout_link".to_string(),
-                })
-            })
-            .attach_printable_lazy(|| {
-                format!("Invalid URL found in request headers {}", origin_or_referer)
-            })?;
+                }))
+                .attach_printable_lazy(|| {
+                    format!(
+                        "Access to payout_link [{}] is forbidden when sec-fetch-dest is not present in request headers",
+                        link_id
+                    )
+                }),
+            }?;
+
+            // Validate origin / referer
+            let domain_in_req = {
+                let origin_or_referer = request_headers
+                    .get("origin")
+                    .or_else(|| request_headers.get("referer"))
+                    .and_then(|v| v.to_str().ok())
+                    .ok_or_else(|| {
+                        report!(errors::ApiErrorResponse::AccessForbidden {
+                            resource: "payout_link".to_string(),
+                        })
+                    })
+                    .attach_printable_lazy(|| {
+                        format!(
+                            "Access to payout_link [{}] is forbidden when origin or referer is not present in request headers",
+                            link_id
+                        )
+                    })?;
 
-        url.host_str()
-            .and_then(|host| url.port().map(|port| format!("{}:{}", host, port)))
-            .or_else(|| url.host_str().map(String::from))
-            .ok_or_else(|| {
-                report!(errors::ApiErrorResponse::AccessForbidden {
+                let url = Url::parse(origin_or_referer)
+                    .map_err(|_| {
+                        report!(errors::ApiErrorResponse::AccessForbidden {
+                            resource: "payout_link".to_string(),
+                        })
+                    })
+                    .attach_printable_lazy(|| {
+                        format!("Invalid URL found in request headers {}", origin_or_referer)
+                    })?;
+
+                url.host_str()
+                    .and_then(|host| url.port().map(|port| format!("{}:{}", host, port)))
+                    .or_else(|| url.host_str().map(String::from))
+                    .ok_or_else(|| {
+                        report!(errors::ApiErrorResponse::AccessForbidden {
+                            resource: "payout_link".to_string(),
+                        })
+                    })
+                    .attach_printable_lazy(|| {
+                        format!("host or port not found in request headers {:?}", url)
+                    })?
+            };
+
+            if validate_domain_against_allowed_domains(
+                &domain_in_req,
+                link_data.allowed_domains.clone(),
+            ) {
+                Ok(link_data.allowed_domains)
+            } else {
+                Err(report!(errors::ApiErrorResponse::AccessForbidden {
                     resource: "payout_link".to_string(),
+                }))
+                .attach_printable_lazy(|| {
+                    format!(
+                        "Access to payout_link [{}] is forbidden from requestor - {}",
+                        link_id, domain_in_req
+                    )
                 })
-            })
-            .attach_printable_lazy(|| {
-                format!("host or port not found in request headers {:?}", url)
-            })?
-    };
-
-    if validate_domain_against_allowed_domains(&domain_in_req, link_data.allowed_domains) {
-        Ok(())
-    } else {
-        Err(report!(errors::ApiErrorResponse::AccessForbidden {
-            resource: "payout_link".to_string(),
-        }))
-        .attach_printable_lazy(|| {
-            format!(
-                "Access to payout_link [{}] is forbidden from requestor - {}",
-                link_id, domain_in_req
-            )
-        })
+            }
+        }
     }
 }
diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs
index 2cc95281f03..b385cc64ad3 100644
--- a/crates/router/src/types/transformers.rs
+++ b/crates/router/src/types/transformers.rs
@@ -1788,6 +1788,7 @@ impl ForeignFrom<api_models::admin::BusinessPayoutLinkConfig>
     fn foreign_from(item: api_models::admin::BusinessPayoutLinkConfig) -> Self {
         Self {
             config: item.config.foreign_into(),
+            payout_test_mode: item.payout_test_mode,
         }
     }
 }
@@ -1798,6 +1799,7 @@ impl ForeignFrom<diesel_models::business_profile::BusinessPayoutLinkConfig>
     fn foreign_from(item: diesel_models::business_profile::BusinessPayoutLinkConfig) -> Self {
         Self {
             config: item.config.foreign_into(),
+            payout_test_mode: item.payout_test_mode,
         }
     }
 }

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
Described in #5668 

### Additional Changes

- [x] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
Allows for easily testing payout links in non prod env.


## How did you test it?
<details>
    <summary>1. Create payout link without setting allowed_domains - *SHOULD FAIL*</summary>

    curl --location 'http://localhost:8080/payouts/create' \
        --header 'Accept-Language: fr' \
        --header 'Content-Type: application/json' \
        --header 'api-key: dev_1axlWLG2ZUMWX1B8TMZ7m1Y7EMIzJl5BW1iLEB6etVvLbEQ1XCKSj6rXHYegkkd3' \
        --data '{
            "amount": 1,
            "currency": "EUR",
            "customer_id": "cus_8GU0bxEIp0EyvQiGSoXw",
            "description": "Its my first payout request",
            "billing": {
                "address": {
                    "city": "Hoogeveen",
                    "country": "NL",
                    "line1": "Raadhuisplein",
                    "line2": "92",
                    "zip": "7901 BW",
                    "state": "FL",
                    "first_name": "John",
                    "last_name": "Doe"
                },
                "phone": {
                    "number": "0650242319",
                    "country_code": "+31"
                }
            },
            "return_url": "https://www.google.co.in",
            "entity_type": "Individual",
            "confirm": false,
            "auto_fulfill": true,
            "session_expiry": 1000000,
            "priority": "instant",
            "profile_id": "pro_24e5hK7LExVPjGqadW55",
            "payout_link": true,
            "connector": [
                "adyen"
            ],
            "payout_link_config": {
                "theme": "#0066ff",
                "logo": "https://hyperswitch.io/favicon.ico",
                "merchant_name": "HyperSwitch Inc.",
                
                "enabled_payment_methods": [
                    {
                        "payment_method": "card",
                        "payment_method_types": [
                            "debit",
                            "credit"
                        ]
                    },
                    {
                        "payment_method": "bank_transfer",
                        "payment_method_types": [
                            "ach",
                            "bacs",
                            "sepa"
                        ]
                    },
                    {
                        "payment_method": "wallet",
                        "payment_method_types": [
                            "pix",
                            "paypal",
                            "venmo"
                        ]
                    }
                ]
            }
        }'

![image](https://github.com/user-attachments/assets/7c76db6f-2912-408c-9ae4-7d436f87d358)

</details>

<details>
    <summary>2. Create payout link without setting allowed_domains in test_mode - *PASS*</summary>

    curl --location 'http://localhost:8080/payouts/create' \
        --header 'Accept-Language: fr' \
        --header 'Content-Type: application/json' \
        --header 'api-key: dev_1axlWLG2ZUMWX1B8TMZ7m1Y7EMIzJl5BW1iLEB6etVvLbEQ1XCKSj6rXHYegkkd3' \
        --data '{
            "amount": 1,
            "currency": "EUR",
            "customer_id": "cus_8GU0bxEIp0EyvQiGSoXw",
            "description": "Its my first payout request",
            "billing": {
                "address": {
                    "city": "Hoogeveen",
                    "country": "NL",
                    "line1": "Raadhuisplein",
                    "line2": "92",
                    "zip": "7901 BW",
                    "state": "FL",
                    "first_name": "John",
                    "last_name": "Doe"
                },
                "phone": {
                    "number": "0650242319",
                    "country_code": "+31"
                }
            },
            "return_url": "https://www.google.co.in",
            "entity_type": "Individual",
            "confirm": false,
            "auto_fulfill": true,
            "session_expiry": 1000000,
            "priority": "instant",
            "profile_id": "pro_24e5hK7LExVPjGqadW55",
            "payout_link": true,
            "connector": [
                "adyen"
            ],
            "payout_link_config": {
                "theme": "#0066ff",
                "logo": "https://hyperswitch.io/favicon.ico",
                "merchant_name": "HyperSwitch Inc.",
                "test_mode": true,
                "enabled_payment_methods": [
                    {
                        "payment_method": "card",
                        "payment_method_types": [
                            "debit",
                            "credit"
                        ]
                    },
                    {
                        "payment_method": "bank_transfer",
                        "payment_method_types": [
                            "ach",
                            "bacs",
                            "sepa"
                        ]
                    },
                    {
                        "payment_method": "wallet",
                        "payment_method_types": [
                            "pix",
                            "paypal",
                            "venmo"
                        ]
                    }
                ]
            }
        }'

![image](https://github.com/user-attachments/assets/dbd870af-5487-40a0-9722-db6d9939f7cb)

![image](https://github.com/user-attachments/assets/e0f19be8-e62e-4179-92e7-c2c86886515f)

</details>

<details>
    <summary>3. Create payout link using allowed_domains without test_mode - *SHOULD OPEN ONLY IN AN IFRAME*</summary>

    curl --location 'http://localhost:8080/payouts/create' \
        --header 'Accept-Language: fr' \
        --header 'Content-Type: application/json' \
        --header 'api-key: dev_1axlWLG2ZUMWX1B8TMZ7m1Y7EMIzJl5BW1iLEB6etVvLbEQ1XCKSj6rXHYegkkd3' \
        --data '{
            "amount": 1,
            "currency": "EUR",
            "customer_id": "cus_8GU0bxEIp0EyvQiGSoXw",
            "description": "Its my first payout request",
            "billing": {
                "address": {
                    "city": "Hoogeveen",
                    "country": "NL",
                    "line1": "Raadhuisplein",
                    "line2": "92",
                    "zip": "7901 BW",
                    "state": "FL",
                    "first_name": "John",
                    "last_name": "Doe"
                },
                "phone": {
                    "number": "0650242319",
                    "country_code": "+31"
                }
            },
            "return_url": "https://www.google.co.in",
            "entity_type": "Individual",
            "confirm": false,
            "auto_fulfill": true,
            "session_expiry": 1000000,
            "priority": "instant",
            "profile_id": "pro_24e5hK7LExVPjGqadW55",
            "payout_link": true,
            "connector": [
                "adyen"
            ],
            "payout_link_config": {
                "theme": "#0066ff",
                "logo": "https://hyperswitch.io/favicon.ico",
                "merchant_name": "HyperSwitch Inc.",
                
                "enabled_payment_methods": [
                    {
                        "payment_method": "card",
                        "payment_method_types": [
                            "debit",
                            "credit"
                        ]
                    },
                    {
                        "payment_method": "bank_transfer",
                        "payment_method_types": [
                            "ach",
                            "bacs",
                            "sepa"
                        ]
                    },
                    {
                        "payment_method": "wallet",
                        "payment_method_types": [
                            "pix",
                            "paypal",
                            "venmo"
                        ]
                    }
                ]
            }
        }'

![image](https://github.com/user-attachments/assets/6a74e591-bf6a-4581-a9e9-52d471d1356a)

![image](https://github.com/user-attachments/assets/aa177ff4-0fb5-4800-8364-eff5a563f9bf)

![image](https://github.com/user-attachments/assets/883784e4-ee88-4a58-a4ec-50a0c2142405)

</details>

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
