Bug: [FEATURE] Add Bank redirects for Paypal payment processor



Add Bank redirect support for Paypal
diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs
index 0d1b3f8f9b6..248525fcc40 100644
--- a/crates/api_models/src/payment_methods.rs
+++ b/crates/api_models/src/payment_methods.rs
@@ -665,3 +665,13 @@ pub struct TokenizedBankTransferValue1 {
 pub struct TokenizedBankTransferValue2 {
     pub customer_id: Option<String>,
 }
+
+#[derive(Debug, serde::Serialize, serde::Deserialize)]
+pub struct TokenizedBankRedirectValue1 {
+    pub data: payments::BankRedirectData,
+}
+
+#[derive(Debug, serde::Serialize, serde::Deserialize)]
+pub struct TokenizedBankRedirectValue2 {
+    pub customer_id: Option<String>,
+}
diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs
index 892590e58ac..5f5294589a5 100644
--- a/crates/api_models/src/payments.rs
+++ b/crates/api_models/src/payments.rs
@@ -803,6 +803,10 @@ pub enum BankRedirectData {
         /// The hyperswitch bank code for eps
         #[schema(value_type = BankNames, example = "triodos_bank")]
         bank_name: Option<api_enums::BankNames>,
+
+        /// The country for bank payment
+        #[schema(value_type = CountryAlpha2, example = "US")]
+        country: Option<api_enums::CountryAlpha2>,
     },
     Giropay {
         /// The billing details for bank redirection
@@ -816,6 +820,10 @@ pub enum BankRedirectData {
         /// Bank account iban
         #[schema(value_type = Option<String>)]
         bank_account_iban: Option<Secret<String>>,
+
+        /// The country for bank payment
+        #[schema(value_type = CountryAlpha2, example = "US")]
+        country: Option<api_enums::CountryAlpha2>,
     },
     Ideal {
         /// The billing details for bank redirection
@@ -824,6 +832,10 @@ pub enum BankRedirectData {
         /// The hyperswitch bank code for ideal
         #[schema(value_type = BankNames, example = "abn_amro")]
         bank_name: Option<api_enums::BankNames>,
+
+        /// The country for bank payment
+        #[schema(value_type = CountryAlpha2, example = "US")]
+        country: Option<api_enums::CountryAlpha2>,
     },
     Interac {
         /// The country for bank payment
diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs
index ec74a8f0214..4c11529f164 100644
--- a/crates/router/src/connector/paypal.rs
+++ b/crates/router/src/connector/paypal.rs
@@ -54,7 +54,8 @@ impl Paypal {
         connector_meta: &Option<serde_json::Value>,
     ) -> CustomResult<Option<String>, errors::ConnectorError> {
         match payment_method {
-            Some(diesel_models::enums::PaymentMethod::Wallet) => {
+            Some(diesel_models::enums::PaymentMethod::Wallet)
+            | Some(diesel_models::enums::PaymentMethod::BankRedirect) => {
                 let meta: PaypalMeta = to_connector_meta(connector_meta.clone())?;
                 Ok(Some(meta.order_id))
             }
@@ -384,7 +385,8 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
         res: Response,
     ) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
         match data.payment_method {
-            diesel_models::enums::PaymentMethod::Wallet => {
+            diesel_models::enums::PaymentMethod::Wallet
+            | diesel_models::enums::PaymentMethod::BankRedirect => {
                 let response: paypal::PaypalRedirectResponse = res
                     .response
                     .parse_struct("paypal PaymentsRedirectResponse")
@@ -516,7 +518,8 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
     ) -> CustomResult<String, errors::ConnectorError> {
         let paypal_meta: PaypalMeta = to_connector_meta(req.request.connector_meta.clone())?;
         match req.payment_method {
-            diesel_models::enums::PaymentMethod::Wallet => Ok(format!(
+            diesel_models::enums::PaymentMethod::Wallet
+            | diesel_models::enums::PaymentMethod::BankRedirect => Ok(format!(
                 "{}v2/checkout/orders/{}",
                 self.base_url(connectors),
                 paypal_meta.order_id
@@ -561,7 +564,8 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
         res: Response,
     ) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
         match data.payment_method {
-            diesel_models::enums::PaymentMethod::Wallet => {
+            diesel_models::enums::PaymentMethod::Wallet
+            | diesel_models::enums::PaymentMethod::BankRedirect => {
                 let response: paypal::PaypalOrdersResponse = res
                     .response
                     .parse_struct("paypal PaymentsOrderResponse")
diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs
index 622651aa9a5..4852016bfe1 100644
--- a/crates/router/src/connector/paypal/transformers.rs
+++ b/crates/router/src/connector/paypal/transformers.rs
@@ -1,3 +1,4 @@
+use api_models::payments::BankRedirectData;
 use common_utils::errors::CustomResult;
 use masking::Secret;
 use serde::{Deserialize, Serialize};
@@ -5,8 +6,8 @@ use url::Url;
 
 use crate::{
     connector::utils::{
-        self, to_connector_meta, AccessTokenRequestInfo, AddressDetailsData, CardData,
-        PaymentsAuthorizeRequestData,
+        self, to_connector_meta, AccessTokenRequestInfo, AddressDetailsData,
+        BankRedirectBillingData, CardData, PaymentsAuthorizeRequestData,
     },
     core::errors,
     services,
@@ -58,6 +59,7 @@ pub struct RedirectRequest {
 #[derive(Debug, Serialize)]
 pub struct ContextStruct {
     return_url: Option<String>,
+    cancel_url: Option<String>,
 }
 
 #[derive(Debug, Serialize)]
@@ -70,6 +72,10 @@ pub struct PaypalRedirectionRequest {
 pub enum PaymentSourceItem {
     Card(CardRequest),
     Paypal(PaypalRedirectionRequest),
+    IDeal(RedirectRequest),
+    Eps(RedirectRequest),
+    Giropay(RedirectRequest),
+    Sofort(RedirectRequest),
 }
 
 #[derive(Debug, Serialize)]
@@ -93,6 +99,68 @@ fn get_address_info(
     };
     Ok(address)
 }
+fn get_payment_source(
+    item: &types::PaymentsAuthorizeRouterData,
+    bank_redirection_data: &BankRedirectData,
+) -> Result<PaymentSourceItem, error_stack::Report<errors::ConnectorError>> {
+    match bank_redirection_data {
+        BankRedirectData::Eps {
+            billing_details,
+            bank_name: _,
+            country,
+        } => Ok(PaymentSourceItem::Eps(RedirectRequest {
+            name: billing_details.get_billing_name()?,
+            country_code: country.ok_or(errors::ConnectorError::MissingRequiredField {
+                field_name: "eps.country",
+            })?,
+            experience_context: ContextStruct {
+                return_url: item.request.complete_authorize_url.clone(),
+                cancel_url: item.request.complete_authorize_url.clone(),
+            },
+        })),
+        BankRedirectData::Giropay {
+            billing_details,
+            country,
+            ..
+        } => Ok(PaymentSourceItem::Giropay(RedirectRequest {
+            name: billing_details.get_billing_name()?,
+            country_code: country.ok_or(errors::ConnectorError::MissingRequiredField {
+                field_name: "giropay.country",
+            })?,
+            experience_context: ContextStruct {
+                return_url: item.request.complete_authorize_url.clone(),
+                cancel_url: item.request.complete_authorize_url.clone(),
+            },
+        })),
+        BankRedirectData::Ideal {
+            billing_details,
+            bank_name: _,
+            country,
+        } => Ok(PaymentSourceItem::IDeal(RedirectRequest {
+            name: billing_details.get_billing_name()?,
+            country_code: country.ok_or(errors::ConnectorError::MissingRequiredField {
+                field_name: "ideal.country",
+            })?,
+            experience_context: ContextStruct {
+                return_url: item.request.complete_authorize_url.clone(),
+                cancel_url: item.request.complete_authorize_url.clone(),
+            },
+        })),
+        BankRedirectData::Sofort {
+            country,
+            preferred_language: _,
+            billing_details,
+        } => Ok(PaymentSourceItem::Sofort(RedirectRequest {
+            name: billing_details.get_billing_name()?,
+            country_code: *country,
+            experience_context: ContextStruct {
+                return_url: item.request.complete_authorize_url.clone(),
+                cancel_url: item.request.complete_authorize_url.clone(),
+            },
+        })),
+        _ => Err(errors::ConnectorError::NotImplemented("Bank Redirect".to_string()).into()),
+    }
+}
 
 impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaypalPaymentsRequest {
     type Error = error_stack::Report<errors::ConnectorError>;
@@ -152,6 +220,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaypalPaymentsRequest {
                         Some(PaymentSourceItem::Paypal(PaypalRedirectionRequest {
                             experience_context: ContextStruct {
                                 return_url: item.request.complete_authorize_url.clone(),
+                                cancel_url: item.request.complete_authorize_url.clone(),
                             },
                         }));
 
@@ -165,6 +234,31 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaypalPaymentsRequest {
                     "Payment Method".to_string(),
                 ))?,
             },
+            api::PaymentMethodData::BankRedirect(ref bank_redirection_data) => {
+                let intent = match item.request.is_auto_capture()? {
+                    true => PaypalPaymentIntent::Capture,
+                    false => Err(errors::ConnectorError::FlowNotSupported {
+                        flow: "Manual capture method for Bank Redirect".to_string(),
+                        connector: "Paypal".to_string(),
+                    })?,
+                };
+                let amount = OrderAmount {
+                    currency_code: item.request.currency,
+                    value: item.request.amount.to_string(),
+                };
+                let reference_id = item.attempt_id.clone();
+                let purchase_units = vec![PurchaseUnitRequest {
+                    reference_id,
+                    amount,
+                }];
+                let payment_source = Some(get_payment_source(item, bank_redirection_data)?);
+
+                Ok(Self {
+                    intent,
+                    purchase_units,
+                    payment_source,
+                })
+            }
             _ => Err(errors::ConnectorError::NotImplemented("Payment Method".to_string()).into()),
         }
     }
diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs
index 221ad0487bf..e7f36449640 100644
--- a/crates/router/src/connector/utils.rs
+++ b/crates/router/src/connector/utils.rs
@@ -795,6 +795,18 @@ impl AddressDetailsData for api::AddressDetails {
     }
 }
 
+pub trait BankRedirectBillingData {
+    fn get_billing_name(&self) -> Result<Secret<String>, Error>;
+}
+
+impl BankRedirectBillingData for payments::BankRedirectBilling {
+    fn get_billing_name(&self) -> Result<Secret<String>, Error> {
+        self.billing_name
+            .clone()
+            .ok_or_else(missing_field_err("billing_details.billing_name"))
+    }
+}
+
 pub trait MandateData {
     fn get_end_date(&self, format: date_time::DateFormat) -> Result<String, Error>;
     fn get_metadata(&self) -> Result<pii::SecretSerdeValue, Error>;
diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs
index 6dca567567c..5a504a0facc 100644
--- a/crates/router/src/core/payment_methods/vault.rs
+++ b/crates/router/src/core/payment_methods/vault.rs
@@ -211,12 +211,57 @@ impl Vaultable for api::WalletData {
     }
 }
 
+impl Vaultable for api_models::payments::BankRedirectData {
+    fn get_value1(&self, _customer_id: Option<String>) -> CustomResult<String, errors::VaultError> {
+        let value1 = api_models::payment_methods::TokenizedBankRedirectValue1 {
+            data: self.to_owned(),
+        };
+
+        utils::Encode::<api_models::payment_methods::TokenizedBankRedirectValue1>::encode_to_string_of_json(&value1)
+            .change_context(errors::VaultError::RequestEncodingFailed)
+            .attach_printable("Failed to encode bank redirect data")
+    }
+
+    fn get_value2(&self, customer_id: Option<String>) -> CustomResult<String, errors::VaultError> {
+        let value2 = api_models::payment_methods::TokenizedBankRedirectValue2 { customer_id };
+
+        utils::Encode::<api_models::payment_methods::TokenizedBankRedirectValue2>::encode_to_string_of_json(&value2)
+            .change_context(errors::VaultError::RequestEncodingFailed)
+            .attach_printable("Failed to encode bank redirect supplementary data")
+    }
+
+    fn from_values(
+        value1: String,
+        value2: String,
+    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
+        let value1: api_models::payment_methods::TokenizedBankRedirectValue1 = value1
+            .parse_struct("TokenizedBankRedirectValue1")
+            .change_context(errors::VaultError::ResponseDeserializationFailed)
+            .attach_printable("Could not deserialize into bank redirect data")?;
+
+        let value2: api_models::payment_methods::TokenizedBankRedirectValue2 = value2
+            .parse_struct("TokenizedBankRedirectValue2")
+            .change_context(errors::VaultError::ResponseDeserializationFailed)
+            .attach_printable("Could not deserialize into supplementary bank redirect data")?;
+
+        let bank_transfer_data = value1.data;
+
+        let supp_data = SupplementaryVaultData {
+            customer_id: value2.customer_id,
+            payment_method_id: None,
+        };
+
+        Ok((bank_transfer_data, supp_data))
+    }
+}
+
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 #[serde(tag = "type", content = "value", rename_all = "snake_case")]
 pub enum VaultPaymentMethod {
     Card(String),
     Wallet(String),
     BankTransfer(String),
+    BankRedirect(String),
 }
 
 impl Vaultable for api::PaymentMethodData {
@@ -227,6 +272,9 @@ impl Vaultable for api::PaymentMethodData {
             Self::BankTransfer(bank_transfer) => {
                 VaultPaymentMethod::BankTransfer(bank_transfer.get_value1(customer_id)?)
             }
+            Self::BankRedirect(bank_redirect) => {
+                VaultPaymentMethod::BankRedirect(bank_redirect.get_value1(customer_id)?)
+            }
             _ => Err(errors::VaultError::PaymentMethodNotSupported)
                 .into_report()
                 .attach_printable("Payment method not supported")?,
@@ -244,6 +292,9 @@ impl Vaultable for api::PaymentMethodData {
             Self::BankTransfer(bank_transfer) => {
                 VaultPaymentMethod::BankTransfer(bank_transfer.get_value2(customer_id)?)
             }
+            Self::BankRedirect(bank_redirect) => {
+                VaultPaymentMethod::BankRedirect(bank_redirect.get_value2(customer_id)?)
+            }
             _ => Err(errors::VaultError::PaymentMethodNotSupported)
                 .into_report()
                 .attach_printable("Payment method not supported")?,
@@ -285,6 +336,15 @@ impl Vaultable for api::PaymentMethodData {
                     api_models::payments::BankTransferData::from_values(mvalue1, mvalue2)?;
                 Ok((Self::BankTransfer(Box::new(bank_transfer)), supp_data))
             }
+            (
+                VaultPaymentMethod::BankRedirect(mvalue1),
+                VaultPaymentMethod::BankRedirect(mvalue2),
+            ) => {
+                let (bank_redirect, supp_data) =
+                    api_models::payments::BankRedirectData::from_values(mvalue1, mvalue2)?;
+                Ok((Self::BankRedirect(bank_redirect), supp_data))
+            }
+
             _ => Err(errors::VaultError::PaymentMethodNotSupported)
                 .into_report()
                 .attach_printable("Payment method not supported"),
diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs
index 326ac771960..2970034d84c 100644
--- a/crates/router/src/core/payments/helpers.rs
+++ b/crates/router/src/core/payments/helpers.rs
@@ -1258,6 +1258,11 @@ pub async fn make_pm_data<'a, F: Clone, R>(
                         Some(storage_enums::PaymentMethod::BankTransfer);
                     pm
                 }
+                Some(api::PaymentMethodData::BankRedirect(_)) => {
+                    payment_data.payment_attempt.payment_method =
+                        Some(storage_enums::PaymentMethod::BankRedirect);
+                    pm
+                }
                 Some(_) => Err(errors::ApiErrorResponse::InternalServerError)
                     .into_report()
                     .attach_printable(
@@ -1280,7 +1285,6 @@ pub async fn make_pm_data<'a, F: Clone, R>(
             Ok(pm_opt.to_owned())
         }
         (pm @ Some(api::PaymentMethodData::PayLater(_)), _) => Ok(pm.to_owned()),
-        (pm @ Some(api::PaymentMethodData::BankRedirect(_)), _) => Ok(pm.to_owned()),
         (pm @ Some(api::PaymentMethodData::Crypto(_)), _) => Ok(pm.to_owned()),
         (pm @ Some(api::PaymentMethodData::BankDebit(_)), _) => Ok(pm.to_owned()),
         (pm @ Some(api::PaymentMethodData::Upi(_)), _) => Ok(pm.to_owned()),
@@ -1311,6 +1315,18 @@ pub async fn make_pm_data<'a, F: Clone, R>(
             payment_data.token = Some(token);
             Ok(pm_opt.to_owned())
         }
+        (pm_opt @ Some(pm @ api::PaymentMethodData::BankRedirect(_)), _) => {
+            let token = vault::Vault::store_payment_method_data_in_locker(
+                state,
+                None,
+                pm,
+                payment_data.payment_intent.customer_id.to_owned(),
+                enums::PaymentMethod::BankRedirect,
+            )
+            .await?;
+            payment_data.token = Some(token);
+            Ok(pm_opt.to_owned())
+        }
         _ => Ok(None),
     }?;
 
diff --git a/crates/test_utils/tests/connectors/paypal_ui.rs b/crates/test_utils/tests/connectors/paypal_ui.rs
index 3e9fc162dc2..6f08c87be2e 100644
--- a/crates/test_utils/tests/connectors/paypal_ui.rs
+++ b/crates/test_utils/tests/connectors/paypal_ui.rs
@@ -19,8 +19,8 @@ async fn should_make_paypal_paypal_wallet_payment(
         web_driver,
         &format!("{CHEKOUT_BASE_URL}/saved/21"),
         vec![
-            Event::Trigger(Trigger::Click(By::Css(".reviewButton"))),
-            Event::Assert(Assert::IsPresent("How Search works")),
+            Event::Trigger(Trigger::Click(By::Css("#payment-submit-btn"))),
+            Event::Assert(Assert::IsPresent("Google")),
             Event::Assert(Assert::ContainsAny(
                 Selector::QueryParamStr,
                 vec!["status=processing"],
@@ -31,8 +31,92 @@ async fn should_make_paypal_paypal_wallet_payment(
     Ok(())
 }
 
+async fn should_make_paypal_ideal_payment(web_driver: WebDriver) -> Result<(), WebDriverError> {
+    let conn = PaypalSeleniumTest {};
+    conn.make_redirection_payment(
+        web_driver,
+        vec![
+            Event::Trigger(Trigger::Goto(&format!("{CHEKOUT_BASE_URL}/saved/181"))),
+            Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
+            Event::Trigger(Trigger::Click(By::Name("Successful"))),
+            Event::Assert(Assert::IsPresent("processing")),
+        ],
+    )
+    .await?;
+    Ok(())
+}
+
+async fn should_make_paypal_giropay_payment(web_driver: WebDriver) -> Result<(), WebDriverError> {
+    let conn = PaypalSeleniumTest {};
+    conn.make_redirection_payment(
+        web_driver,
+        vec![
+            Event::Trigger(Trigger::Goto(&format!("{CHEKOUT_BASE_URL}/saved/233"))),
+            Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
+            Event::Trigger(Trigger::Click(By::Name("Successful"))),
+            Event::Assert(Assert::IsPresent("processing")),
+        ],
+    )
+    .await?;
+    Ok(())
+}
+
+async fn should_make_paypal_eps_payment(web_driver: WebDriver) -> Result<(), WebDriverError> {
+    let conn = PaypalSeleniumTest {};
+    conn.make_redirection_payment(
+        web_driver,
+        vec![
+            Event::Trigger(Trigger::Goto(&format!("{CHEKOUT_BASE_URL}/saved/234"))),
+            Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
+            Event::Trigger(Trigger::Click(By::Name("Successful"))),
+            Event::Assert(Assert::IsPresent("processing")),
+        ],
+    )
+    .await?;
+    Ok(())
+}
+
+async fn should_make_paypal_sofort_payment(web_driver: WebDriver) -> Result<(), WebDriverError> {
+    let conn = PaypalSeleniumTest {};
+    conn.make_redirection_payment(
+        web_driver,
+        vec![
+            Event::Trigger(Trigger::Goto(&format!("{CHEKOUT_BASE_URL}/saved/235"))),
+            Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
+            Event::Trigger(Trigger::Click(By::Name("Successful"))),
+            Event::Assert(Assert::IsPresent("processing")),
+        ],
+    )
+    .await?;
+    Ok(())
+}
+
 #[test]
 #[serial]
 fn should_make_paypal_paypal_wallet_payment_test() {
     tester!(should_make_paypal_paypal_wallet_payment);
 }
+
+#[test]
+#[serial]
+fn should_make_paypal_ideal_payment_test() {
+    tester!(should_make_paypal_ideal_payment);
+}
+
+#[test]
+#[serial]
+fn should_make_paypal_giropay_payment_test() {
+    tester!(should_make_paypal_giropay_payment);
+}
+
+#[test]
+#[serial]
+fn should_make_paypal_eps_payment_test() {
+    tester!(should_make_paypal_eps_payment);
+}
+
+#[test]
+#[serial]
+fn should_make_paypal_sofort_payment_test() {
+    tester!(should_make_paypal_sofort_payment);
+}
diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json
index be303878459..d3e61a909f6 100644
--- a/openapi/openapi_spec.json
+++ b/openapi/openapi_spec.json
@@ -2827,7 +2827,8 @@
                 "type": "object",
                 "required": [
                   "billing_details",
-                  "bank_name"
+                  "bank_name",
+                  "country"
                 ],
                 "properties": {
                   "billing_details": {
@@ -2835,6 +2836,9 @@
                   },
                   "bank_name": {
                     "$ref": "#/components/schemas/BankNames"
+                  },
+                  "country": {
+                    "$ref": "#/components/schemas/CountryAlpha2"
                   }
                 }
               }
@@ -2849,7 +2853,8 @@
               "giropay": {
                 "type": "object",
                 "required": [
-                  "billing_details"
+                  "billing_details",
+                  "country"
                 ],
                 "properties": {
                   "billing_details": {
@@ -2864,6 +2869,9 @@
                     "type": "string",
                     "description": "Bank account iban",
                     "nullable": true
+                  },
+                  "country": {
+                    "$ref": "#/components/schemas/CountryAlpha2"
                   }
                 }
               }
@@ -2879,7 +2887,8 @@
                 "type": "object",
                 "required": [
                   "billing_details",
-                  "bank_name"
+                  "bank_name",
+                  "country"
                 ],
                 "properties": {
                   "billing_details": {
@@ -2887,6 +2896,9 @@
                   },
                   "bank_name": {
                     "$ref": "#/components/schemas/BankNames"
+                  },
+                  "country": {
+                    "$ref": "#/components/schemas/CountryAlpha2"
                   }
                 }
               }

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

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

## Description
<!-- Describe your changes in detail -->
Add bank redirects - Ideal, Eps, Giropay, and Sofort for Paypal connector.


### Additional Changes

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

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

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


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless its an obvious bug or documentation fix
that will have little conversation).
-->
 

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
Tested using postman.
<img width="1264" alt="Screenshot 2023-05-10 at 2 54 12 PM" src="https://github.com/juspay/hyperswitch/assets/70575890/4959d087-59eb-446e-8d16-d8300a48fe4a">

<img width="1240" alt="Screenshot 2023-05-10 at 2 55 11 PM" src="https://github.com/juspay/hyperswitch/assets/70575890/0cfbd9f7-a745-4e83-aa21-f20d4e3ab26f">

Tested a successful payment
<img width="1082" alt="Screenshot 2023-05-10 at 2 55 52 PM" src="https://github.com/juspay/hyperswitch/assets/70575890/3a9daec8-ecf0-40e4-9797-53c769867e54">

<img width="1276" alt="Screenshot 2023-05-10 at 2 56 26 PM" src="https://github.com/juspay/hyperswitch/assets/70575890/f5d02eb1-51ba-4218-9223-631593b321b4">

Tested a failed payment
<img width="1181" alt="Screenshot 2023-05-10 at 2 59 02 PM" src="https://github.com/juspay/hyperswitch/assets/70575890/a59684e1-d999-468c-a5df-b6eda7d26ec8">

<img width="1212" alt="Screenshot 2023-05-10 at 2 59 23 PM" src="https://github.com/juspay/hyperswitch/assets/70575890/2cba6e5b-559a-4d18-8065-c6cbc5b625d6">

UI Tests for Bank Redirects
<img width="984" alt="Screen Shot 2023-07-28 at 4 18 19 PM" src="https://github.com/juspay/hyperswitch/assets/70575890/013799d6-312f-46d6-9062-1fe043974133">


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

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed submitted code
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
