File: crates/test_utils/tests/connectors/selenium.rs
 Public functions: 5
 Public structs: 7

#![allow(
    clippy::expect_used,
    clippy::panic,
    clippy::unwrap_in_result,
    clippy::missing_panics_doc,
    clippy::unwrap_used
)]
use std::{
    collections::{HashMap, HashSet},
    env,
    io::Read,
    path::{MAIN_SEPARATOR, MAIN_SEPARATOR_STR},
    time::Duration,
};

use async_trait::async_trait;
use base64::Engine;
use serde::{Deserialize, Serialize};
use serde_json::json;
use test_utils::connector_auth;
use thirtyfour::{components::SelectElement, prelude::*, WebDriver};

#[derive(Clone)]
pub enum Event<'a> {
    RunIf(Assert<'a>, Vec<Event<'a>>),
    EitherOr(Assert<'a>, Vec<Event<'a>>, Vec<Event<'a>>),
    Assert(Assert<'a>),
    Trigger(Trigger<'a>),
}

#[derive(Clone)]
#[allow(dead_code)]
pub enum Trigger<'a> {
    Goto(&'a str),
    Click(By),
    ClickNth(By, usize),
    SelectOption(By, &'a str),
    ChangeQueryParam(&'a str, &'a str),
    SwitchTab(Position),
    SwitchFrame(By),
    Find(By),
    Query(By),
    SendKeys(By, &'a str),
    Sleep(u64),
}

#[derive(Clone)]
pub enum Position {
    Prev,
    Next,
}
#[derive(Clone)]
pub enum Selector {
    Title,
    QueryParamStr,
}

#[derive(Clone)]
pub enum Assert<'a> {
    Eq(Selector, &'a str),
    Contains(Selector, &'a str),
    ContainsAny(Selector, Vec<&'a str>),
    EitherOfThemExist(&'a str, &'a str),
    IsPresent(&'a str),
    IsElePresent(By),
    IsPresentNow(&'a str),
}

pub static CHECKOUT_BASE_URL: &str = "https://hs-payments-test.netlify.app";
#[async_trait]
pub trait SeleniumTest {
    fn get_saved_testcases(&self) -> serde_json::Value {
        get_saved_testcases()
    }
    fn get_configs(&self) -> connector_auth::ConnectorAuthentication {
        get_configs()
    }
    async fn retry_click(
        &self,
        times: i32,
        interval: u64,
        driver: &WebDriver,
        by: By,
    ) -> Result<(), WebDriverError> {
        let mut res = Ok(());
        for _i in 0..times {
            res = self.click_element(driver, by.clone()).await;
            if res.is_err() {
                tokio::time::sleep(Duration::from_secs(interval)).await;
            } else {
                break;
            }
        }
        return res;
    }
    fn get_connector_name(&self) -> String;
    async fn complete_actions(
        &self,
        driver: &WebDriver,
        actions: Vec<Event<'_>>,
    ) -> Result<(), WebDriverError> {
        for action in actions {
            match action {
                Event::Assert(assert) => match assert {
                    Assert::Contains(selector, text) => match selector {
                        Selector::QueryParamStr => {
                            let url = driver.current_url().await?;
                            assert!(url.query().unwrap().contains(text))
                        }
                        _ => assert!(driver.title().await?.contains(text)),
                    },
                    Assert::ContainsAny(selector, search_keys) => match selector {
                        Selector::QueryParamStr => {
                            let url = driver.current_url().await?;
                            assert!(search_keys
                                .iter()
                                .any(|key| url.query().unwrap().contains(key)))
                        }
                        _ => assert!(driver.title().await?.contains(search_keys.first().unwrap())),
                    },
                    Assert::EitherOfThemExist(text_1, text_2) => assert!(
                        is_text_present_now(driver, text_1).await?
                            || is_text_present_now(driver, text_2).await?
                    ),
                    Assert::Eq(_selector, text) => assert_eq!(driver.title().await?, text),
                    Assert::IsPresent(text) => {
                        assert!(is_text_present(driver, text).await?)
                    }
                    Assert::IsElePresent(selector) => {
                        assert!(is_element_present(driver, selector).await?)
                    }
                    Assert::IsPresentNow(text) => {
                        assert!(is_text_present_now(driver, text).await?)
                    }
                },
                Event::RunIf(con_event, events) => match con_event {
                    Assert::Contains(selector, text) => match selector {
                        Selector::QueryParamStr => {
                            let url = driver.current_url().await?;
                            if url.query().unwrap().contains(text) {
                                self.complete_actions(driver, events).await?;
                            }
                        }
                        _ => assert!(driver.title().await?.contains(text)),
                    },
                    Assert::ContainsAny(selector, keys) => match selector {
                        Selector::QueryParamStr => {
                            let url = driver.current_url().await?;
                            if keys.iter().any(|key| url.query().unwrap().contains(key)) {
                                self.complete_actions(driver, events).await?;
                            }
                        }
                        _ => assert!(driver.title().await?.contains(keys.first().unwrap())),
                    },
                    Assert::Eq(_selector, text) => {
                        if text == driver.title().await? {
                            self.complete_actions(driver, events).await?;
                        }
                    }
                    Assert::EitherOfThemExist(text_1, text_2) => {
                        if is_text_present_now(driver, text_1).await.is_ok()
                            || is_text_present_now(driver, text_2).await.is_ok()
                        {
                            self.complete_actions(driver, events).await?;
                        }
                    }
                    Assert::IsPresent(text) => {
                        if is_text_present(driver, text).await.is_ok() {
                            self.complete_actions(driver, events).await?;
                        }
                    }
                    Assert::IsElePresent(text) => {
                        if is_element_present(driver, text).await.is_ok() {
                            self.complete_actions(driver, events).await?;
                        }
                    }
                    Assert::IsPresentNow(text) => {
                        if is_text_present_now(driver, text).await.is_ok() {
                            self.complete_actions(driver, events).await?;
                        }
                    }
                },
                Event::EitherOr(con_event, success, failure) => match con_event {
                    Assert::Contains(selector, text) => match selector {
                        Selector::QueryParamStr => {
                            let url = driver.current_url().await?;
                            self.complete_actions(
                                driver,
                                if url.query().unwrap().contains(text) {
                                    success
                                } else {
                                    failure
                                },
                            )
                            .await?;
                        }
                        _ => assert!(driver.title().await?.contains(text)),
                    },
                    Assert::ContainsAny(selector, keys) => match selector {
                        Selector::QueryParamStr => {
                            let url = driver.current_url().await?;
                            self.complete_actions(
                                driver,
                                if keys.iter().any(|key| url.query().unwrap().contains(key)) {
                                    success
                                } else {
                                    failure
                                },
                            )
                            .await?;
                        }
                        _ => assert!(driver.title().await?.contains(keys.first().unwrap())),
                    },
                    Assert::Eq(_selector, text) => {
                        self.complete_actions(
                            driver,
                            if text == driver.title().await? {
                                success
                            } else {
                                failure
                            },
                        )
                        .await?;
                    }
                    Assert::EitherOfThemExist(text_1, text_2) => {
                        self.complete_actions(
                            driver,
                            if is_text_present_now(driver, text_1).await.is_ok()
                                || is_text_present_now(driver, text_2).await.is_ok()
                            {
                                success
                            } else {
                                failure
                            },
                        )
                        .await?;
                    }
                    Assert::IsPresent(text) => {
                        self.complete_actions(
                            driver,
                            if is_text_present(driver, text).await.is_ok() {
                                success
                            } else {
                                failure
                            },
                        )
                        .await?;
                    }
                    Assert::IsElePresent(by) => {
                        self.complete_actions(
                            driver,
                            if is_element_present(driver, by).await.is_ok() {
                                success
                            } else {
                                failure
                            },
                        )
                        .await?;
                    }
                    Assert::IsPresentNow(text) => {
                        self.complete_actions(
                            driver,
                            if is_text_present_now(driver, text).await.is_ok() {
                                success
                            } else {
                                failure
                            },
                        )
                        .await?;
                    }
                },
                Event::Trigger(trigger) => match trigger {
                    Trigger::Goto(url) => {
                        let saved_tests =
                            serde_json::to_string(&self.get_saved_testcases()).unwrap();
                        let conf = serde_json::to_string(&self.get_configs()).unwrap();
                        let configs = self.get_configs().automation_configs.unwrap();
                        let hs_base_url = configs
                            .hs_base_url
                            .unwrap_or_else(|| "http://localhost:8080".to_string());
                        let configs_url = configs.configs_url.unwrap();
                        let hs_api_keys = configs.hs_api_keys.unwrap();
                        let test_env = configs.hs_test_env.unwrap();
                        let script = &[
                            format!("localStorage.configs='{configs_url}'").as_str(),
                            format!("localStorage.current_env='{test_env}'").as_str(),
                            "localStorage.hs_api_key=''",
                            format!("localStorage.hs_api_keys='{hs_api_keys}'").as_str(),
                            format!("localStorage.base_url='{hs_base_url}'").as_str(),
                            format!("localStorage.hs_api_configs='{conf}'").as_str(),
                            format!("localStorage.saved_payments=JSON.stringify({saved_tests})")
                                .as_str(),
                            "localStorage.force_sync='true'",
                            format!(
                                "localStorage.current_connector=\"{}\";",
                                self.get_connector_name().clone()
                            )
                            .as_str(),
                        ]
                        .join(";");
                        driver.goto(url).await?;
                        driver.execute(script, Vec::new()).await?;
                    }
                    Trigger::Click(by) => {
                        self.retry_click(3, 5, driver, by.clone()).await?;
                    }
                    Trigger::ClickNth(by, n) => {
                        let ele = driver.query(by).all().await?.into_iter().nth(n).unwrap();
                        ele.wait_until().enabled().await?;
                        ele.wait_until().displayed().await?;
                        ele.wait_until().clickable().await?;
                        ele.scroll_into_view().await?;
                        ele.click().await?;
                    }
                    Trigger::Find(by) => {
                        driver.find(by).await?;
                    }
                    Trigger::Query(by) => {
                        driver.query(by).first().await?;
                    }
                    Trigger::SendKeys(by, input) => {
                        let ele = driver.query(by).first().await?;
                        ele.wait_until().displayed().await?;
                        ele.send_keys(&input).await?;
                    }
                    Trigger::SelectOption(by, input) => {
                        let ele = driver.query(by).first().await?;
                        let select_element = SelectElement::new(&ele).await?;
                        select_element.select_by_partial_text(input).await?;
                    }
                    Trigger::ChangeQueryParam(param, value) => {
                        let mut url = driver.current_url().await?;
                        let mut hash_query: HashMap<String, String> =
                            url.query_pairs().into_owned().collect();
                        hash_query.insert(param.to_string(), value.to_string());
                        let url_str = serde_urlencoded::to_string(hash_query)
                            .expect("Query Param update failed");
                        url.set_query(Some(&url_str));
                        driver.goto(url.as_str()).await?;
                    }
                    Trigger::Sleep(seconds) => {
                        tokio::time::sleep(Duration::from_secs(seconds)).await;
                    }
                    Trigger::SwitchTab(position) => match position {
                        Position::Next => {
                            let windows = driver.windows().await?;
                            if let Some(window) = windows.iter().next_back() {
                                driver.switch_to_window(window.to_owned()).await?;
                            }
                        }
                        Position::Prev => {
                            let windows = driver.windows().await?;
                            if let Some(window) = windows.into_iter().next() {
                                driver.switch_to_window(window.to_owned()).await?;
                            }
                        }
                    },
                    Trigger::SwitchFrame(by) => {
                        let iframe = driver.query(by).first().await?;
                        iframe.wait_until().displayed().await?;
                        iframe.clone().enter_frame().await?;
                    }
                },
            }
        }
        Ok(())
    }

    async fn click_element(&self, driver: &WebDriver, by: By) -> Result<(), WebDriverError> {
        let ele = driver.query(by).first().await?;
        ele.wait_until().enabled().await?;
        ele.wait_until().displayed().await?;
        ele.wait_until().clickable().await?;
        ele.scroll_into_view().await?;
        ele.click().await
    }

    async fn make_redirection_payment(
        &self,
        web_driver: WebDriver,
        actions: Vec<Event<'_>>,
    ) -> Result<(), WebDriverError> {
        // To support failure retries
        let result = self
            .execute_steps(web_driver.clone(), actions.clone())
            .await;
        if result.is_err() {
            self.execute_steps(web_driver, actions).await
        } else {
            result
        }
    }
    async fn execute_steps(
        &self,
        web_driver: WebDriver,
        actions: Vec<Event<'_>>,
    ) -> Result<(), WebDriverError> {
        let config = self.get_configs().automation_configs.unwrap();
        if config.run_minimum_steps.unwrap() {
            self.complete_actions(&web_driver, actions.get(..3).unwrap().to_vec())
                .await
        } else {
            self.complete_actions(&web_driver, actions).await
        }
    }
    async fn make_gpay_payment(
        &self,
        web_driver: WebDriver,
        url: &str,
        actions: Vec<Event<'_>>,
    ) -> Result<(), WebDriverError> {
        self.execute_gpay_steps(web_driver, url, actions).await
    }
    async fn execute_gpay_steps(
        &self,
        web_driver: WebDriver,
        url: &str,
        actions: Vec<Event<'_>>,
    ) -> Result<(), WebDriverError> {
        let config = self.get_configs().automation_configs.unwrap();
        let (email, pass) = (&config.gmail_email.unwrap(), &config.gmail_pass.unwrap());
        let default_actions = vec![
            Event::Trigger(Trigger::Goto(url)),
            Event::Trigger(Trigger::Click(By::Css(".gpay-button"))),
            Event::Trigger(Trigger::SwitchTab(Position::Next)),
            Event::Trigger(Trigger::Sleep(5)),
            Event::RunIf(
                Assert::EitherOfThemExist("Use your Google Account", "Sign in"),
                vec![
                    Event::Trigger(Trigger::SendKeys(By::Id("identifierId"), email)),
                    Event::Trigger(Trigger::ClickNth(By::Tag("button"), 2)),
                    Event::EitherOr(
                        Assert::IsPresent("Welcome"),
                        vec![
                            Event::Trigger(Trigger::SendKeys(By::Name("Passwd"), pass)),
                            Event::Trigger(Trigger::Sleep(2)),
                            Event::Trigger(Trigger::Click(By::Id("passwordNext"))),
                            Event::Trigger(Trigger::Sleep(10)),
                        ],
                        vec![
                            Event::Trigger(Trigger::SendKeys(By::Id("identifierId"), email)),
                            Event::Trigger(Trigger::ClickNth(By::Tag("button"), 2)),
                            Event::Trigger(Trigger::SendKeys(By::Name("Passwd"), pass)),
                            Event::Trigger(Trigger::Sleep(2)),
                            Event::Trigger(Trigger::Click(By::Id("passwordNext"))),
                            Event::Trigger(Trigger::Sleep(10)),
                        ],
                    ),
                ],
            ),
            Event::Trigger(Trigger::SwitchFrame(By::Css(
                ".bootstrapperIframeContainerElement iframe",
            ))),
            Event::Assert(Assert::IsPresent("Gpay Tester")),
            Event::Trigger(Trigger::Click(By::ClassName("jfk-button-action"))),
            Event::Trigger(Trigger::SwitchTab(Position::Prev)),
        ];
        self.complete_actions(&web_driver, default_actions).await?;
        self.complete_actions(&web_driver, actions).await
    }
    async fn make_affirm_payment(
        &self,
        driver: WebDriver,
        url: &str,
        actions: Vec<Event<'_>>,
    ) -> Result<(), WebDriverError> {
        self.complete_actions(
            &driver,
            vec![
                Event::Trigger(Trigger::Goto(url)),
                Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
            ],
        )
        .await?;
        let mut affirm_actions = vec![
            Event::RunIf(
                Assert::IsPresent("Big purchase? No problem."),
                vec![
                    Event::Trigger(Trigger::SendKeys(
                        By::Css("input[data-testid='phone-number-field']"),
                        "(833) 549-5574", // any test phone number accepted by affirm
                    )),
                    Event::Trigger(Trigger::Click(By::Css(
                        "button[data-testid='submit-button']",
                    ))),
                    Event::Trigger(Trigger::SendKeys(
                        By::Css("input[data-testid='phone-pin-field']"),
                        "1234",
                    )),
                ],
            ),
            Event::Trigger(Trigger::Click(By::Css(
                "button[data-testid='skip-payment-button']",
            ))),
            Event::Trigger(Trigger::Click(By::Css("div[data-testid='indicator']"))),
            Event::Trigger(Trigger::Click(By::Css(
                "button[data-testid='submit-button']",
            ))),
            Event::Trigger(Trigger::Click(By::Css("div[data-testid='indicator']"))),
            Event::Trigger(Trigger::Click(By::Css(
                "div[data-testid='disclosure-checkbox-indicator']",
            ))),
            Event::Trigger(Trigger::Click(By::Css(
                "button[data-testid='submit-button']",
            ))),
        ];
        affirm_actions.extend(actions);
        self.complete_actions(&driver, affirm_actions).await
    }
    async fn make_webhook_test(
        &self,
        web_driver: WebDriver,
        payment_url: &str,
        actions: Vec<Event<'_>>,
        webhook_retry_time: u64,
        webhook_status: &str,
    ) -> Result<(), WebDriverError> {
        self.complete_actions(
            &web_driver,
            vec![Event::Trigger(Trigger::Goto(payment_url))],
        )
        .await?;
        self.complete_actions(&web_driver, actions).await?; //additional actions needs to make a payment
        self.complete_actions(
            &web_driver,
            vec![Event::Trigger(Trigger::Goto(&format!(
                "{CHECKOUT_BASE_URL}/events"
            )))],
        )
        .await?;
        let element = web_driver.query(By::Css("h2.last-payment")).first().await?;
        let payment_id = element.text().await?;
        let retries = 3; // no of retry times
        for _i in 0..retries {
            let configs = self.get_configs().automation_configs.unwrap();
            let outgoing_webhook_url = configs.hs_webhook_url.unwrap().to_string();
            let client = reqwest::Client::new();
            let response = client.get(outgoing_webhook_url).send().await.unwrap(); // get events from outgoing webhook endpoint
            let body_text = response.text().await.unwrap();
            let data: WebhookResponse = serde_json::from_str(&body_text).unwrap();
            let last_three_events = data.data.get(data.data.len().saturating_sub(3)..).unwrap(); // Get the last three elements if available
            for last_event in last_three_events {
                let last_event_body = &last_event.step.request.body;
                let decoded_bytes = base64::engine::general_purpose::STANDARD //decode the encoded outgoing webhook event
                    .decode(last_event_body)
                    .unwrap();
                let decoded_str = String::from_utf8(decoded_bytes).unwrap();
                let webhook_response: HsWebhookResponse =
                    serde_json::from_str(&decoded_str).unwrap();
                if payment_id == webhook_response.content.object.payment_id
                    && webhook_status == webhook_response.content.object.status
                {
                    return Ok(());
                }
            }
            self.complete_actions(
                &web_driver,
                vec![Event::Trigger(Trigger::Sleep(webhook_retry_time))],
            )
            .await?;
        }
        Err(WebDriverError::CustomError("Webhook Not Found".to_string()))
    }
    async fn make_paypal_payment(
        &self,
        web_driver: WebDriver,
        url: &str,
        actions: Vec<Event<'_>>,
    ) -> Result<(), WebDriverError> {
        let pypl_url = url.to_string();
        // To support failure retries
        let result = self
            .execute_paypal_steps(web_driver.clone(), &pypl_url, actions.clone())
            .await;
        if result.is_err() {
            self.execute_paypal_steps(web_driver.clone(), &pypl_url, actions.clone())
                .await
        } else {
            result
        }
    }
    async fn execute_paypal_steps(
        &self,
        web_driver: WebDriver,
        url: &str,
        actions: Vec<Event<'_>>,
    ) -> Result<(), WebDriverError> {
        self.complete_actions(
            &web_driver,
            vec![
                Event::Trigger(Trigger::Goto(url)),
                Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
            ],
        )
        .await?;
        let (email, pass) = (
            &self
                .get_configs()
                .automation_configs
                .unwrap()
                .pypl_email
                .unwrap(),
            &self
                .get_configs()
                .automation_configs
                .unwrap()
                .pypl_pass
                .unwrap(),
        );
        let mut pypl_actions = vec![
            Event::Trigger(Trigger::Sleep(8)),
            Event::RunIf(
                Assert::IsPresentNow("Enter your email address to get started"),
                vec![
                    Event::Trigger(Trigger::SendKeys(By::Id("email"), email)),
                    Event::Trigger(Trigger::Click(By::Id("btnNext"))),
                ],
            ),
            Event::RunIf(
                Assert::IsPresentNow("Password"),
                vec![
                    Event::Trigger(Trigger::SendKeys(By::Id("password"), pass)),
                    Event::Trigger(Trigger::Click(By::Id("btnLogin"))),
                ],
            ),
        ];
        pypl_actions.extend(actions);
        self.complete_actions(&web_driver, pypl_actions).await
    }
    async fn make_clearpay_payment(
        &self,
        driver: WebDriver,
        url: &str,
        actions: Vec<Event<'_>>,
    ) -> Result<(), WebDriverError> {
        self.complete_actions(
            &driver,
            vec![
                Event::Trigger(Trigger::Goto(url)),
                Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
                Event::Trigger(Trigger::Sleep(5)),
                Event::RunIf(
                    Assert::IsPresentNow("Manage Cookies"),
                    vec![
                        Event::Trigger(Trigger::Click(By::Css("button.cookie-setting-link"))),
                        Event::Trigger(Trigger::Click(By::Id("accept-recommended-btn-handler"))),
                    ],
                ),
            ],
        )
        .await?;
        let (email, pass) = (
            &self
                .get_configs()
                .automation_configs
                .unwrap()
                .clearpay_email
                .unwrap(),
            &self
                .get_configs()
                .automation_configs
                .unwrap()
                .clearpay_pass
                .unwrap(),
        );
        let mut clearpay_actions = vec![
            Event::Trigger(Trigger::Sleep(3)),
            Event::EitherOr(
                Assert::IsPresent("Please enter your password"),
                vec![
                    Event::Trigger(Trigger::SendKeys(By::Css("input[name='password']"), pass)),
                    Event::Trigger(Trigger::Click(By::Css("button[type='submit']"))),
                ],
                vec![
                    Event::Trigger(Trigger::SendKeys(
                        By::Css("input[name='identifier']"),
                        email,
                    )),
                    Event::Trigger(Trigger::Click(By::Css("button[type='submit']"))),
                    Event::Trigger(Trigger::Sleep(3)),
                    Event::Trigger(Trigger::SendKeys(By::Css("input[name='password']"), pass)),
                    Event::Trigger(Trigger::Click(By::Css("button[type='submit']"))),
                ],
            ),
            Event::Trigger(Trigger::Click(By::Css(
                "button[data-testid='summary-button']",
            ))),
        ];
        clearpay_actions.extend(actions);
        self.complete_actions(&driver, clearpay_actions).await
    }
}
async fn is_text_present_now(driver: &WebDriver, key: &str) -> WebDriverResult<bool> {
    let mut xpath = "//*[contains(text(),'".to_owned();
    xpath.push_str(key);
    xpath.push_str("')]");
    let result = driver.find(By::XPath(&xpath)).await?;
    let display: &str = &result.css_value("display").await?;
    if display.is_empty() || display == "none" {
        return Err(WebDriverError::CustomError("Element is hidden".to_string()));
    }
    result.is_present().await
}
async fn is_text_present(driver: &WebDriver, key: &str) -> WebDriverResult<bool> {
    let mut xpath = "//*[contains(text(),'".to_owned();
    xpath.push_str(key);
    xpath.push_str("')]");
    let result = driver.query(By::XPath(&xpath)).first().await?;
    result.is_present().await
}
async fn is_element_present(driver: &WebDriver, by: By) -> WebDriverResult<bool> {
    let element = driver.query(by).first().await?;
    element.is_present().await
}

#[macro_export]
macro_rules! tester_inner {
    ($execute:ident, $webdriver:expr) => {{
        use std::{
            sync::{Arc, Mutex},
            thread,
        };

        let driver = $webdriver;

        // we'll need the session_id from the thread
        // NOTE: even if it panics, so can't just return it
        let session_id = Arc::new(Mutex::new(None));

        // run test in its own thread to catch panics
        let sid = session_id.clone();
        let res = thread::spawn(move || {
            let runtime = tokio::runtime::Builder::new_current_thread()
                .enable_all()
                .build()
                .unwrap();
            let driver = runtime
                .block_on(driver)
                .expect("failed to construct test WebDriver");
            *sid.lock().unwrap() = runtime.block_on(driver.session_id()).ok();
            // make sure we close, even if an assertion fails
            let client = driver.clone();
            let x = runtime.block_on(async move {
                let run = tokio::spawn($execute(driver)).await;
                let _ = client.quit().await;
                run
            });
            drop(runtime);
            x.expect("test panicked")
        })
        .join();
        let success = handle_test_error(res);
        assert!(success);
    }};
}

#[macro_export]
macro_rules! function {
    () => {{
        fn f() {}
        fn type_name_of<T>(_: T) -> &'static str {
            std::any::type_name::<T>()
        }
        let name = type_name_of(f);
        &name.get(..name.len() - 3).unwrap()
    }};
}

#[macro_export]
macro_rules! tester {
    ($f:ident) => {{
        use $crate::{function, tester_inner};
        let test_name = format!("{:?}", function!());
        if (should_ignore_test(&test_name)) {
            return;
        }
        let browser = get_browser();
        let url = make_url(&browser);
        let caps = make_capabilities(&browser);
        tester_inner!($f, WebDriver::new(url, caps));
    }};
}

fn get_saved_testcases() -> serde_json::Value {
    let env_value = env::var("CONNECTOR_TESTS_FILE_PATH").ok();
    if env_value.is_none() {
        return serde_json::json!("");
    }
    let path = env_value.unwrap();
    let mut file = &std::fs::File::open(path).expect("Failed to open file");
    let mut contents = String::new();
    file.read_to_string(&mut contents)
        .expect("Failed to read file");

    // Parse the JSON data
    serde_json::from_str(&contents).expect("Failed to parse JSON")
}
fn get_configs() -> connector_auth::ConnectorAuthentication {
    let path =
        env::var("CONNECTOR_AUTH_FILE_PATH").expect("connector authentication file path not set");
    toml::from_str(
        &std::fs::read_to_string(path).expect("connector authentication config file not found"),
    )
    .expect("Failed to read connector authentication config file")
}

pub fn should_ignore_test(name: &str) -> bool {
    let conf = get_saved_testcases()
        .get("tests_to_ignore")
        .unwrap_or(&json!([]))
        .clone();
    let tests_to_ignore: HashSet<String> =
        serde_json::from_value(conf).unwrap_or_else(|_| HashSet::new());
    let modules: Vec<_> = name.split("::").collect();
    let file_match = format!(
        "{}::*",
        <&str>::clone(modules.get(1).expect("Error obtaining module path segment"))
    );
    let module_name = modules
        .get(1..3)
        .expect("Error obtaining module path segment")
        .join("::");
    // Ignore if it matches patterns like nuvei_ui::*, nuvei_ui::should_make_nuvei_eps_payment_test
    tests_to_ignore.contains(&file_match) || tests_to_ignore.contains(&module_name)
}

pub fn get_browser() -> String {
    "firefox".to_string()
}

// based on the browser settings build profile info
pub fn make_capabilities(browser: &str) -> Capabilities {
    match browser {
        "firefox" => {
            let mut caps = DesiredCapabilities::firefox();
            let ignore_profile = env::var("IGNORE_BROWSER_PROFILE").ok();
            if ignore_profile.is_none() || ignore_profile.unwrap() == "false" {
                let profile_path = &format!("-profile={}", get_firefox_profile_path().unwrap());
                caps.add_firefox_arg(profile_path).unwrap();
            } else {
                let profile_path = &format!("-profile={}", get_firefox_profile_path().unwrap());
                caps.add_firefox_arg(profile_path).unwrap();
                caps.add_firefox_arg("--headless").ok();
            }
            caps.into()
        }
        "chrome" => {
            let mut caps = DesiredCapabilities::chrome();
            let profile_path = &format!("user-data-dir={}", get_chrome_profile_path().unwrap());
            caps.add_chrome_arg(profile_path).unwrap();
            caps.into()
        }
        &_ => DesiredCapabilities::safari().into(),
    }
}

fn get_chrome_profile_path() -> Result<String, WebDriverError> {
    let exe = env::current_exe()?;
    let dir = exe.parent().expect("Executable must be in some directory");
    let mut base_path = dir
        .to_str()
        .map(|str| {
            let mut fp = str.split(MAIN_SEPARATOR).collect::<Vec<_>>();
            fp.truncate(3);
            fp.join(MAIN_SEPARATOR_STR)
        })
        .unwrap();
    if env::consts::OS == "macos" {
        base_path.push_str(r"/Library/Application\ Support/Google/Chrome/Default");
        //Issue: 1573
    } // We're only using Firefox on Ubuntu runner
    Ok(base_path)
}

fn get_firefox_profile_path() -> Result<String, WebDriverError> {
    let exe = env::current_exe()?;
    let dir = exe.parent().expect("Executable must be in some directory");
    let mut base_path = dir
        .to_str()
        .map(|str| {
            let mut fp = str.split(MAIN_SEPARATOR).collect::<Vec<_>>();
            fp.truncate(3);
            fp.join(MAIN_SEPARATOR_STR)
        })
        .unwrap();
    if env::consts::OS == "macos" {
        base_path.push_str(r#"/Library/Application Support/Firefox/Profiles/hs-test"#);
    //Issue: 1573
    } else if env::consts::OS == "linux" {
        if let Some(home_dir) = env::var_os("HOME") {
            if let Some(home_path) = home_dir.to_str() {
                let profile_path = format!("{home_path}/.mozilla/firefox/hs-test");
                return Ok(profile_path);
            }
        }
    }
    Ok(base_path)
}

pub fn make_url(browser: &str) -> &'static str {
    match browser {
        "firefox" => "http://localhost:4444",
        "chrome" => "http://localhost:9515",
        &_ => "",
    }
}

pub fn handle_test_error(
    res: Result<Result<(), WebDriverError>, Box<dyn std::any::Any + Send>>,
) -> bool {
    match res {
        Ok(Ok(_)) => true,
        Ok(Err(web_driver_error)) => {
            eprintln!("test future failed to resolve: {web_driver_error:?}");
            false
        }
        Err(e) => {
            if let Some(web_driver_error) = e.downcast_ref::<WebDriverError>() {
                eprintln!("test future panicked: {web_driver_error:?}");
            } else {
                eprintln!("test future panicked; an assertion probably failed");
            }
            false
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct WebhookResponse {
    data: Vec<WebhookResponseData>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct WebhookResponseData {
    step: WebhookRequestData,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct WebhookRequestData {
    request: WebhookRequest,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct WebhookRequest {
    body: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct HsWebhookResponse {
    content: HsWebhookContent,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct HsWebhookContent {
    object: HsWebhookObject,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct HsWebhookObject {
    payment_id: String,
    status: String,
    connector: String,
}


/// Get an organization id from String
    pub fn try_from_string(org_id: String) -> CustomResult<Self, ValidationError> {
        Self::try_from(std::borrow::Cow::from(org_id))
    }


File: crates/hyperswitch_connectors/src/connectors/gocardless/transformers.rs
 Public structs: 40

use common_enums::{enums, CountryAlpha2, UsStatesAbbreviation};
use common_utils::{
    id_type,
    pii::{self, IpAddress},
    types::MinorUnit,
};
use hyperswitch_domain_models::{
    address::AddressDetails,
    payment_method_data::{BankDebitData, PaymentMethodData},
    router_data::{ConnectorAuthType, PaymentMethodToken, RouterData},
    router_flow_types::refunds::Execute,
    router_request_types::{
        ConnectorCustomerData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
        PaymentsSyncData, ResponseId, SetupMandateRequestData,
    },
    router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData},
    types,
};
use hyperswitch_interfaces::errors;
use masking::{ExposeInterface, Secret};
use serde::{Deserialize, Serialize};

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{
        self, AddressDetailsData, BrowserInformationData, CustomerData, ForeignTryFrom,
        PaymentsAuthorizeRequestData, PaymentsSetupMandateRequestData, RouterData as _,
    },
};

pub struct GocardlessRouterData<T> {
    pub amount: MinorUnit,
    pub router_data: T,
}

impl<T> From<(MinorUnit, T)> for GocardlessRouterData<T> {
    fn from((amount, item): (MinorUnit, T)) -> Self {
        Self {
            amount,
            router_data: item,
        }
    }
}

#[derive(Default, Debug, Serialize)]
pub struct GocardlessCustomerRequest {
    customers: GocardlessCustomer,
}

#[derive(Default, Debug, Serialize)]
pub struct GocardlessCustomer {
    address_line1: Option<Secret<String>>,
    address_line2: Option<Secret<String>>,
    address_line3: Option<Secret<String>>,
    city: Option<Secret<String>>,
    region: Option<Secret<String>>,
    country_code: Option<CountryAlpha2>,
    email: pii::Email,
    given_name: Secret<String>,
    family_name: Secret<String>,
    metadata: CustomerMetaData,
    danish_identity_number: Option<Secret<String>>,
    postal_code: Option<Secret<String>>,
    swedish_identity_number: Option<Secret<String>>,
}

#[derive(Default, Debug, Serialize)]
pub struct CustomerMetaData {
    crm_id: Option<Secret<id_type::CustomerId>>,
}

impl TryFrom<&types::ConnectorCustomerRouterData> for GocardlessCustomerRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::ConnectorCustomerRouterData) -> Result<Self, Self::Error> {
        let email = item.request.get_email()?;
        let billing_details_name = item.get_billing_full_name()?.expose();

        let (given_name, family_name) = billing_details_name
            .trim()
            .rsplit_once(' ')
            .unwrap_or((&billing_details_name, &billing_details_name));

        let billing_address = item.get_billing_address()?;

        let metadata = CustomerMetaData {
            crm_id: item.customer_id.clone().map(Secret::new),
        };
        let region = get_region(billing_address)?;
        Ok(Self {
            customers: GocardlessCustomer {
                email,
                given_name: Secret::new(given_name.to_string()),
                family_name: Secret::new(family_name.to_string()),
                metadata,
                address_line1: billing_address.line1.to_owned(),
                address_line2: billing_address.line2.to_owned(),
                address_line3: billing_address.line3.to_owned(),
                country_code: billing_address.country,
                region,
                // Should be populated based on the billing country
                danish_identity_number: None,
                postal_code: billing_address.zip.to_owned(),
                // Should be populated based on the billing country
                swedish_identity_number: None,
                city: billing_address.city.clone().map(Secret::new),
            },
        })
    }
}

fn get_region(
    address_details: &AddressDetails,
) -> Result<Option<Secret<String>>, error_stack::Report<errors::ConnectorError>> {
    match address_details.country {
        Some(CountryAlpha2::US) => {
            let state = address_details.get_state()?.to_owned();
            Ok(Some(Secret::new(
                UsStatesAbbreviation::foreign_try_from(state.expose())?.to_string(),
            )))
        }
        _ => Ok(None),
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GocardlessCustomerResponse {
    customers: Customers,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Customers {
    id: Secret<String>,
}

impl<F>
    TryFrom<
        ResponseRouterData<
            F,
            GocardlessCustomerResponse,
            ConnectorCustomerData,
            PaymentsResponseData,
        >,
    > for RouterData<F, ConnectorCustomerData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            GocardlessCustomerResponse,
            ConnectorCustomerData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(PaymentsResponseData::ConnectorCustomerResponse {
                connector_customer_id: item.response.customers.id.expose(),
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
pub struct GocardlessBankAccountRequest {
    customer_bank_accounts: CustomerBankAccounts,
}

#[derive(Debug, Serialize)]
pub struct CustomerBankAccounts {
    #[serde(flatten)]
    accounts: CustomerBankAccount,
    links: CustomerAccountLink,
}

#[derive(Debug, Serialize)]
pub struct CustomerAccountLink {
    customer: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum CustomerBankAccount {
    InternationalBankAccount(InternationalBankAccount),
    AUBankAccount(AUBankAccount),
    USBankAccount(USBankAccount),
}

#[derive(Debug, Serialize)]
pub struct InternationalBankAccount {
    iban: Secret<String>,
    account_holder_name: Secret<String>,
}

#[derive(Debug, Serialize)]
pub struct AUBankAccount {
    country_code: CountryAlpha2,
    account_number: Secret<String>,
    branch_code: Secret<String>,
    account_holder_name: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub struct USBankAccount {
    country_code: CountryAlpha2,
    account_number: Secret<String>,
    bank_code: Secret<String>,
    account_type: AccountType,
    account_holder_name: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum AccountType {
    Checking,
    Savings,
}

impl TryFrom<&types::TokenizationRouterData> for GocardlessBankAccountRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::TokenizationRouterData) -> Result<Self, Self::Error> {
        let customer = item.get_connector_customer_id()?;
        let accounts = CustomerBankAccount::try_from(item)?;
        let links = CustomerAccountLink {
            customer: Secret::new(customer),
        };
        Ok(Self {
            customer_bank_accounts: CustomerBankAccounts { accounts, links },
        })
    }
}

impl TryFrom<&types::TokenizationRouterData> for CustomerBankAccount {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::TokenizationRouterData) -> Result<Self, Self::Error> {
        match &item.request.payment_method_data {
            PaymentMethodData::BankDebit(bank_debit_data) => {
                Self::try_from((bank_debit_data, item))
            }
            PaymentMethodData::Card(_)
            | PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::Wallet(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::BankTransfer(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Gocardless"),
                )
                .into())
            }
        }
    }
}

impl TryFrom<(&BankDebitData, &types::TokenizationRouterData)> for CustomerBankAccount {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (bank_debit_data, item): (&BankDebitData, &types::TokenizationRouterData),
    ) -> Result<Self, Self::Error> {
        match bank_debit_data {
            BankDebitData::AchBankDebit {
                account_number,
                routing_number,
                bank_type,
                ..
            } => {
                let bank_type = bank_type.ok_or_else(utils::missing_field_err("bank_type"))?;
                let country_code = item.get_billing_country()?;
                let account_holder_name = item.get_billing_full_name()?;
                let us_bank_account = USBankAccount {
                    country_code,
                    account_number: account_number.clone(),
                    bank_code: routing_number.clone(),
                    account_type: AccountType::from(bank_type),
                    account_holder_name,
                };
                Ok(Self::USBankAccount(us_bank_account))
            }
            BankDebitData::BecsBankDebit {
                account_number,
                bsb_number,
                ..
            } => {
                let country_code = item.get_billing_country()?;
                let account_holder_name = item.get_billing_full_name()?;
                let au_bank_account = AUBankAccount {
                    country_code,
                    account_number: account_number.clone(),
                    branch_code: bsb_number.clone(),
                    account_holder_name,
                };
                Ok(Self::AUBankAccount(au_bank_account))
            }
            BankDebitData::SepaBankDebit { iban, .. } => {
                let account_holder_name = item.get_billing_full_name()?;
                let international_bank_account = InternationalBankAccount {
                    iban: iban.clone(),
                    account_holder_name,
                };
                Ok(Self::InternationalBankAccount(international_bank_account))
            }
            BankDebitData::BacsBankDebit { .. } => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Gocardless"),
            )
            .into()),
        }
    }
}

impl From<common_enums::BankType> for AccountType {
    fn from(item: common_enums::BankType) -> Self {
        match item {
            common_enums::BankType::Checking => Self::Checking,
            common_enums::BankType::Savings => Self::Savings,
        }
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GocardlessBankAccountResponse {
    customer_bank_accounts: CustomerBankAccountResponse,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CustomerBankAccountResponse {
    pub id: Secret<String>,
}

impl<F>
    TryFrom<
        ResponseRouterData<
            F,
            GocardlessBankAccountResponse,
            PaymentMethodTokenizationData,
            PaymentsResponseData,
        >,
    > for RouterData<F, PaymentMethodTokenizationData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            GocardlessBankAccountResponse,
            PaymentMethodTokenizationData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(PaymentsResponseData::TokenizationResponse {
                token: item.response.customer_bank_accounts.id.expose(),
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
pub struct GocardlessMandateRequest {
    mandates: Mandate,
}

#[derive(Debug, Serialize)]
pub struct Mandate {
    scheme: GocardlessScheme,
    metadata: MandateMetaData,
    payer_ip_address: Option<Secret<String, IpAddress>>,
    links: MandateLink,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum GocardlessScheme {
    Becs,
    SepaCore,
    Ach,
    BecsNz,
}

#[derive(Debug, Serialize)]
pub struct MandateMetaData {
    payment_reference: String,
}

#[derive(Debug, Serialize)]
pub struct MandateLink {
    customer_bank_account: Secret<String>,
}

impl TryFrom<&types::SetupMandateRouterData> for GocardlessMandateRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::SetupMandateRouterData) -> Result<Self, Self::Error> {
        let (scheme, payer_ip_address) = match &item.request.payment_method_data {
            PaymentMethodData::BankDebit(bank_debit_data) => {
                let payer_ip_address = get_ip_if_required(bank_debit_data, item)?;
                Ok((
                    GocardlessScheme::try_from(bank_debit_data)?,
                    payer_ip_address,
                ))
            }
            PaymentMethodData::Card(_)
            | PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::Wallet(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::BankTransfer(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    "Setup Mandate flow for selected payment method through Gocardless".to_string(),
                ))
            }
        }?;
        let payment_method_token = item.get_payment_method_token()?;
        let customer_bank_account = match payment_method_token {
            PaymentMethodToken::Token(token) => Ok(token),
            PaymentMethodToken::ApplePayDecrypt(_)
            | PaymentMethodToken::PazeDecrypt(_)
            | PaymentMethodToken::GooglePayDecrypt(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    "Setup Mandate flow for selected payment method through Gocardless".to_string(),
                ))
            }
        }?;
        Ok(Self {
            mandates: Mandate {
                scheme,
                metadata: MandateMetaData {
                    payment_reference: item.connector_request_reference_id.clone(),
                },
                payer_ip_address,
                links: MandateLink {
                    customer_bank_account,
                },
            },
        })
    }
}

fn get_ip_if_required(
    bank_debit_data: &BankDebitData,
    item: &types::SetupMandateRouterData,
) -> Result<Option<Secret<String, IpAddress>>, error_stack::Report<errors::ConnectorError>> {
    let ip_address = item.request.get_browser_info()?.get_ip_address()?;
    match bank_debit_data {
        BankDebitData::AchBankDebit { .. } => Ok(Some(ip_address)),
        BankDebitData::SepaBankDebit { .. }
        | BankDebitData::BecsBankDebit { .. }
        | BankDebitData::BacsBankDebit { .. } => Ok(None),
    }
}

impl TryFrom<&BankDebitData> for GocardlessScheme {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &BankDebitData) -> Result<Self, Self::Error> {
        match item {
            BankDebitData::AchBankDebit { .. } => Ok(Self::Ach),
            BankDebitData::SepaBankDebit { .. } => Ok(Self::SepaCore),
            BankDebitData::BecsBankDebit { .. } => Ok(Self::Becs),
            BankDebitData::BacsBankDebit { .. } => Err(errors::ConnectorError::NotImplemented(
                "Setup Mandate flow for selected payment method through Gocardless".to_string(),
            )
            .into()),
        }
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GocardlessMandateResponse {
    mandates: MandateResponse,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MandateResponse {
    id: Secret<String>,
}

impl<F>
    TryFrom<
        ResponseRouterData<
            F,
            GocardlessMandateResponse,
            SetupMandateRequestData,
            PaymentsResponseData,
        >,
    > for RouterData<F, SetupMandateRequestData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            GocardlessMandateResponse,
            SetupMandateRequestData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let mandate_reference = Some(MandateReference {
            connector_mandate_id: Some(item.response.mandates.id.clone().expose()),
            payment_method_id: None,
            mandate_metadata: None,
            connector_mandate_request_reference_id: None,
        });
        Ok(Self {
            response: Ok(PaymentsResponseData::TransactionResponse {
                connector_metadata: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                resource_id: ResponseId::NoResponseId,
                redirection_data: Box::new(None),
                mandate_reference: Box::new(mandate_reference),
                network_txn_id: None,
                charges: None,
            }),
            status: enums::AttemptStatus::Charged,
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
pub struct GocardlessPaymentsRequest {
    payments: GocardlessPayment,
}

#[derive(Debug, Serialize)]
pub struct GocardlessPayment {
    amount: MinorUnit,
    currency: enums::Currency,
    description: Option<String>,
    metadata: PaymentMetaData,
    links: PaymentLink,
}

#[derive(Debug, Serialize)]
pub struct PaymentMetaData {
    payment_reference: String,
}

#[derive(Debug, Serialize)]
pub struct PaymentLink {
    mandate: Secret<String>,
}

impl TryFrom<&GocardlessRouterData<&types::PaymentsAuthorizeRouterData>>
    for GocardlessPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &GocardlessRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let mandate_id = if item.router_data.request.is_mandate_payment() {
            item.router_data
                .request
                .connector_mandate_id()
                .ok_or_else(utils::missing_field_err("mandate_id"))
        } else {
            Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("gocardless"),
            )
            .into())
        }?;
        let payments = GocardlessPayment {
            amount: item.router_data.request.minor_amount,
            currency: item.router_data.request.currency,
            description: item.router_data.description.clone(),
            metadata: PaymentMetaData {
                payment_reference: item.router_data.connector_request_reference_id.clone(),
            },
            links: PaymentLink {
                mandate: Secret::new(mandate_id),
            },
        };
        Ok(Self { payments })
    }
}

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

impl TryFrom<&ConnectorAuthType> for GocardlessAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
                access_token: api_key.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GocardlessPaymentStatus {
    PendingCustomerApproval,
    PendingSubmission,
    Submitted,
    Confirmed,
    PaidOut,
    Cancelled,
    CustomerApprovalDenied,
    Failed,
}

impl From<GocardlessPaymentStatus> for enums::AttemptStatus {
    fn from(item: GocardlessPaymentStatus) -> Self {
        match item {
            GocardlessPaymentStatus::PendingCustomerApproval
            | GocardlessPaymentStatus::PendingSubmission
            | GocardlessPaymentStatus::Submitted => Self::Pending,
            GocardlessPaymentStatus::Confirmed | GocardlessPaymentStatus::PaidOut => Self::Charged,
            GocardlessPaymentStatus::Cancelled => Self::Voided,
            GocardlessPaymentStatus::CustomerApprovalDenied => Self::AuthenticationFailed,
            GocardlessPaymentStatus::Failed => Self::Failure,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GocardlessPaymentsResponse {
    payments: PaymentResponse,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentResponse {
    status: GocardlessPaymentStatus,
    id: String,
}

impl<F>
    TryFrom<
        ResponseRouterData<
            F,
            GocardlessPaymentsResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    > for RouterData<F, PaymentsAuthorizeData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            GocardlessPaymentsResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let mandate_reference = MandateReference {
            connector_mandate_id: Some(item.data.request.get_connector_mandate_id()?),
            payment_method_id: None,
            mandate_metadata: None,
            connector_mandate_request_reference_id: None,
        };
        Ok(Self {
            status: enums::AttemptStatus::from(item.response.payments.status),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.payments.id),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(Some(mandate_reference)),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

impl<F>
    TryFrom<
        ResponseRouterData<F, GocardlessPaymentsResponse, PaymentsSyncData, PaymentsResponseData>,
    > for RouterData<F, PaymentsSyncData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            GocardlessPaymentsResponse,
            PaymentsSyncData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: enums::AttemptStatus::from(item.response.payments.status),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.payments.id),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

// REFUND :
#[derive(Default, Debug, Serialize)]
pub struct GocardlessRefundRequest {
    refunds: GocardlessRefund,
}

#[derive(Default, Debug, Serialize)]
pub struct GocardlessRefund {
    amount: MinorUnit,
    metadata: RefundMetaData,
    links: RefundLink,
}

#[derive(Default, Debug, Serialize)]
pub struct RefundMetaData {
    refund_reference: String,
}

#[derive(Default, Debug, Serialize)]
pub struct RefundLink {
    payment: String,
}

impl<F> TryFrom<&GocardlessRouterData<&types::RefundsRouterData<F>>> for GocardlessRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &GocardlessRouterData<&types::RefundsRouterData<F>>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            refunds: GocardlessRefund {
                amount: item.amount.to_owned(),
                metadata: RefundMetaData {
                    refund_reference: item.router_data.connector_request_reference_id.clone(),
                },
                links: RefundLink {
                    payment: item.router_data.request.connector_transaction_id.clone(),
                },
            },
        })
    }
}

#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct RefundResponse {
    id: String,
}

impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>>
    for types::RefundsRouterData<Execute>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.to_string(),
                refund_status: enums::RefundStatus::Pending,
            }),
            ..item.data
        })
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct GocardlessErrorResponse {
    pub error: GocardlessError,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct GocardlessError {
    pub message: String,
    pub code: u16,
    pub errors: Vec<Error>,
    #[serde(rename = "type")]
    pub error_type: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Error {
    pub field: Option<String>,
    pub message: String,
}

#[derive(Debug, Deserialize)]
pub struct GocardlessWebhookEvent {
    pub events: Vec<WebhookEvent>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WebhookEvent {
    pub resource_type: WebhookResourceType,
    pub action: WebhookAction,
    pub links: WebhooksLink,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WebhookResourceType {
    Payments,
    Refunds,
    Mandates,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum WebhookAction {
    PaymentsAction(PaymentsAction),
    RefundsAction(RefundsAction),
    MandatesAction(MandatesAction),
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PaymentsAction {
    Created,
    CustomerApprovalGranted,
    CustomerApprovalDenied,
    Submitted,
    Confirmed,
    PaidOut,
    LateFailureSettled,
    SurchargeFeeDebited,
    Failed,
    Cancelled,
    ResubmissionRequired,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RefundsAction {
    Created,
    Failed,
    Paid,
    // Payout statuses
    RefundSettled,
    FundsReturned,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MandatesAction {
    Created,
    CustomerApprovalGranted,
    CustomerApprovalSkipped,
    Active,
    Cancelled,
    Failed,
    Transferred,
    Expired,
    Submitted,
    ResubmissionRequested,
    Reinstated,
    Replaced,
    Consumed,
    Blocked,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum WebhooksLink {
    PaymentWebhooksLink(PaymentWebhooksLink),
    RefundWebhookLink(RefundWebhookLink),
    MandateWebhookLink(MandateWebhookLink),
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RefundWebhookLink {
    pub refund: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PaymentWebhooksLink {
    pub payment: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MandateWebhookLink {
    pub mandate: String,
}

impl TryFrom<&WebhookEvent> for GocardlessPaymentsResponse {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &WebhookEvent) -> Result<Self, Self::Error> {
        let id = match &item.links {
            WebhooksLink::PaymentWebhooksLink(link) => link.payment.to_owned(),
            WebhooksLink::RefundWebhookLink(_) | WebhooksLink::MandateWebhookLink(_) => {
                Err(errors::ConnectorError::WebhookEventTypeNotFound)?
            }
        };
        Ok(Self {
            payments: PaymentResponse {
                status: GocardlessPaymentStatus::try_from(&item.action)?,
                id,
            },
        })
    }
}

impl TryFrom<&WebhookAction> for GocardlessPaymentStatus {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &WebhookAction) -> Result<Self, Self::Error> {
        match item {
            WebhookAction::PaymentsAction(action) => match action {
                PaymentsAction::CustomerApprovalGranted | PaymentsAction::Submitted => {
                    Ok(Self::Submitted)
                }
                PaymentsAction::CustomerApprovalDenied => Ok(Self::CustomerApprovalDenied),
                PaymentsAction::LateFailureSettled => Ok(Self::Failed),
                PaymentsAction::Failed => Ok(Self::Failed),
                PaymentsAction::Cancelled => Ok(Self::Cancelled),
                PaymentsAction::Confirmed => Ok(Self::Confirmed),
                PaymentsAction::PaidOut => Ok(Self::PaidOut),
                PaymentsAction::SurchargeFeeDebited
                | PaymentsAction::ResubmissionRequired
                | PaymentsAction::Created => Err(errors::ConnectorError::WebhookEventTypeNotFound)?,
            },
            WebhookAction::RefundsAction(_) | WebhookAction::MandatesAction(_) => {
                Err(errors::ConnectorError::WebhookEventTypeNotFound)?
            }
        }
    }
}


pub struct InterpreterError {
    pub error_type: InterpreterErrorType,
    pub metadata: HashMap<String, serde_json::Value>,
}


pub struct AccountInformation {
    pub entity_id: String,
    pub balance: MinorUnit,
    pub currency: Currency,
    pub status: AccountStatus,
}


pub struct ScheduledToken {
    pub token_details: PaymentProcessorTokenDetails,
    pub schedule_time: time::PrimitiveDateTime,
}


pub struct PartnerFlowCredentials {
    pub(super) client_id: Secret<String>,
    pub(super) client_secret: Secret<String>,
    pub(super) payer_id: Secret<String>,
}


impl api::PaymentCapture for Gpayments {}

impl api::PaymentCapture for for Gpayments


/// get_poll_id
    pub fn get_poll_id(&self, unique_id: &str) -> String {
        format!("poll_{}_{unique_id}", self.get_string_repr())
    }


pub async fn get_frm_filter_for_dimension<T>(
    dimension: FrmDimensions,
    merchant_id: &common_utils::id_type::MerchantId,
    time_range: &TimeRange,
    pool: &T,
) -> FiltersResult<Vec<FrmFilterRow>>
where
    T: AnalyticsDataSource + FrmFilterAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    let mut query_builder: QueryBuilder<T> = QueryBuilder::new(AnalyticsCollection::FraudCheck);

    query_builder.add_select_column(dimension).switch()?;
    time_range
        .set_filter_clause(&mut query_builder)
        .attach_printable("Error filtering time range")
        .switch()?;

    query_builder
        .add_filter_clause("merchant_id", merchant_id)
        .switch()?;

    query_builder.set_distinct();

    query_builder
        .execute_query::<FrmFilterRow, _>(pool)
        .await
        .change_context(FiltersError::QueryBuildingError)?
        .change_context(FiltersError::QueryExecutionFailure)
}


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

pub mod transformers;

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

use crate::{constants::headers, types::ResponseRouterData, utils};

#[derive(Clone)]
pub struct Thunes {
    amount_converter: &'static (dyn AmountConvertor<Output = StringMinorUnit> + Sync),
}

impl Thunes {
    pub fn new() -> &'static Self {
        &Self {
            amount_converter: &StringMinorUnitForConnector,
        }
    }
}

impl api::Payment for Thunes {}
impl api::PaymentSession for Thunes {}
impl api::ConnectorAccessToken for Thunes {}
impl api::MandateSetup for Thunes {}
impl api::PaymentAuthorize for Thunes {}
impl api::PaymentSync for Thunes {}
impl api::PaymentCapture for Thunes {}
impl api::PaymentVoid for Thunes {}
impl api::Refund for Thunes {}
impl api::RefundExecute for Thunes {}
impl api::RefundSync for Thunes {}
impl api::PaymentToken for Thunes {}

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
    for Thunes
{
    // Not Implemented (R)
}

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

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

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

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

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

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

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

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

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

impl ConnectorValidation for Thunes {
    //TODO: implement functions when support enabled
}

impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Thunes {
    //TODO: implement sessions flow
}

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

impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData> for Thunes {}

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

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

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

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

        let connector_router_data = thunes::ThunesRouterData::from((amount, req));
        let connector_req = thunes::ThunesPaymentsRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

    fn handle_response(
        &self,
        data: &PaymentsAuthorizeRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsAuthorizeRouterData, errors::ConnectorError> {
        let response: thunes::ThunesPaymentsResponse = res
            .response
            .parse_struct("Thunes PaymentsAuthorizeResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

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

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

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

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

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

    fn handle_response(
        &self,
        data: &PaymentsSyncRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsSyncRouterData, errors::ConnectorError> {
        let response: thunes::ThunesPaymentsResponse = res
            .response
            .parse_struct("thunes PaymentsSyncResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

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

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

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

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

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

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

    fn handle_response(
        &self,
        data: &PaymentsCaptureRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsCaptureRouterData, errors::ConnectorError> {
        let response: thunes::ThunesPaymentsResponse = res
            .response
            .parse_struct("Thunes PaymentsCaptureResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

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

impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Thunes {}

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

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

    fn get_url(
        &self,
        _req: &RefundsRouterData<Execute>,
        _connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
    }

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

        let connector_router_data = thunes::ThunesRouterData::from((refund_amount, req));
        let connector_req = thunes::ThunesRefundRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

    fn handle_response(
        &self,
        data: &RefundsRouterData<Execute>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<RefundsRouterData<Execute>, errors::ConnectorError> {
        let response: thunes::RefundResponse =
            res.response
                .parse_struct("thunes RefundResponse")
                .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

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

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

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

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

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

    fn handle_response(
        &self,
        data: &RefundSyncRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<RefundSyncRouterData, errors::ConnectorError> {
        let response: thunes::RefundResponse = res
            .response
            .parse_struct("thunes RefundSyncResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

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

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

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

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

static THUNES_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
    display_name: "Thunes",
    description: "Thunes Payouts is a global payment solution that enables businesses to send instant, secure, and cost-effective cross-border payments to bank accounts, mobile wallets, and cards in over 130 countries using a single API",
    connector_type: common_enums::HyperswitchConnectorCategory::PayoutProcessor,
    integration_status: common_enums::ConnectorIntegrationStatus::Sandbox,
};

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

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        None
    }

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


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

use error_stack::ResultExt;

use crate::errors;

/// A global id that can be used to identify a refund
#[derive(
    Debug,
    Clone,
    Hash,
    PartialEq,
    Eq,
    serde::Serialize,
    serde::Deserialize,
    diesel::expression::AsExpression,
)]
#[diesel(sql_type = diesel::sql_types::Text)]
pub struct GlobalRefundId(super::GlobalId);

// Database related implementations so that this field can be used directly in the database tables
crate::impl_queryable_id_type!(GlobalRefundId);

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

    /// Generate a new GlobalRefundId from a cell id
    pub fn generate(cell_id: &crate::id_type::CellId) -> Self {
        let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Refund);
        Self(global_id)
    }
}

// TODO: refactor the macro to include this id use case as well
impl TryFrom<std::borrow::Cow<'static, str>> for GlobalRefundId {
    type Error = error_stack::Report<errors::ValidationError>;
    fn try_from(value: std::borrow::Cow<'static, str>) -> Result<Self, Self::Error> {
        let merchant_ref_id = super::GlobalId::from_string(value).change_context(
            errors::ValidationError::IncorrectValueProvided {
                field_name: "refund_id",
            },
        )?;
        Ok(Self(merchant_ref_id))
    }
}

// TODO: refactor the macro to include this id use case as well
impl<DB> diesel::serialize::ToSql<diesel::sql_types::Text, DB> for GlobalRefundId
where
    DB: diesel::backend::Backend,
    super::GlobalId: diesel::serialize::ToSql<diesel::sql_types::Text, DB>,
{
    fn to_sql<'b>(
        &'b self,
        out: &mut diesel::serialize::Output<'b, '_, DB>,
    ) -> diesel::serialize::Result {
        self.0.to_sql(out)
    }
}

impl<DB> diesel::deserialize::FromSql<diesel::sql_types::Text, DB> for GlobalRefundId
where
    DB: diesel::backend::Backend,
    super::GlobalId: diesel::deserialize::FromSql<diesel::sql_types::Text, DB>,
{
    fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
        super::GlobalId::from_sql(value).map(Self)
    }
}


Web Documentation: Customizable and Native Integrations | Hyperswitch
# Type: Web Doc

How to get started?
Pick the platform / framework of your choice to get started with integrating the client SDK on your application
Hyperswitch Web
Hyperswitch Android
Hyperswitch iOS
Hyperswitch React Native
Hyperswitch Flutter
Payment Features
Server Setup
Modify and Experiment
Hyperswitch Unified Checkout is an inclusive, consistent and blended payment experience optimized for the best conversion rates.
Inclusive
Global payments, diverse methods - cards, buy now pay later, and digital wallets. Unified Checkout adapts to local preferences, integrates languages for an inclusive solution.
Consistent
Consistent payment experience on Web, Android, and iOS with smart forms, minimal redirects, and intelligent retries. Unified Checkout ensures reliability and uniformity.
Blended
Tailor payment seamlessly with 40+ styling APIs for a native, cohesive, branded checkout in your Android app or website.
While the Unified Checkout is pre-optimized for maximum conversions, Hyperswitch does not restrict you to stick to a one-size-fits-all approach. Using Hyperswitch SDK APIs, you get complete control over modifying the payment experience by,
Including new fields to the payment form:
Quickly adapting to use cases such as collecting billing addresses, or zip code as additional information, since you will have complete control of your payment experience.
Prioritizing payment methods:
You can make an impact on the payment mix or conversion rates by prioritizing/ promoting specific payment methods for your customers.
Switching themes and layouts of checkout page:
The Unified Checkout comes with a wide range of pre-designed themes and layouts which you can choose from.
3 months ago
Was this helpful?


pub struct AciPaymentsRequest {
    #[serde(flatten)]
    pub txn_details: TransactionDetails,
    #[serde(flatten)]
    pub payment_method: PaymentDetails,
    #[serde(flatten)]
    pub instruction: Option<Instruction>,
    pub shopper_result_url: Option<String>,
}


pub struct MandateWebhookLink {
    pub mandate: String,
}


Documentation: api-reference/intelligent-router-api-reference/static-routing/evaluate-routing-rule.mdx
# Type: Doc File

---
openapi: post /euclid.EuclidService/Evaluate
---


pub struct CybersoucrePaymentInstrument {
    id: Secret<String>,
}


File: crates/payment_methods/src/lib.rs

pub mod configs;
pub mod controller;
pub mod core;
pub mod helpers;
pub mod state;


impl ConnectorRedirectResponse for Bluesnap {
    fn get_flow_type(
        &self,
        _query_params: &str,
        json_payload: Option<serde_json::Value>,
        action: PaymentAction,
    ) -> CustomResult<CallConnectorAction, errors::ConnectorError> {
        match action {
            PaymentAction::PSync | PaymentAction::PaymentAuthenticateCompleteAuthorize => {
                Ok(CallConnectorAction::Trigger)
            }
            PaymentAction::CompleteAuthorize => {
                let redirection_response: bluesnap::BluesnapRedirectionResponse = json_payload
                    .ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload {
                        field_name: "json_payload",
                    })?
                    .parse_value("BluesnapRedirectionResponse")
                    .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

                let redirection_result: bluesnap::BluesnapThreeDsResult = redirection_response
                    .authentication_response
                    .parse_struct("BluesnapThreeDsResult")
                    .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

                match redirection_result.status.as_str() {
                    "Success" => Ok(CallConnectorAction::Trigger),
                    _ => Ok(CallConnectorAction::StatusUpdate {
                        status: enums::AttemptStatus::AuthenticationFailed,
                        error_code: redirection_result.code,
                        error_message: redirection_result
                            .info
                            .as_ref()
                            .and_then(|info| info.errors.as_ref().and_then(|error| error.first()))
                            .cloned(),
                    }),
                }
            }
        }
    }
}

impl ConnectorRedirectResponse for for Bluesnap


impl GenericLinkNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<GenericLinkState> {
        generics::generic_insert(conn, self)
            .await
            .and_then(|res: GenericLink| {
                GenericLinkState::try_from(res)
                    .change_context(db_errors::DatabaseError::Others)
                    .attach_printable("failed to parse generic link data from DB")
            })
    }

    pub async fn insert_pm_collect_link(
        self,
        conn: &PgPooledConn,
    ) -> StorageResult<PaymentMethodCollectLink> {
        generics::generic_insert(conn, self)
            .await
            .and_then(|res: GenericLink| {
                PaymentMethodCollectLink::try_from(res)
                    .change_context(db_errors::DatabaseError::Others)
                    .attach_printable("failed to parse payment method collect link data from DB")
            })
    }

    pub async fn insert_payout_link(self, conn: &PgPooledConn) -> StorageResult<PayoutLink> {
        generics::generic_insert(conn, self)
            .await
            .and_then(|res: GenericLink| {
                PayoutLink::try_from(res)
                    .change_context(db_errors::DatabaseError::Others)
                    .attach_printable("failed to parse payout link data from DB")
            })
    }
}