Bug: [Feature] Bank redirect support (IDeal, Giropay) for worldline



This issue is regarding Bank redirect(Ideal ,Giropay) support for worldline
diff --git a/Cargo.lock b/Cargo.lock
index 3ef85ac8f6a..a310cff5c1a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2968,7 +2968,7 @@ dependencies = [
 [[package]]
 name = "opentelemetry"
 version = "0.18.0"
-source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
+source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
 dependencies = [
  "opentelemetry_api",
  "opentelemetry_sdk",
@@ -2977,7 +2977,7 @@ dependencies = [
 [[package]]
 name = "opentelemetry-otlp"
 version = "0.11.0"
-source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
+source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
 dependencies = [
  "async-trait",
  "futures",
@@ -2994,7 +2994,7 @@ dependencies = [
 [[package]]
 name = "opentelemetry-proto"
 version = "0.1.0"
-source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
+source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
 dependencies = [
  "futures",
  "futures-util",
@@ -3006,7 +3006,7 @@ dependencies = [
 [[package]]
 name = "opentelemetry_api"
 version = "0.18.0"
-source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
+source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
 dependencies = [
  "fnv",
  "futures-channel",
@@ -3021,7 +3021,7 @@ dependencies = [
 [[package]]
 name = "opentelemetry_sdk"
 version = "0.18.0"
-source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
+source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
 dependencies = [
  "async-trait",
  "crossbeam-channel",
diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs
index 41800ad1978..a7dcc8941d2 100644
--- a/crates/api_models/src/enums.rs
+++ b/crates/api_models/src/enums.rs
@@ -755,6 +755,7 @@ pub enum BankNames {
     #[serde(rename = "ePlatby VÚB")]
     EPlatbyVUB,
     ErsteBankUndSparkassen,
+    FrieslandBank,
     HypoAlpeadriabankInternationalAg,
     HypoNoeLbFurNiederosterreichUWien,
     HypoOberosterreichSalzburgSteiermark,
diff --git a/crates/router/src/connector/worldline/transformers.rs b/crates/router/src/connector/worldline/transformers.rs
index 74f678b2877..9869f52ea6f 100644
--- a/crates/router/src/connector/worldline/transformers.rs
+++ b/crates/router/src/connector/worldline/transformers.rs
@@ -1,11 +1,13 @@
-use api_models::payments as api_models;
+use api_models::payments;
 use common_utils::pii::Email;
 use masking::{PeekInterface, Secret};
 use serde::{Deserialize, Serialize};
+use url::Url;
 
 use crate::{
     connector::utils::{self, CardData},
     core::errors,
+    services,
     types::{
         self,
         api::{self, enums as api_enums},
@@ -93,10 +95,83 @@ pub struct Shipping {
     pub zip: Option<Secret<String>>,
 }
 
-#[derive(Default, Debug, Serialize, Eq, PartialEq)]
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub enum WorldlinePaymentMethod {
+    CardPaymentMethodSpecificInput(Box<CardPaymentMethod>),
+    RedirectPaymentMethodSpecificInput(Box<RedirectPaymentMethod>),
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct RedirectPaymentMethod {
+    pub payment_product_id: u16,
+    pub redirection_data: RedirectionData,
+    #[serde(flatten)]
+    pub payment_method_specific_data: PaymentMethodSpecificData,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct RedirectionData {
+    pub return_url: Option<String>,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub enum PaymentMethodSpecificData {
+    PaymentProduct816SpecificInput(Box<Giropay>),
+    PaymentProduct809SpecificInput(Box<Ideal>),
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Giropay {
+    pub bank_account_iban: BankAccountIban,
+}
+
+#[derive(Debug, Serialize)]
+pub struct Ideal {
+    #[serde(rename = "issuerId")]
+    pub issuer_id: WorldlineBic,
+}
+
+#[derive(Debug, Serialize)]
+pub enum WorldlineBic {
+    #[serde(rename = "ABNANL2A")]
+    Abnamro,
+    #[serde(rename = "ASNBNL21")]
+    Asn,
+    #[serde(rename = "FRBKNL2L")]
+    Friesland,
+    #[serde(rename = "KNABNL2H")]
+    Knab,
+    #[serde(rename = "RABONL2U")]
+    Rabobank,
+    #[serde(rename = "RBRBNL21")]
+    Regiobank,
+    #[serde(rename = "SNSBNL2A")]
+    Sns,
+    #[serde(rename = "TRIONL2U")]
+    Triodos,
+    #[serde(rename = "FVLBNL22")]
+    Vanlanschot,
+    #[serde(rename = "INGBNL2A")]
+    Ing,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BankAccountIban {
+    pub account_holder_name: Secret<String>,
+    pub iban: Option<Secret<String>>,
+}
+
+#[derive(Debug, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct PaymentsRequest {
-    pub card_payment_method_specific_input: CardPaymentMethod,
+    #[serde(flatten)]
+    pub payment_data: WorldlinePaymentMethod,
     pub order: Order,
     pub shipping: Option<Shipping>,
 }
@@ -104,15 +179,44 @@ pub struct PaymentsRequest {
 impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentsRequest {
     type Error = error_stack::Report<errors::ConnectorError>;
     fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
-        match item.request.payment_method_data {
+        let payment_data = match item.request.payment_method_data {
             api::PaymentMethodData::Card(ref card) => {
-                make_card_request(&item.address, &item.request, card)
+                WorldlinePaymentMethod::CardPaymentMethodSpecificInput(Box::new(make_card_request(
+                    &item.request,
+                    card,
+                )?))
             }
-            _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
-        }
+            api::PaymentMethodData::BankRedirect(ref bank_redirect) => {
+                WorldlinePaymentMethod::RedirectPaymentMethodSpecificInput(Box::new(
+                    make_bank_redirect_request(&item.request, bank_redirect)?,
+                ))
+            }
+            _ => Err(errors::ConnectorError::NotImplemented(
+                "Payment methods".to_string(),
+            ))?,
+        };
+        let customer = build_customer_info(&item.address, &item.request.email)?;
+        let order = Order {
+            amount_of_money: AmountOfMoney {
+                amount: item.request.amount,
+                currency_code: item.request.currency.to_string().to_uppercase(),
+            },
+            customer,
+        };
+
+        let shipping = item
+            .address
+            .shipping
+            .as_ref()
+            .and_then(|shipping| shipping.address.clone())
+            .map(Shipping::from);
+        Ok(Self {
+            payment_data,
+            order,
+            shipping,
+        })
     }
 }
-
 #[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
 pub enum Gateway {
     Amex = 2,
@@ -139,11 +243,33 @@ impl TryFrom<utils::CardIssuer> for Gateway {
     }
 }
 
+impl TryFrom<&api_models::enums::BankNames> for WorldlineBic {
+    type Error = error_stack::Report<errors::ConnectorError>;
+    fn try_from(bank: &api_models::enums::BankNames) -> Result<Self, Self::Error> {
+        match bank {
+            api_models::enums::BankNames::AbnAmro => Ok(Self::Abnamro),
+            api_models::enums::BankNames::AsnBank => Ok(Self::Asn),
+            api_models::enums::BankNames::Ing => Ok(Self::Ing),
+            api_models::enums::BankNames::Knab => Ok(Self::Knab),
+            api_models::enums::BankNames::Rabobank => Ok(Self::Rabobank),
+            api_models::enums::BankNames::Regiobank => Ok(Self::Regiobank),
+            api_models::enums::BankNames::SnsBank => Ok(Self::Sns),
+            api_models::enums::BankNames::TriodosBank => Ok(Self::Triodos),
+            api_models::enums::BankNames::VanLanschot => Ok(Self::Vanlanschot),
+            api_models::enums::BankNames::FrieslandBank => Ok(Self::Friesland),
+            _ => Err(errors::ConnectorError::FlowNotSupported {
+                flow: bank.to_string(),
+                connector: "Worldline".to_string(),
+            }
+            .into()),
+        }
+    }
+}
+
 fn make_card_request(
-    address: &types::PaymentAddress,
     req: &types::PaymentsAuthorizeData,
-    ccard: &api_models::Card,
-) -> Result<PaymentsRequest, error_stack::Report<errors::ConnectorError>> {
+    ccard: &payments::Card,
+) -> Result<CardPaymentMethod, error_stack::Report<errors::ConnectorError>> {
     let expiry_year = ccard.card_exp_year.peek().clone();
     let secret_value = format!(
         "{}{}",
@@ -164,33 +290,55 @@ fn make_card_request(
         requires_approval: matches!(req.capture_method, Some(enums::CaptureMethod::Manual)),
         payment_product_id,
     };
+    Ok(card_payment_method_specific_input)
+}
 
-    let customer = build_customer_info(address, &req.email)?;
-
-    let order = Order {
-        amount_of_money: AmountOfMoney {
-            amount: req.amount,
-            currency_code: req.currency.to_string().to_uppercase(),
-        },
-        customer,
+fn make_bank_redirect_request(
+    req: &types::PaymentsAuthorizeData,
+    bank_redirect: &payments::BankRedirectData,
+) -> Result<RedirectPaymentMethod, error_stack::Report<errors::ConnectorError>> {
+    let return_url = req.router_return_url.clone();
+    let redirection_data = RedirectionData { return_url };
+    let (payment_method_specific_data, payment_product_id) = match bank_redirect {
+        payments::BankRedirectData::Giropay {
+            billing_details,
+            bank_account_iban,
+            ..
+        } => (
+            {
+                PaymentMethodSpecificData::PaymentProduct816SpecificInput(Box::new(Giropay {
+                    bank_account_iban: BankAccountIban {
+                        account_holder_name: billing_details.billing_name.clone(),
+                        iban: bank_account_iban.clone(),
+                    },
+                }))
+            },
+            816,
+        ),
+        payments::BankRedirectData::Ideal { bank_name, .. } => (
+            {
+                PaymentMethodSpecificData::PaymentProduct809SpecificInput(Box::new(Ideal {
+                    issuer_id: WorldlineBic::try_from(bank_name)?,
+                }))
+            },
+            809,
+        ),
+        _ => {
+            return Err(
+                errors::ConnectorError::NotImplemented("Payment methods".to_string()).into(),
+            )
+        }
     };
-
-    let shipping = address
-        .shipping
-        .as_ref()
-        .and_then(|shipping| shipping.address.clone())
-        .map(|address| Shipping { ..address.into() });
-
-    Ok(PaymentsRequest {
-        card_payment_method_specific_input,
-        order,
-        shipping,
+    Ok(RedirectPaymentMethod {
+        payment_product_id,
+        redirection_data,
+        payment_method_specific_data,
     })
 }
 
 fn get_address(
     payment_address: &types::PaymentAddress,
-) -> Option<(&api_models::Address, &api_models::AddressDetails)> {
+) -> Option<(&payments::Address, &payments::AddressDetails)> {
     let billing = payment_address.billing.as_ref()?;
     let address = billing.address.as_ref()?;
     address.country.as_ref()?;
@@ -226,8 +374,8 @@ fn build_customer_info(
     })
 }
 
-impl From<api_models::AddressDetails> for BillingAddress {
-    fn from(value: api_models::AddressDetails) -> Self {
+impl From<payments::AddressDetails> for BillingAddress {
+    fn from(value: payments::AddressDetails) -> Self {
         Self {
             city: value.city,
             country_code: value.country,
@@ -238,8 +386,8 @@ impl From<api_models::AddressDetails> for BillingAddress {
     }
 }
 
-impl From<api_models::AddressDetails> for Shipping {
-    fn from(value: api_models::AddressDetails) -> Self {
+impl From<payments::AddressDetails> for Shipping {
+    fn from(value: payments::AddressDetails) -> Self {
         Self {
             city: value.city,
             country_code: value.country,
@@ -295,6 +443,7 @@ pub enum PaymentStatus {
     #[default]
     Processing,
     Created,
+    Redirected,
 }
 
 impl ForeignFrom<(PaymentStatus, enums::CaptureMethod)> for enums::AttemptStatus {
@@ -316,6 +465,7 @@ impl ForeignFrom<(PaymentStatus, enums::CaptureMethod)> for enums::AttemptStatus
             }
             PaymentStatus::PendingApproval => Self::Authorized,
             PaymentStatus::Created => Self::Started,
+            PaymentStatus::Redirected => Self::AuthenticationPending,
             _ => Self::Pending,
         }
     }
@@ -356,9 +506,23 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, Payment, T, types::PaymentsRespo
     }
 }
 
-#[derive(Default, Debug, Clone, Deserialize, PartialEq)]
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
 pub struct PaymentResponse {
     pub payment: Payment,
+    pub merchant_action: Option<MerchantAction>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct MerchantAction {
+    pub redirect_data: RedirectData,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct RedirectData {
+    #[serde(rename = "redirectURL")]
+    pub redirect_url: Url,
 }
 
 impl<F, T> TryFrom<types::ResponseRouterData<F, PaymentResponse, T, types::PaymentsResponseData>>
@@ -368,6 +532,13 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, PaymentResponse, T, types::Payme
     fn try_from(
         item: types::ResponseRouterData<F, PaymentResponse, T, types::PaymentsResponseData>,
     ) -> Result<Self, Self::Error> {
+        let redirection_data = item
+            .response
+            .merchant_action
+            .map(|action| action.redirect_data.redirect_url)
+            .map(|redirect_url| {
+                services::RedirectForm::from((redirect_url, services::Method::Get))
+            });
         Ok(Self {
             status: enums::AttemptStatus::foreign_from((
                 item.response.payment.status,
@@ -375,7 +546,7 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, PaymentResponse, T, types::Payme
             )),
             response: Ok(types::PaymentsResponseData::TransactionResponse {
                 resource_id: types::ResponseId::ConnectorTransactionId(item.response.payment.id),
-                redirection_data: None,
+                redirection_data,
                 mandate_reference: None,
                 connector_metadata: None,
                 network_txn_id: None,

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

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

## Description
<!-- Describe your changes in detail -->
This PR has following changes 
 1)Bank Redirects (Ideal and giropay)
2) Add `FrieslandBank` bank in `BankNames`  enum

**Note** : For the worldline connector , the final status of card payments remains in the processing state within their sandbox environment. Likewise, in their sandbox environment, the status of their bank redirects remains in the "redirected" state, which is `requiresCustomerAction` for us .

### 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).
-->
Adding bank redirect support for worldline

## 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 existing card payment** 
<img width="871" alt="Screenshot 2023-05-05 at 7 14 48 PM" src="https://user-images.githubusercontent.com/121822803/236483230-2eebf35a-ccf1-4661-ab59-4bc8becc34f2.png">

**tested bank redirects using postman** 
**Giropay**

<img width="1285" alt="Screenshot 2023-05-05 at 4 59 15 PM" src="https://user-images.githubusercontent.com/121822803/236480959-3451bbd6-74bd-47ba-999e-2d8be0369cc4.png">

**Redirected to return url**
<img width="1452" alt="Screenshot 2023-05-05 at 4 58 46 PM" src="https://user-images.githubusercontent.com/121822803/236480991-4ae82fc8-5f15-4484-9e78-645975d6e542.png">

**payment status**
<img width="1293" alt="Screenshot 2023-05-05 at 4 59 35 PM" src="https://user-images.githubusercontent.com/121822803/236481125-3cb42f94-c9ef-4620-ae75-e24eac9c9546.png">

**ideal**
<img width="1276" alt="Screenshot 2023-05-05 at 5 00 04 PM" src="https://user-images.githubusercontent.com/121822803/236481176-0e8bb281-e588-4acd-a5cc-cfc535fbfa40.png">



## 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
