diff --git a/Cargo.lock b/Cargo.lock index 3ef85ac8f6..a310cff5c1 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 41800ad197..a7dcc8941d 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 74f678b287..9869f52ea6 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>, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum WorldlinePaymentMethod { + CardPaymentMethodSpecificInput(Box), + RedirectPaymentMethodSpecificInput(Box), +} + +#[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, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum PaymentMethodSpecificData { + PaymentProduct816SpecificInput(Box), + PaymentProduct809SpecificInput(Box), +} + +#[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, + pub iban: Option>, +} + +#[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, } @@ -104,15 +179,44 @@ pub struct PaymentsRequest { impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentsRequest { type Error = error_stack::Report; fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { - 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 for Gateway { } } +impl TryFrom<&api_models::enums::BankNames> for WorldlineBic { + type Error = error_stack::Report; + fn try_from(bank: &api_models::enums::BankNames) -> Result { + 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> { + ccard: &payments::Card, +) -> Result> { 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> { + 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 for BillingAddress { - fn from(value: api_models::AddressDetails) -> Self { +impl From for BillingAddress { + fn from(value: payments::AddressDetails) -> Self { Self { city: value.city, country_code: value.country, @@ -238,8 +386,8 @@ impl From for BillingAddress { } } -impl From for Shipping { - fn from(value: api_models::AddressDetails) -> Self { +impl From 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 TryFrom, +} + +#[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 TryFrom> @@ -368,6 +532,13 @@ impl TryFrom, ) -> Result { + 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 TryFrom