feat(connector) : add bank redirect support for worldline (#1060)

Signed-off-by: chikke srujan <121822803+srujanchikke@users.noreply.github.com>
This commit is contained in:
chikke srujan
2023-05-10 00:08:43 +05:30
committed by GitHub
parent 1304d912e5
commit bc4ac529aa
3 changed files with 214 additions and 42 deletions

10
Cargo.lock generated
View File

@ -2968,7 +2968,7 @@ dependencies = [
[[package]] [[package]]
name = "opentelemetry" name = "opentelemetry"
version = "0.18.0" 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 = [ dependencies = [
"opentelemetry_api", "opentelemetry_api",
"opentelemetry_sdk", "opentelemetry_sdk",
@ -2977,7 +2977,7 @@ dependencies = [
[[package]] [[package]]
name = "opentelemetry-otlp" name = "opentelemetry-otlp"
version = "0.11.0" 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 = [ dependencies = [
"async-trait", "async-trait",
"futures", "futures",
@ -2994,7 +2994,7 @@ dependencies = [
[[package]] [[package]]
name = "opentelemetry-proto" name = "opentelemetry-proto"
version = "0.1.0" 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 = [ dependencies = [
"futures", "futures",
"futures-util", "futures-util",
@ -3006,7 +3006,7 @@ dependencies = [
[[package]] [[package]]
name = "opentelemetry_api" name = "opentelemetry_api"
version = "0.18.0" 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 = [ dependencies = [
"fnv", "fnv",
"futures-channel", "futures-channel",
@ -3021,7 +3021,7 @@ dependencies = [
[[package]] [[package]]
name = "opentelemetry_sdk" name = "opentelemetry_sdk"
version = "0.18.0" 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 = [ dependencies = [
"async-trait", "async-trait",
"crossbeam-channel", "crossbeam-channel",

View File

@ -755,6 +755,7 @@ pub enum BankNames {
#[serde(rename = "ePlatby VÚB")] #[serde(rename = "ePlatby VÚB")]
EPlatbyVUB, EPlatbyVUB,
ErsteBankUndSparkassen, ErsteBankUndSparkassen,
FrieslandBank,
HypoAlpeadriabankInternationalAg, HypoAlpeadriabankInternationalAg,
HypoNoeLbFurNiederosterreichUWien, HypoNoeLbFurNiederosterreichUWien,
HypoOberosterreichSalzburgSteiermark, HypoOberosterreichSalzburgSteiermark,

View File

@ -1,11 +1,13 @@
use api_models::payments as api_models; use api_models::payments;
use common_utils::pii::Email; use common_utils::pii::Email;
use masking::{PeekInterface, Secret}; use masking::{PeekInterface, Secret};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url;
use crate::{ use crate::{
connector::utils::{self, CardData}, connector::utils::{self, CardData},
core::errors, core::errors,
services,
types::{ types::{
self, self,
api::{self, enums as api_enums}, api::{self, enums as api_enums},
@ -93,10 +95,83 @@ pub struct Shipping {
pub zip: Option<Secret<String>>, 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")] #[serde(rename_all = "camelCase")]
pub struct PaymentsRequest { pub struct PaymentsRequest {
pub card_payment_method_specific_input: CardPaymentMethod, #[serde(flatten)]
pub payment_data: WorldlinePaymentMethod,
pub order: Order, pub order: Order,
pub shipping: Option<Shipping>, pub shipping: Option<Shipping>,
} }
@ -104,15 +179,44 @@ pub struct PaymentsRequest {
impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentsRequest { impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentsRequest {
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> { 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) => { api::PaymentMethodData::Card(ref card) => {
make_card_request(&item.address, &item.request, card) WorldlinePaymentMethod::CardPaymentMethodSpecificInput(Box::new(make_card_request(
} &item.request,
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), card,
} )?))
} }
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)] #[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
pub enum Gateway { pub enum Gateway {
Amex = 2, 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( fn make_card_request(
address: &types::PaymentAddress,
req: &types::PaymentsAuthorizeData, req: &types::PaymentsAuthorizeData,
ccard: &api_models::Card, ccard: &payments::Card,
) -> Result<PaymentsRequest, error_stack::Report<errors::ConnectorError>> { ) -> Result<CardPaymentMethod, error_stack::Report<errors::ConnectorError>> {
let expiry_year = ccard.card_exp_year.peek().clone(); let expiry_year = ccard.card_exp_year.peek().clone();
let secret_value = format!( let secret_value = format!(
"{}{}", "{}{}",
@ -164,33 +290,55 @@ fn make_card_request(
requires_approval: matches!(req.capture_method, Some(enums::CaptureMethod::Manual)), requires_approval: matches!(req.capture_method, Some(enums::CaptureMethod::Manual)),
payment_product_id, payment_product_id,
}; };
Ok(card_payment_method_specific_input)
}
let customer = build_customer_info(address, &req.email)?; fn make_bank_redirect_request(
req: &types::PaymentsAuthorizeData,
let order = Order { bank_redirect: &payments::BankRedirectData,
amount_of_money: AmountOfMoney { ) -> Result<RedirectPaymentMethod, error_stack::Report<errors::ConnectorError>> {
amount: req.amount, let return_url = req.router_return_url.clone();
currency_code: req.currency.to_string().to_uppercase(), 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(),
}, },
customer, }))
},
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(),
)
}
}; };
Ok(RedirectPaymentMethod {
let shipping = address payment_product_id,
.shipping redirection_data,
.as_ref() payment_method_specific_data,
.and_then(|shipping| shipping.address.clone())
.map(|address| Shipping { ..address.into() });
Ok(PaymentsRequest {
card_payment_method_specific_input,
order,
shipping,
}) })
} }
fn get_address( fn get_address(
payment_address: &types::PaymentAddress, payment_address: &types::PaymentAddress,
) -> Option<(&api_models::Address, &api_models::AddressDetails)> { ) -> Option<(&payments::Address, &payments::AddressDetails)> {
let billing = payment_address.billing.as_ref()?; let billing = payment_address.billing.as_ref()?;
let address = billing.address.as_ref()?; let address = billing.address.as_ref()?;
address.country.as_ref()?; address.country.as_ref()?;
@ -226,8 +374,8 @@ fn build_customer_info(
}) })
} }
impl From<api_models::AddressDetails> for BillingAddress { impl From<payments::AddressDetails> for BillingAddress {
fn from(value: api_models::AddressDetails) -> Self { fn from(value: payments::AddressDetails) -> Self {
Self { Self {
city: value.city, city: value.city,
country_code: value.country, country_code: value.country,
@ -238,8 +386,8 @@ impl From<api_models::AddressDetails> for BillingAddress {
} }
} }
impl From<api_models::AddressDetails> for Shipping { impl From<payments::AddressDetails> for Shipping {
fn from(value: api_models::AddressDetails) -> Self { fn from(value: payments::AddressDetails) -> Self {
Self { Self {
city: value.city, city: value.city,
country_code: value.country, country_code: value.country,
@ -295,6 +443,7 @@ pub enum PaymentStatus {
#[default] #[default]
Processing, Processing,
Created, Created,
Redirected,
} }
impl ForeignFrom<(PaymentStatus, enums::CaptureMethod)> for enums::AttemptStatus { 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::PendingApproval => Self::Authorized,
PaymentStatus::Created => Self::Started, PaymentStatus::Created => Self::Started,
PaymentStatus::Redirected => Self::AuthenticationPending,
_ => Self::Pending, _ => 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 struct PaymentResponse {
pub payment: Payment, 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>> 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( fn try_from(
item: types::ResponseRouterData<F, PaymentResponse, T, types::PaymentsResponseData>, item: types::ResponseRouterData<F, PaymentResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> { ) -> 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 { Ok(Self {
status: enums::AttemptStatus::foreign_from(( status: enums::AttemptStatus::foreign_from((
item.response.payment.status, item.response.payment.status,
@ -375,7 +546,7 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, PaymentResponse, T, types::Payme
)), )),
response: Ok(types::PaymentsResponseData::TransactionResponse { response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.payment.id), resource_id: types::ResponseId::ConnectorTransactionId(item.response.payment.id),
redirection_data: None, redirection_data,
mandate_reference: None, mandate_reference: None,
connector_metadata: None, connector_metadata: None,
network_txn_id: None, network_txn_id: None,